dsh-prime-memory 0.11.0

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 (151) hide show
  1. package/CHANGELOG.en.md +28 -0
  2. package/CHANGELOG.ja.md +30 -0
  3. package/CHANGELOG.ko.md +30 -0
  4. package/CHANGELOG.md +1220 -0
  5. package/ENGINEERING-NOTES.md +452 -0
  6. package/INSTALL.en.md +92 -0
  7. package/INSTALL.ja.md +92 -0
  8. package/INSTALL.ko.md +92 -0
  9. package/INSTALL.md +92 -0
  10. package/LICENSE +21 -0
  11. package/README.en.md +458 -0
  12. package/README.ja.md +306 -0
  13. package/README.ko.md +306 -0
  14. package/README.md +425 -0
  15. package/assets/changelog/0.8.10/01-write-only-pill.png +0 -0
  16. package/assets/changelog/0.8.9/01-panel.png +0 -0
  17. package/assets/changelog/0.8.9/02-halo.png +0 -0
  18. package/assets/changelog/0.8.9/03-layer-segmented-panel.png +0 -0
  19. package/assets/changelog/0.8.9/04-layer-l1-panel.png +0 -0
  20. package/assets/img/EmbeddingSource.png +0 -0
  21. package/assets/img/Hero.png +0 -0
  22. package/assets/img/Layers.png +0 -0
  23. package/assets/img/MemoryTools.png +0 -0
  24. package/assets/img/Modes.png +0 -0
  25. package/assets/img/ToolTrajectory.png +0 -0
  26. package/assets/img/ui-dark.jpg +0 -0
  27. package/assets/img/ui-light.jpg +0 -0
  28. package/assets/readme/bench-dialog.svg +70 -0
  29. package/assets/readme/bench-workflow.svg +79 -0
  30. package/assets/readme/flow.svg +189 -0
  31. package/assets/readme/storage.svg +115 -0
  32. package/cordis.patch.yml +16 -0
  33. package/dist/bench-control.d.ts +34 -0
  34. package/dist/bench-control.js +16 -0
  35. package/dist/client.js +4293 -0
  36. package/dist/config.d.ts +683 -0
  37. package/dist/config.js +129 -0
  38. package/dist/contract.d.ts +820 -0
  39. package/dist/contract.js +1 -0
  40. package/dist/embedding-worker.cjs +176 -0
  41. package/dist/graph/apply.d.ts +37 -0
  42. package/dist/graph/apply.js +270 -0
  43. package/dist/graph/constraints.d.ts +47 -0
  44. package/dist/graph/constraints.js +38 -0
  45. package/dist/graph/search.d.ts +16 -0
  46. package/dist/graph/search.js +115 -0
  47. package/dist/graph/types.d.ts +142 -0
  48. package/dist/graph/types.js +14 -0
  49. package/dist/hooks/capture.d.ts +32 -0
  50. package/dist/hooks/capture.js +194 -0
  51. package/dist/hooks/recall.d.ts +63 -0
  52. package/dist/hooks/recall.js +429 -0
  53. package/dist/index.d.ts +534 -0
  54. package/dist/index.js +344 -0
  55. package/dist/llm-usage.d.ts +26 -0
  56. package/dist/llm-usage.js +37 -0
  57. package/dist/llm.d.ts +153 -0
  58. package/dist/llm.js +530 -0
  59. package/dist/pipeline/graph.d.ts +35 -0
  60. package/dist/pipeline/graph.js +104 -0
  61. package/dist/pipeline/l1.d.ts +19 -0
  62. package/dist/pipeline/l1.js +271 -0
  63. package/dist/pipeline/l2.d.ts +13 -0
  64. package/dist/pipeline/l2.js +83 -0
  65. package/dist/pipeline/l3.d.ts +15 -0
  66. package/dist/pipeline/l3.js +78 -0
  67. package/dist/pipeline/rebuild.d.ts +61 -0
  68. package/dist/pipeline/rebuild.js +307 -0
  69. package/dist/pipeline/ruminate.d.ts +89 -0
  70. package/dist/pipeline/ruminate.js +298 -0
  71. package/dist/pipeline/runner.d.ts +167 -0
  72. package/dist/pipeline/runner.js +638 -0
  73. package/dist/pipeline/trigger.d.ts +40 -0
  74. package/dist/pipeline/trigger.js +75 -0
  75. package/dist/prompts/graph-projection.d.ts +70 -0
  76. package/dist/prompts/graph-projection.js +167 -0
  77. package/dist/prompts/l1-dedup.d.ts +22 -0
  78. package/dist/prompts/l1-dedup.js +251 -0
  79. package/dist/prompts/l1-extraction.d.ts +22 -0
  80. package/dist/prompts/l1-extraction.js +457 -0
  81. package/dist/prompts/persona.d.ts +23 -0
  82. package/dist/prompts/persona.js +240 -0
  83. package/dist/prompts/scene.d.ts +32 -0
  84. package/dist/prompts/scene.js +414 -0
  85. package/dist/runtime-package-lock.json +982 -0
  86. package/dist/settings.d.ts +50 -0
  87. package/dist/settings.js +355 -0
  88. package/dist/stats.d.ts +109 -0
  89. package/dist/stats.js +929 -0
  90. package/dist/store/bm25.d.ts +19 -0
  91. package/dist/store/bm25.js +63 -0
  92. package/dist/store/cost-ledger.d.ts +75 -0
  93. package/dist/store/cost-ledger.js +171 -0
  94. package/dist/store/download-queue.d.ts +79 -0
  95. package/dist/store/download-queue.js +424 -0
  96. package/dist/store/embedding-source.d.ts +118 -0
  97. package/dist/store/embedding-source.js +443 -0
  98. package/dist/store/embedding.d.ts +90 -0
  99. package/dist/store/embedding.js +206 -0
  100. package/dist/store/graph-store.d.ts +94 -0
  101. package/dist/store/graph-store.js +641 -0
  102. package/dist/store/l0.d.ts +40 -0
  103. package/dist/store/l0.js +197 -0
  104. package/dist/store/l1.d.ts +93 -0
  105. package/dist/store/l1.js +297 -0
  106. package/dist/store/local-embedding.d.ts +89 -0
  107. package/dist/store/local-embedding.js +227 -0
  108. package/dist/store/model-catalog.d.ts +48 -0
  109. package/dist/store/model-catalog.js +81 -0
  110. package/dist/store/occupancy.d.ts +30 -0
  111. package/dist/store/occupancy.js +134 -0
  112. package/dist/store/pending.d.ts +36 -0
  113. package/dist/store/pending.js +103 -0
  114. package/dist/store/persona.d.ts +15 -0
  115. package/dist/store/persona.js +60 -0
  116. package/dist/store/recall-dedupe.d.ts +26 -0
  117. package/dist/store/recall-dedupe.js +138 -0
  118. package/dist/store/runtime-installer.d.ts +59 -0
  119. package/dist/store/runtime-installer.js +243 -0
  120. package/dist/store/scenes.d.ts +24 -0
  121. package/dist/store/scenes.js +160 -0
  122. package/dist/store/search-utils.d.ts +38 -0
  123. package/dist/store/search-utils.js +100 -0
  124. package/dist/store/session-modes.d.ts +35 -0
  125. package/dist/store/session-modes.js +144 -0
  126. package/dist/store/sqlite.d.ts +246 -0
  127. package/dist/store/sqlite.js +1491 -0
  128. package/dist/store/state.d.ts +41 -0
  129. package/dist/store/state.js +72 -0
  130. package/dist/token-cost.d.ts +23 -0
  131. package/dist/token-cost.js +185 -0
  132. package/dist/tools/index.d.ts +34 -0
  133. package/dist/tools/index.js +758 -0
  134. package/dist/types.d.ts +139 -0
  135. package/dist/types.js +38 -0
  136. package/dist/util/context-occupancy.d.ts +68 -0
  137. package/dist/util/context-occupancy.js +92 -0
  138. package/dist/util/filelog.d.ts +6 -0
  139. package/dist/util/filelog.js +108 -0
  140. package/dist/util/io.d.ts +18 -0
  141. package/dist/util/io.js +97 -0
  142. package/dist/util/recall-budget.d.ts +31 -0
  143. package/dist/util/recall-budget.js +84 -0
  144. package/dist/util/sanitize.d.ts +11 -0
  145. package/dist/util/sanitize.js +67 -0
  146. package/dist/util/text.d.ts +16 -0
  147. package/dist/util/text.js +61 -0
  148. package/dist/util/tokenizer.d.ts +9 -0
  149. package/dist/util/tokenizer.js +50 -0
  150. package/dsh.plugin.json +22 -0
  151. package/package.json +118 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * 蒸馏触发决策纯函数(ADR-0003 / 规格 B 节;决策与执行分离,不依赖 dsh 宿主即可测
3
+ * 调度语义)。
4
+ *
5
+ * 语义(CONTEXT.md「渐进阈值」「会话切片」):
6
+ * - 生效阈值从 1 起步(新用户首轮即出记忆),每次成功抽取后翻倍至稳态值毕业;
7
+ * - 阈值按会话切片计数,只抽取达到阈值的那个会话的切片;
8
+ * - warmup 值 0 = 已毕业(使用稳态值),按档位桶各记一次(全局爬坡,非按会话)。
9
+ */
10
+ /** 生效阈值:爬坡中取 min(当前爬坡值, 稳态),毕业(0)取稳态。 */
11
+ export function effectiveExtractThreshold(warmup, steady) {
12
+ if (!Number.isFinite(warmup) || warmup <= 0)
13
+ return steady;
14
+ return Math.min(warmup, steady);
15
+ }
16
+ /** 成功抽取后推进爬坡:翻倍,达到稳态即毕业(0);已毕业保持 0。 */
17
+ export function advanceWarmupThreshold(current, steady) {
18
+ if (!Number.isFinite(current) || current <= 0)
19
+ return 0;
20
+ const next = current * 2;
21
+ return next >= steady ? 0 : next;
22
+ }
23
+ /**
24
+ * 档位切换动作表(ADR-0003):
25
+ * - 非 off 档间切换 → flush(该会话切片立即按捕获档位蒸馏,新档位从空切片起步)
26
+ * - 切到 off → park(切片挂起:用户刚说"停止记忆",把存量再蒸馏违背意图)
27
+ * - 从 off 切回 → unpark(挂起片按捕获档位落袋)
28
+ */
29
+ export function modeSwitchAction(oldMode, newMode) {
30
+ if (oldMode === newMode)
31
+ return 'none';
32
+ if (newMode === 'off')
33
+ return 'park';
34
+ if (oldMode === 'off')
35
+ return 'unpark';
36
+ return 'flush';
37
+ }
38
+ // ── 背景参考按会话现查(ADR-0003:抽取背景从 L0 按会话取,剔除切片自身) ──
39
+ /** 从"该会话最近消息"里剔除切片自身成员后取尾部 n 条作为抽取背景。
40
+ * 剔除防止抽取目标混入背景(L0 在 turn/end 已先落盘,现查必含切片)。 */
41
+ export function pickSessionBackground(recent, sliceIds, n) {
42
+ if (n <= 0)
43
+ return [];
44
+ return recent.filter((m) => !sliceIds.has(m.id)).slice(-n);
45
+ }
46
+ // ── 抽取失败退避(修"重试风暴":LLM 网关故障期间闲置兜底每 30s 入队一个任务,
47
+ // 在 120s LLM 超时的等待中堆积成连环调用)──
48
+ /** 退避基数与封顶:60s 起步指数翻倍,封顶 30 分钟(LLM 故障自愈的合理量级)。 */
49
+ const EXTRACT_BACKOFF_BASE_MS = 60_000;
50
+ const EXTRACT_BACKOFF_CAP_MS = 30 * 60_000;
51
+ /** 连续失败第 failStreak 次后的自动重试等待时长(1 → 60s,2 → 120s,…封顶 30min)。 */
52
+ export function extractionBackoffMs(failStreak) {
53
+ if (!Number.isFinite(failStreak) || failStreak <= 0)
54
+ return EXTRACT_BACKOFF_BASE_MS;
55
+ return Math.min(EXTRACT_BACKOFF_BASE_MS * 2 ** (failStreak - 1), EXTRACT_BACKOFF_CAP_MS);
56
+ }
57
+ /**
58
+ * 闲置扫描:静默达标且有切片的会话。off 档会话跳过(挂起语义);
59
+ * 活动时间优先取运行时记录,缺省回退切片内最晚消息时间。
60
+ */
61
+ export function idleSessionsToFlush(slices, lastActivity, now, idleMs, isOffSession) {
62
+ if (!(idleMs > 0))
63
+ return [];
64
+ const out = [];
65
+ for (const s of slices) {
66
+ if (s.count <= 0)
67
+ continue;
68
+ if (isOffSession(s.sessionId))
69
+ continue;
70
+ const activity = lastActivity.get(s.sessionId) ?? s.lastMessageAt;
71
+ if (now - activity >= idleMs)
72
+ out.push(s.sessionId);
73
+ }
74
+ return out;
75
+ }
@@ -0,0 +1,70 @@
1
+ import type { GraphEdge, GraphNode, GraphRecordStatus } from '../graph/types.js';
2
+ import type { MemoryRecord } from '../types.js';
3
+ /** 单条记忆在 prompt 中的字符上限(8 条/批 × 2000 字 ≈ 1.6 万字,远低于输入预算)。 */
4
+ export declare const GRAPH_RECORD_TEXT_LIMIT = 2000;
5
+ /** 上下文节点序列化时每节点携带的 active 事实条数上限。 */
6
+ export declare const GRAPH_PROMPT_FACTS_PER_NODE = 8;
7
+ /** 单条事实值在 prompt 中的字符上限。 */
8
+ export declare const GRAPH_PROMPT_FACT_CHARS = 200;
9
+ /** 投影任务无产出时的约定返回(写入 prompt 与测试)。 */
10
+ export declare const GRAPH_EMPTY_RESULT_HINT = "{\"reason\":\"\u672C\u6279\u6CA1\u6709\u503C\u5F97\u6C89\u6DC0\u7684\u5B9E\u4F53\u6216\u5173\u7CFB\",\"nodes\":[],\"edges\":[]}";
11
+ /** golden 示例:提示词展示的输出样例,同时是 apply 校验器的零丢弃回归样本。 */
12
+ export declare const GRAPH_PROJECTION_EXAMPLE: {
13
+ reason: string;
14
+ nodes: ({
15
+ ref: string;
16
+ name: string;
17
+ type: string;
18
+ aliases: string[];
19
+ tags: string[];
20
+ state: string;
21
+ facts: {
22
+ key: string;
23
+ value: string;
24
+ }[];
25
+ confidence: number;
26
+ sourceRecordIds: string[];
27
+ } | {
28
+ ref: string;
29
+ name: string;
30
+ type: string;
31
+ tags: string[];
32
+ state: string;
33
+ facts: {
34
+ key: string;
35
+ value: string[];
36
+ }[];
37
+ confidence: number;
38
+ sourceRecordIds: string[];
39
+ aliases?: undefined;
40
+ } | {
41
+ ref: string;
42
+ name: string;
43
+ type: string;
44
+ confidence: number;
45
+ sourceRecordIds: string[];
46
+ aliases?: undefined;
47
+ tags?: undefined;
48
+ state?: undefined;
49
+ facts?: undefined;
50
+ })[];
51
+ edges: {
52
+ fromRef: string;
53
+ toRef: string;
54
+ relation: string;
55
+ confidence: number;
56
+ sourceRecordIds: string[];
57
+ }[];
58
+ };
59
+ /** 系统提示(约束常量内插;类型/关系词/上限与校验器同源)。 */
60
+ export declare function getGraphProjectionSystemPrompt(): string;
61
+ /** 上下文节点序列化(仅 active facts,有界;superseded/disputed 历史不进 prompt)。 */
62
+ export declare function serializeNodeForPrompt(node: GraphNode): string;
63
+ /** 用户上下文装配(记录 + 已有节点 + 已有边;数量上限由调用方选择后传入)。 */
64
+ export declare function buildGraphProjectionPrompt(inputs: {
65
+ records: readonly MemoryRecord[];
66
+ nodes: readonly GraphNode[];
67
+ edges: readonly GraphEdge[];
68
+ }): string;
69
+ /** 生命周期词汇(节点状态中文标注,prompt/工具复用)。 */
70
+ export declare const GRAPH_STATUS_LABELS: Record<GraphRecordStatus, string>;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * 图谱投影 prompt(系统提示 + 用户上下文装配)。
3
+ *
4
+ * 净室边界:全文自写(C1)——只对齐"记录是唯一事实来源、关系用有向边表达、
5
+ * 来源只能引用本批"的语义,措辞/示例/上限从本项目自身约束推导。
6
+ * 字段上限与节点类型全部从 src/graph/constraints.ts 内插(防提示词/校验漂移:
7
+ * 常量改动时提示词自动跟随,测试另以"提示词含全部类型与上限数字"双钉固定)。
8
+ * 文末的 golden 示例(GRAPH_PROJECTION_EXAMPLE)同时是 G1 apply 的校验样本——
9
+ * 提示词示例与校验器共用同一条真值,示例漂移会直接挂测试。
10
+ */
11
+ import { GRAPH_CAP, GRAPH_NODE_TYPES, GRAPH_RELATION_WORDS } from '../graph/constraints.js';
12
+ /** 单条记忆在 prompt 中的字符上限(8 条/批 × 2000 字 ≈ 1.6 万字,远低于输入预算)。 */
13
+ export const GRAPH_RECORD_TEXT_LIMIT = 2000;
14
+ /** 上下文节点序列化时每节点携带的 active 事实条数上限。 */
15
+ export const GRAPH_PROMPT_FACTS_PER_NODE = 8;
16
+ /** 单条事实值在 prompt 中的字符上限。 */
17
+ export const GRAPH_PROMPT_FACT_CHARS = 200;
18
+ /** 投影任务无产出时的约定返回(写入 prompt 与测试)。 */
19
+ export const GRAPH_EMPTY_RESULT_HINT = '{"reason":"本批没有值得沉淀的实体或关系","nodes":[],"edges":[]}';
20
+ /** golden 示例:提示词展示的输出样例,同时是 apply 校验器的零丢弃回归样本。 */
21
+ export const GRAPH_PROJECTION_EXAMPLE = {
22
+ reason: '本批记录提到张三参与 GraphX 项目并使用 VSCode,沉淀实体与协作关系',
23
+ nodes: [
24
+ {
25
+ ref: 'n1',
26
+ name: '张三',
27
+ type: 'person',
28
+ aliases: ['老张'],
29
+ tags: ['前端负责人'],
30
+ state: '负责 GraphX 的前端模块',
31
+ facts: [{ key: '职位', value: '前端负责人' }],
32
+ confidence: 0.9,
33
+ sourceRecordIds: ['rec-1'],
34
+ },
35
+ {
36
+ ref: 'n2',
37
+ name: 'GraphX',
38
+ type: 'project',
39
+ tags: ['可视化'],
40
+ state: '正在开发图谱可视化模块',
41
+ facts: [{ key: '里程碑', value: ['完成 Schema 设计', '联调中'] }],
42
+ confidence: 0.85,
43
+ sourceRecordIds: ['rec-1', 'rec-2'],
44
+ },
45
+ {
46
+ ref: 'n3',
47
+ name: 'VSCode',
48
+ type: 'tool',
49
+ confidence: 0.8,
50
+ sourceRecordIds: ['rec-2'],
51
+ },
52
+ ],
53
+ edges: [
54
+ { fromRef: 'n1', toRef: 'n2', relation: '参与', confidence: 0.9, sourceRecordIds: ['rec-1'] },
55
+ { fromRef: 'n1', toRef: 'n3', relation: '使用', confidence: 0.8, sourceRecordIds: ['rec-2'] },
56
+ ],
57
+ };
58
+ /** 系统提示(约束常量内插;类型/关系词/上限与校验器同源)。 */
59
+ export function getGraphProjectionSystemPrompt() {
60
+ return `你是记忆系统的知识图谱投影器。输入是一批 L1 记忆记录(唯一事实来源)与图谱中已有的实体节点,你的任务是从记录中识别值得长期沉淀的实体与它们之间的有向关系,输出节点与边的提案。
61
+
62
+ ## 铁律
63
+
64
+ - **记录是唯一事实来源**:提案中的每个结论都必须来自本批提供的记忆记录,禁止使用任何常识推断或输入之外的知识;无法对应到记录 id 的提案会被系统直接丢弃。
65
+ - **来源引用**:每个节点、每条 fact、每条边的 sourceRecordIds 只能填本批输入中出现的记录 id。
66
+ - **关系必须用有向边表达**(fromRef/toRef),禁止把"A 属于 B"这类关系写成 fact 字符串——fact 只描述实体自身属性。
67
+ - **别名合并**:同一实体的拼写变体(全角/半角、大小写、空格连字符差异)必须合并到同一节点,新变体放进 aliases,不要创建重复节点。
68
+ - 子串相似不是同一实体:只有归一后名称一致且类型相同才视为同一实体。
69
+
70
+ ## 节点类型(只允许这五类)
71
+
72
+ ${GRAPH_NODE_TYPES.join(' | ')}
73
+
74
+ - tags 是语义角色标签(如"前端负责人"、"可视化"),每节点 1~6 个,不能替代稳定的 type。
75
+ - 单行状态(state)描述实体"现在是什么状态";facts 是结构化属性(key/value)。
76
+ - 日期字段(validFrom/validTo)只在记录中有明确时间证据时填写,格式 ISO,不猜测。
77
+
78
+ ## 关系词(用规范中文)
79
+
80
+ ${GRAPH_RELATION_WORDS.join('、')}(也可用其他贴切的中文动词短语,≤${GRAPH_CAP.relation} 字)
81
+
82
+ ## 字段上限
83
+
84
+ - name ≤${GRAPH_CAP.name} 字;aliases ≤${GRAPH_CAP.aliases} 条;tags ≤${GRAPH_CAP.tags} 个
85
+ - state ≤${GRAPH_CAP.state} 字;fact 的 key ≤${GRAPH_CAP.factKey} 字、value ≤${GRAPH_CAP.factValue} 字(数组值 ≤${GRAPH_CAP.factValueItems} 个元素)
86
+ - 单提案 sourceRecordIds ≤${GRAPH_CAP.proposalSources} 个
87
+
88
+ ## 输出格式
89
+
90
+ 严格输出一个 JSON 对象,不输出任何其他内容(包括解释或代码围栏):
91
+
92
+ {
93
+ "reason": "一句话说明本批沉淀了什么(或为什么为空)",
94
+ "nodes": [
95
+ {
96
+ "ref": "提案内临时引用(如 n1),边的 fromRef/toRef 用它连线",
97
+ "name": "实体名",
98
+ "type": "person|project|organization|tool|place",
99
+ "aliases": ["拼写变体"],
100
+ "tags": ["语义角色标签"],
101
+ "state": "单行当前状态(可选)",
102
+ "facts": [{ "key": "属性名", "value": "属性值或字符串数组" }],
103
+ "validFrom": "ISO 日期(可选,有证据才填)",
104
+ "confidence": 0.9,
105
+ "sourceRecordIds": ["本批记录 id"]
106
+ }
107
+ ],
108
+ "edges": [
109
+ { "fromRef": "n1", "toRef": "n2", "relation": "参与", "confidence": 0.9, "sourceRecordIds": ["本批记录 id"] }
110
+ ]
111
+ }
112
+
113
+ 没有值得沉淀的实体或关系时输出:${GRAPH_EMPTY_RESULT_HINT}`;
114
+ }
115
+ /** 上下文节点序列化(仅 active facts,有界;superseded/disputed 历史不进 prompt)。 */
116
+ export function serializeNodeForPrompt(node) {
117
+ const parts = [
118
+ `- id=${node.id} | ${node.name} | 类型 ${node.type}`,
119
+ ];
120
+ if (node.aliases.length > 0)
121
+ parts.push(` 别名: ${node.aliases.join('、')}`);
122
+ if (node.tags?.length)
123
+ parts.push(` 标签: ${node.tags.join('、')}`);
124
+ if (node.currentState)
125
+ parts.push(` 状态: ${node.currentState.replaceAll('\n', ' / ')}`);
126
+ const activeFacts = node.facts.filter((f) => f.status === 'active').slice(0, GRAPH_PROMPT_FACTS_PER_NODE);
127
+ for (const f of activeFacts) {
128
+ const value = Array.isArray(f.value) ? f.value.join('、') : f.value;
129
+ parts.push(` 事实 ${f.key}: ${value.slice(0, GRAPH_PROMPT_FACT_CHARS)}`);
130
+ }
131
+ return parts.join('\n');
132
+ }
133
+ function recordLine(r) {
134
+ const meta = r.metadata;
135
+ const times = [];
136
+ if (meta?.activity_start_time)
137
+ times.push(`开始 ${String(meta.activity_start_time)}`);
138
+ if (meta?.activity_end_time)
139
+ times.push(`结束 ${String(meta.activity_end_time)}`);
140
+ return [
141
+ `- id=${r.id} | 类型 ${r.type}${r.family ? ` | 族 ${r.family}` : ''} | 记录时间 ${new Date(r.createdAt).toISOString()}`,
142
+ ` 内容: ${r.content.slice(0, GRAPH_RECORD_TEXT_LIMIT)}`,
143
+ ...(times.length > 0 ? [` 活动时间: ${times.join(', ')}`] : []),
144
+ ].join('\n');
145
+ }
146
+ function edgeLine(edges, nameOf) {
147
+ return edges.map((e) => `- ${nameOf(e.fromNodeId)} --[${e.relation}]--> ${nameOf(e.toNodeId)}`);
148
+ }
149
+ /** 用户上下文装配(记录 + 已有节点 + 已有边;数量上限由调用方选择后传入)。 */
150
+ export function buildGraphProjectionPrompt(inputs) {
151
+ const nameOf = new Map(inputs.nodes.map((n) => [n.id, n.name]));
152
+ const nameFor = (id) => nameOf.get(id) ?? '(未知节点)';
153
+ const sections = [];
154
+ sections.push(`## 本批记忆记录(唯一事实来源;sourceRecordIds 只能引用这些 id)\n\n${inputs.records.map(recordLine).join('\n') || '(空)'}`);
155
+ sections.push(`## 图谱已有节点(合并实体时对照;别名变体并入这些节点而不是新建)\n\n${inputs.nodes.map(serializeNodeForPrompt).join('\n') || '(图谱当前为空)'}`);
156
+ const lines = edgeLine(inputs.edges, nameFor);
157
+ sections.push(`## 图谱已有边(active)\n\n${lines.join('\n') || '(无)'}`);
158
+ sections.push('## 任务\n\n从本批记录中识别实体(五类节点)与有向关系,输出提案 JSON。注意:\n- 已有节点在本批出现新信息时,提案同一实体(名称归一一致 + 类型相同),系统会合并来源与别名;\n- 状态/事实随时间变化的,直接给新值,系统会自动关闭旧值的有效区间;\n- 同一关系指向新目标时系统自动 supersede 旧边,照常输出新边即可。');
159
+ return sections.join('\n\n');
160
+ }
161
+ /** 生命周期词汇(节点状态中文标注,prompt/工具复用)。 */
162
+ export const GRAPH_STATUS_LABELS = {
163
+ active: '生效中',
164
+ superseded: '已被更新',
165
+ disputed: '有争议',
166
+ archived: '已归档',
167
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 去重 prompt(批量冲突检测:store/update/merge/skip 决策词表)。
3
+ *
4
+ * 净室重写说明:本文件的 prompt 文案按重写规格(Phase 2 决策)逐字沿用——
5
+ * prompt 内容直接决定蒸馏质量,是已发布行为的一部分,不属于可自由重写文本;
6
+ * 代码结构与注释随实现重写。
7
+ */
8
+ import type { ExtractedMemory, ExtractMode, MemoryRecord } from '../types.js';
9
+ export declare const CONFLICT_DETECTION_SYSTEM_PROMPT = "\u4F60\u662F\u8BB0\u5FC6\u51B2\u7A81\u68C0\u6D4B\u5668\u3002\u6279\u91CF\u6BD4\u8F83\u591A\u6761\u3010\u65B0\u8BB0\u5FC6\u3011\u4E0E\u3010\u7EDF\u4E00\u5019\u9009\u8BB0\u5FC6\u6C60\u3011\u4E2D\u7684\u5DF2\u6709\u8BB0\u5FC6\uFF0C\u9010\u6761\u51B3\u5B9A\u5982\u4F55\u5904\u7406\u3002\n\n**\u8F93\u51FA\u8BED\u8A00**\uFF1A`merged_content` \u4F7F\u7528\u4E0E\u5019\u9009\u6C60\u4E2D\u5DF2\u6709\u8BB0\u5FC6\u76F8\u540C\u7684\u8BED\u8A00\uFF1BJSON \u5B57\u6BB5\u540D\u3001\u679A\u4E3E\u503C\u3001record_id\u3001ISO \u65F6\u95F4\u6233\u4FDD\u6301\u82F1\u6587\u3002\n\n## \u6838\u5FC3\u89C4\u5219\n\n- **\u8DE8 type \u5408\u5E76**\uFF1A\u4E0D\u540C type\uFF08persona / episodic / instruction / work_fact / work_task / work_method / work_artifact\uFF09\u7684\u8BB0\u5FC6\u5982\u679C\u8BED\u4E49\u4E0A\u63CF\u8FF0\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6\uFF0C**\u53EF\u4EE5\u5408\u5E76**\u3002\n- **\u591A\u5BF9\u591A\u5408\u5E76**\uFF1A\u4E00\u6761\u65B0\u8BB0\u5FC6\u53EF\u4EE5\u540C\u65F6\u66FF\u6362/\u5408\u5E76\u5019\u9009\u6C60\u4E2D\u7684**\u591A\u6761**\u5DF2\u6709\u8BB0\u5FC6\uFF08\u901A\u8FC7 target_ids \u6570\u7EC4\u6307\u5B9A\uFF09\u3002\n- \u5408\u5E76\u540E\u4F60\u5FC5\u987B\u5224\u65AD\u65B0\u8BB0\u5FC6\u7684\u6700\u4F73 type\uFF08merged_type\uFF09\u3002\n\n## \u5224\u65AD\u903B\u8F91\n\n1. **\u5206\u8FA8\u8BB0\u5FC6\u6027\u8D28**\uFF1A\n - **\u72B6\u6001\u7C7B**\uFF08persona/instruction\uFF09\uFF1A\u504F\u597D\u3001\u7279\u8D28\u3001\u957F\u671F\u8BBE\u5B9A\u3001\u76F8\u5BF9\u7A33\u5B9A\u7684\u4E8B\u5B9E\u3001\u884C\u4E3A\u89C4\u5219\n - **\u4E8B\u4EF6\u7C7B**\uFF08episodic\uFF09\uFF1A\u4E00\u6B21\u6027\u7ECF\u5386\u3001\u5E26\u65F6\u95F4\u70B9\u7684\u5BA2\u89C2\u8BB0\u5F55\uFF0C\u5EFA\u8BAE\u5408\u5E76\u540C\u4E00\u4EF6\u4E8B\u7684\u524D\u56E0\u540E\u679C\n\n2. **\u5224\u65AD\u662F\u5426\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6**\uFF1A\u4E3B\u4F53\u76F8\u540C\u3001\u4E3B\u9898\u4E00\u81F4\u3001\u65F6\u95F4\u63A5\u8FD1\u3001scene_name \u76F8\u4F3C\n\n3. **\u9009\u62E9\u52A8\u4F5C**\uFF1A\n - \"store\"\uFF1A\u89C6\u4E3A\u65B0\u4FE1\u606F\uFF0C\u65B0\u589E\u5F53\u524D\u8BB0\u5FC6\u3002\n - \"skip\"\uFF1A\u5DF2\u6709\u8BB0\u5FC6\u66F4\u597D\uFF0C\u65B0\u8BB0\u5FC6\u65E0\u589E\u91CF\u6216\u66F4\u6A21\u7CCA\uFF0C\u5FFD\u7565\u5F53\u524D\u8BB0\u5FC6\u3002\n - \"update\"\uFF1A\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6\uFF0C\u65B0\u8BB0\u5FC6\u5728\u5185\u5BB9\u6216\u65F6\u95F4\u4E0A\u66F4\u4F18\uFF08\u66F4\u5177\u4F53\u3001\u66F4\u665A\u6216\u7EA0\u9519\uFF09\uFF0C\u4EE5\u65B0\u8BB0\u5FC6\u4E3A\u4E3B\u8986\u76D6\u65E7\u8BB0\u5FC6\uFF0C\u53EF\u4FDD\u7559\u65E7\u8BB0\u5FC6\u4E2D\u4ECD\u6B63\u786E\u7684\u7EC6\u8282\u3002\n - \"merge\"\uFF1A\u540C\u4E00\u4E8B\u5B9E\u6216\u540C\u4E00\u6F14\u5316\u8FC7\u7A0B\uFF0C\u591A\u6761\u8BB0\u5FC6\u4FE1\u606F\u4E92\u8865\u4E14\u4E0D\u77DB\u76FE\uFF0C\u5408\u5E76\u6210\u4E00\u6761\u66F4\u5B8C\u6574\u8BB0\u5FC6\uFF0C\u4FE1\u606F\u5C3D\u91CF\u4E0D\u5197\u4F59\u3002\n\n4. **\u7B56\u7565\u503E\u5411**\uFF1A\n - \u72B6\u6001\u7C7B\uFF1A\u591A\u6761\u63CF\u8FF0\u540C\u4E00\u504F\u597D/\u7279\u8D28 \u2192 \u503E\u5411 merge\uFF1B\u65E0\u589E\u91CF \u2192 skip\uFF1B\u660E\u786E\u66F4\u65B0 \u2192 update\n - \u4E8B\u4EF6\u7C7B\uFF1A\u540C\u4E00\u4E8B\u4EF6\u7684\u524D\u56E0\u540E\u679C\u3001\u4E0D\u540C\u9636\u6BB5 \u2192 \u503E\u5411 merge \u4E3A\u4E00\u6761\u5B8C\u6574\u53D9\u8FF0\uFF1B\u5B8C\u5168\u76F8\u540C \u2192 skip\n - \u8DE8\u7C7B\u578B\u793A\u4F8B\uFF1A\u4E00\u6761 episodic \"\u7528\u6237\u5728 2018 \u5E74\u5F00\u59CB\u505A\u64AD\u5BA2\" + \u4E00\u6761 persona \"\u7528\u6237\u6709\u64AD\u5BA2\u5236\u4F5C\u7ECF\u9A8C\" \u2192 \u53EF merge \u4E3A\u4E00\u6761 persona \u6216 episodic\uFF08\u53D6\u51B3\u4E8E\u4FE1\u606F\u4FA7\u91CD\uFF09\n\n5. **timestamp \u5904\u7406**\uFF1A\n - merge / update \u65F6\uFF0Cmerged_timestamps \u5E94\u5305\u542B**\u6240\u6709\u76F8\u5173\u8BB0\u5FC6\u7684\u65F6\u95F4\u6233\u5E76\u96C6**\uFF08\u53BB\u91CD\u6392\u5E8F\uFF09\n - \u8FD9\u6837\u53EF\u4EE5\u4FDD\u7559\u4E8B\u4EF6\u53D1\u751F\u7684\u5B8C\u6574\u65F6\u95F4\u7EBF\n\n## \u8F93\u51FA\u683C\u5F0F\n\n\u4E25\u683C\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u6BCF\u4E2A\u5143\u7D20\u5BF9\u5E94\u4E00\u6761\u65B0\u8BB0\u5FC6\u7684\u51B3\u7B56\u3002\u4E0D\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u5185\u5BB9\uFF1A\n\n[\n {\n \"record_id\": \"\u65B0\u8BB0\u5FC6\u7684 record_id\",\n \"action\": \"store|update|skip|merge\",\n \"target_ids\": [\"\u8981\u5220\u9664\u7684\u5019\u9009\u8BB0\u5FC6 record_id 1\", \"record_id 2\"],\n \"merged_content\": \"\u5408\u5E76/\u66F4\u65B0\u540E\u7684\u8BB0\u5FC6\u5185\u5BB9\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\",\n \"merged_type\": \"\u5408\u5E76\u540E\u7684\u6700\u4F73 type\uFF1Apersona|episodic|instruction|work_fact|work_task|work_method|work_artifact\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\",\n \"merged_priority\": 85,\n \"merged_timestamps\": [\"\u5408\u5E76\u540E\u7684\u65F6\u95F4\u6233\u6570\u7EC4\uFF0C\u5305\u542B\u6240\u6709\u65B0\u65E7\u8BB0\u5FC6\u65F6\u95F4\u6233\u7684\u5E76\u96C6\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\"]\n }\n]\n\n\u5B57\u6BB5\u8BF4\u660E\uFF1A\n- target_ids\uFF1A\u8981\u5220\u9664\u66FF\u6362\u7684\u65E7\u8BB0\u5FC6 ID **\u6570\u7EC4**\uFF08\u53EF\u4EE5 1 \u6761\u6216\u591A\u6761\uFF09\u3002store/skip \u65F6\u7701\u7565\u6216\u4E3A\u7A7A\u3002\n- merged_content\uFF1Amerge/update \u65F6\u7684\u6700\u7EC8\u8BB0\u5FC6\u6587\u672C\u3002store/skip \u65F6\u7701\u7565\u3002\n- merged_type\uFF1Amerge/update \u540E\u8BB0\u5FC6\u5E94\u5F52\u5C5E\u7684 type\u3002\u6839\u636E\u5408\u5E76\u540E\u5185\u5BB9\u672C\u8D28\u5224\u65AD\u3002\n- merged_priority\uFF1Amerge/update \u540E\u7684\u65B0\u4F18\u5148\u7EA7\uFF080-100 \u6574\u6570\uFF0Cmerge/update \u65F6\u5FC5\u586B\uFF09\u3002\u5408\u5E76\u540E\u4FE1\u606F\u66F4\u5B8C\u6574\u3001\u66F4\u786E\u5B9A\uFF0C\u901A\u5E38\u5E94**\u914C\u60C5\u63D0\u5347** priority\uFF08\u4F8B\u5982\u4E24\u6761 priority 70 \u7684\u8BB0\u5FC6\u5408\u5E76\u540E\u53EF\u63D0\u5347\u5230 80\uFF09\u3002\u53C2\u8003\u6807\u51C6\uFF1A80-100\uFF08\u6838\u5FC3\u7279\u8D28/\u91CD\u8981\u4E8B\u4EF6\uFF09\uFF0C60-79\uFF08\u4E00\u822C\u504F\u597D/\u666E\u901A\u6D3B\u52A8\uFF09\uFF0C<60\uFF08\u6B21\u8981\u4FE1\u606F\uFF09\u3002\n- merged_timestamps\uFF1A\u5408\u5E76\u540E\u7684\u65F6\u95F4\u6233\u6570\u7EC4\u3002\u6536\u96C6\u65B0\u8BB0\u5FC6 + \u6240\u6709\u88AB\u5408\u5E76\u65E7\u8BB0\u5FC6\u7684\u65F6\u95F4\u6233\uFF0C\u53BB\u91CD\u6392\u5E8F\u3002";
10
+ export declare const WORK_CONFLICT_DETECTION_SYSTEM_PROMPT = "\u4F60\u662F\u56E2\u961F\u5DE5\u4F5C\u8BB0\u5FC6\u51B2\u7A81\u68C0\u6D4B\u5668\u3002\u6279\u91CF\u6BD4\u8F83\u591A\u6761\u3010\u65B0\u8BB0\u5FC6\u3011\u4E0E\u3010\u7EDF\u4E00\u5019\u9009\u8BB0\u5FC6\u6C60\u3011\u4E2D\u7684\u5DF2\u6709\u8BB0\u5FC6\uFF0C\u9010\u6761\u51B3\u5B9A\u5982\u4F55\u5904\u7406\u3002\n\n**\u8F93\u51FA\u8BED\u8A00**\uFF1A`merged_content` \u4F7F\u7528\u4E0E\u5019\u9009\u6C60\u4E2D\u5DF2\u6709\u8BB0\u5FC6\u76F8\u540C\u7684\u8BED\u8A00\uFF1BJSON \u5B57\u6BB5\u540D\u3001\u679A\u4E3E\u503C\u3001record_id\u3001ISO \u65F6\u95F4\u6233\u4FDD\u6301\u82F1\u6587\u3002\n\n## \u6838\u5FC3\u89C4\u5219\n\n- **\u8DE8 type \u5408\u5E76**\uFF1A\u4E0D\u540C type\uFF08work_fact / work_task / work_method / work_artifact\uFF09\u7684\u8BB0\u5FC6\u5982\u679C\u8BED\u4E49\u4E0A\u63CF\u8FF0\u540C\u4E00\u5DE5\u4F5C\u5BF9\u8C61\u3001\u4EFB\u52A1\u3001\u65B9\u6CD5\u6216\u8D44\u4EA7\uFF0C**\u53EF\u4EE5\u5408\u5E76**\u3002\n- **\u591A\u5BF9\u591A\u5408\u5E76**\uFF1A\u4E00\u6761\u65B0\u8BB0\u5FC6\u53EF\u4EE5\u540C\u65F6\u66FF\u6362/\u5408\u5E76\u5019\u9009\u6C60\u4E2D\u7684**\u591A\u6761**\u5DF2\u6709\u8BB0\u5FC6\uFF08\u901A\u8FC7 target_ids \u6570\u7EC4\u6307\u5B9A\uFF09\u3002\n- \u5408\u5E76\u540E\u4F60\u5FC5\u987B\u5224\u65AD\u65B0\u8BB0\u5FC6\u7684\u6700\u4F73 type\uFF08merged_type\uFF09\u3002\n- \u8BB0\u5FC6\u9ED8\u8BA4\u4F1A\u5728\u9879\u76EE\u56E2\u961F\u5185\u5171\u4EAB\uFF0C\u5408\u5E76\u5185\u5BB9\u5E94\u53EA\u4FDD\u7559\u5DE5\u4F5C\u76F8\u5173\u4FE1\u606F\u3002\n\n## \u5224\u65AD\u903B\u8F91\n\n1. **\u5206\u8FA8\u8BB0\u5FC6\u6027\u8D28**\uFF1A\n - **\u5DE5\u4F5C\u4E8B\u5B9E\u7C7B\uFF08work_fact\uFF09**\uFF1A\u9879\u76EE\u4E8B\u5B9E\u3001\u9700\u6C42\u3001\u51B3\u7B56\u3001\u72B6\u6001\u3001\u98CE\u9669\u3001\u7EA6\u675F\u3001\u5B9E\u9A8C\u7ED3\u679C\u3001\u5BA2\u6237\u53CD\u9988\u3002\n - **\u5DE5\u4F5C\u4EFB\u52A1\u7C7B\uFF08work_task\uFF09**\uFF1A\u5F85\u529E\u3001owner\u3001deadline\u3001\u4E0B\u4E00\u6B65\u8BA1\u5212\u3001\u4EFB\u52A1\u72B6\u6001\u53D8\u5316\u3002\n - **\u5DE5\u4F5C\u65B9\u6CD5\u7C7B\uFF08work_method\uFF09**\uFF1ASOP\u3001\u7981\u5FCC\u3001\u539F\u5219\u3001\u7ECF\u9A8C\u3001\u8BBE\u8BA1\u601D\u8DEF\u3001\u5224\u65AD\u6807\u51C6\u3001Agent \u884C\u4E3A\u89C4\u5219\u3002\n - **\u5DE5\u4F5C\u8D44\u4EA7\u7C7B\uFF08work_artifact\uFF09**\uFF1A\u6587\u6863\u3001PR\u3001Issue\u3001Prompt\u3001\u62A5\u544A\u3001\u4EE3\u7801\u5206\u652F\u3001\u8BBE\u8BA1\u7A3F\u3001\u94FE\u63A5\u7B49\u3002\n\n2. **\u5224\u65AD\u662F\u5426\u540C\u4E00\u5DE5\u4F5C\u5BF9\u8C61/\u6F14\u5316\u8FC7\u7A0B**\uFF1A\n - \u540C\u4E00\u9879\u76EE\u3001\u6A21\u5757\u3001\u9700\u6C42\u3001\u4EFB\u52A1\u3001\u98CE\u9669\u3001\u51B3\u7B56\u3001\u65B9\u6CD5\u3001\u8D44\u4EA7\uFF0C\u4E14 scene_name \u6216\u8BED\u4E49\u9AD8\u5EA6\u76F8\u4F3C\u3002\n - \u540C\u4E00\u4EFB\u52A1\u7684\u4E0D\u540C\u9636\u6BB5\u3001\u540C\u4E00\u65B9\u6CD5\u7684\u8865\u5145\u3001\u540C\u4E00\u8D44\u4EA7\u7684\u7248\u672C\u6216\u7528\u9014\u53D8\u5316\uFF0C\u901A\u5E38\u53EF\u4EE5\u5408\u5E76\u3002\n - \u4EC5\u5C5E\u4E8E\u540C\u4E00\u5927\u9879\u76EE\u4F46\u8BA8\u8BBA\u5BF9\u8C61\u4E0D\u540C\uFF0C\u4E0D\u5E94\u5F3A\u884C\u5408\u5E76\u3002\n\n3. **\u9009\u62E9\u52A8\u4F5C**\uFF1A\n - \"store\"\uFF1A\u89C6\u4E3A\u65B0\u4FE1\u606F\uFF0C\u65B0\u589E\u5F53\u524D\u8BB0\u5FC6\u3002\n - \"skip\"\uFF1A\u5DF2\u6709\u8BB0\u5FC6\u66F4\u597D\uFF0C\u65B0\u8BB0\u5FC6\u65E0\u589E\u91CF\u6216\u66F4\u6A21\u7CCA\uFF0C\u5FFD\u7565\u5F53\u524D\u8BB0\u5FC6\u3002\n - \"update\"\uFF1A\u540C\u4E00\u5DE5\u4F5C\u5BF9\u8C61\uFF0C\u65B0\u8BB0\u5FC6\u66F4\u5177\u4F53\u3001\u66F4\u65B0\u3001\u66F4\u6743\u5A01\u6216\u7EA0\u6B63\u65E7\u4FE1\u606F\uFF0C\u4EE5\u65B0\u8BB0\u5FC6\u4E3A\u4E3B\u8986\u76D6\u65E7\u8BB0\u5FC6\uFF0C\u53EF\u4FDD\u7559\u65E7\u8BB0\u5FC6\u4E2D\u4ECD\u6B63\u786E\u7684\u7EC6\u8282\u3002\n - \"merge\"\uFF1A\u540C\u4E00\u5DE5\u4F5C\u5BF9\u8C61\u6216\u540C\u4E00\u6F14\u5316\u8FC7\u7A0B\uFF0C\u65B0\u65E7\u8BB0\u5FC6\u4E92\u8865\u4E14\u4E0D\u77DB\u76FE\uFF0C\u5408\u5E76\u6210\u4E00\u6761\u66F4\u5B8C\u6574\u8BB0\u5FC6\uFF0C\u4FE1\u606F\u5C3D\u91CF\u4E0D\u5197\u4F59\u3002\n\n4. **\u7B56\u7565\u503E\u5411**\uFF1A\n - work_fact\uFF1A\u540C\u4E00\u4E8B\u5B9E/\u51B3\u7B56/\u72B6\u6001\u7684\u8865\u5145\u6216\u4FEE\u6B63 \u2192 \u503E\u5411 update \u6216 merge\u3002\n - work_task\uFF1A\u540C\u4E00\u4EFB\u52A1\u7684 owner\u3001deadline\u3001\u72B6\u6001\u53D8\u5316 \u2192 \u503E\u5411 update\uFF1B\u8865\u5145\u4F9D\u8D56\u6216\u9A8C\u6536\u6807\u51C6 \u2192 \u503E\u5411 merge\u3002\n - work_method\uFF1A\u540C\u4E00 SOP\u3001\u7981\u5FCC\u3001\u539F\u5219\u3001\u7ECF\u9A8C\u7684\u8865\u5145 \u2192 \u503E\u5411 merge\uFF1B\u66F4\u6E05\u6670\u901A\u7528\u7684\u8868\u8FF0 \u2192 \u503E\u5411 update\u3002\n - work_artifact\uFF1A\u540C\u4E00\u6587\u6863\u3001PR\u3001Prompt\u3001\u62A5\u544A\u7B49\u8D44\u4EA7\u7684\u7528\u9014\u3001\u7248\u672C\u3001\u94FE\u63A5\u8865\u5145 \u2192 \u503E\u5411 merge \u6216 update\u3002\n - \u8DE8\u7C7B\u578B\u793A\u4F8B\uFF1A\u4E00\u6761 work_fact \"\u56E2\u961F\u51B3\u5B9A L1 type \u4FDD\u6301\u5C11\u91CF\u9AD8\u5C42\u5206\u7C7B\" + \u4E00\u6761 work_method \"L1 type \u4E0D\u5B9C\u8FC7\u7EC6\uFF0C\u5426\u5219\u5F71\u54CD L2/L3 \u805A\u5408\" \u2192 \u53EF merge \u4E3A work_method\u3002\n\n5. **timestamp \u5904\u7406**\uFF1A\n - merge / update \u65F6\uFF0Cmerged_timestamps \u5E94\u5305\u542B**\u6240\u6709\u76F8\u5173\u8BB0\u5FC6\u7684\u65F6\u95F4\u6233\u5E76\u96C6**\uFF08\u53BB\u91CD\u6392\u5E8F\uFF09\u3002\n - \u8FD9\u6837\u53EF\u4EE5\u4FDD\u7559\u5DE5\u4F5C\u4E8B\u5B9E\u3001\u4EFB\u52A1\u6216\u65B9\u6CD5\u6F14\u5316\u7684\u5B8C\u6574\u65F6\u95F4\u7EBF\u3002\n\n## \u8F93\u51FA\u683C\u5F0F\n\n\u4E25\u683C\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u6BCF\u4E2A\u5143\u7D20\u5BF9\u5E94\u4E00\u6761\u65B0\u8BB0\u5FC6\u7684\u51B3\u7B56\u3002\u4E0D\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u5185\u5BB9\uFF1A\n\n[\n {\n \"record_id\": \"\u65B0\u8BB0\u5FC6\u7684 record_id\",\n \"action\": \"store|update|skip|merge\",\n \"target_ids\": [\"\u8981\u5220\u9664\u7684\u5019\u9009\u8BB0\u5FC6 record_id 1\", \"record_id 2\"],\n \"merged_content\": \"\u5408\u5E76/\u66F4\u65B0\u540E\u7684\u8BB0\u5FC6\u5185\u5BB9\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\",\n \"merged_type\": \"\u5408\u5E76\u540E\u7684\u6700\u4F73 type\uFF1Awork_fact|work_task|work_method|work_artifact\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\",\n \"merged_priority\": 85,\n \"merged_timestamps\": [\"\u5408\u5E76\u540E\u7684\u65F6\u95F4\u6233\u6570\u7EC4\uFF0C\u5305\u542B\u6240\u6709\u65B0\u65E7\u8BB0\u5FC6\u65F6\u95F4\u6233\u7684\u5E76\u96C6\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\"]\n }\n]\n\n\u5B57\u6BB5\u8BF4\u660E\uFF1A\n- target_ids\uFF1A\u8981\u5220\u9664\u66FF\u6362\u7684\u65E7\u8BB0\u5FC6 ID **\u6570\u7EC4**\uFF08\u53EF\u4EE5 1 \u6761\u6216\u591A\u6761\uFF09\u3002store/skip \u65F6\u7701\u7565\u6216\u4E3A\u7A7A\u3002\n- merged_content\uFF1Amerge/update \u65F6\u7684\u6700\u7EC8\u8BB0\u5FC6\u6587\u672C\u3002store/skip \u65F6\u7701\u7565\u3002\n- merged_type\uFF1Amerge/update \u540E\u8BB0\u5FC6\u5E94\u5F52\u5C5E\u7684 type\u3002\u6839\u636E\u5408\u5E76\u540E\u5185\u5BB9\u672C\u8D28\u5224\u65AD\u3002\n- merged_priority\uFF1Amerge/update \u540E\u7684\u65B0\u4F18\u5148\u7EA7\uFF080-100 \u6574\u6570\uFF0Cmerge/update \u65F6\u5FC5\u586B\uFF09\u3002\u5408\u5E76\u540E\u4FE1\u606F\u66F4\u5B8C\u6574\u3001\u66F4\u786E\u5B9A\uFF0C\u901A\u5E38\u5E94**\u914C\u60C5\u63D0\u5347** priority\u3002\u53C2\u8003\u6807\u51C6\uFF1A80-100\uFF08\u5173\u952E\u4E8B\u5B9E/\u91CD\u8981\u4EFB\u52A1/\u6838\u5FC3\u65B9\u6CD5/\u91CD\u8981\u8D44\u4EA7\uFF09\uFF0C60-79\uFF08\u4E00\u822C\u5DE5\u4F5C\u4FE1\u606F\uFF09\uFF0C<60\uFF08\u6B21\u8981\u4FE1\u606F\uFF09\u3002\n- merged_timestamps\uFF1A\u5408\u5E76\u540E\u7684\u65F6\u95F4\u6233\u6570\u7EC4\u3002\u6536\u96C6\u65B0\u8BB0\u5FC6 + \u6240\u6709\u88AB\u5408\u5E76\u65E7\u8BB0\u5FC6\u7684\u65F6\u95F4\u6233\uFF0C\u53BB\u91CD\u6392\u5E8F\u3002";
11
+ export declare const ALL_CONFLICT_DETECTION_SYSTEM_PROMPT = "\u4F60\u662F\u8BB0\u5FC6\u51B2\u7A81\u68C0\u6D4B\u5668\u3002\u6279\u91CF\u6BD4\u8F83\u591A\u6761\u3010\u65B0\u8BB0\u5FC6\u3011\u4E0E\u3010\u7EDF\u4E00\u5019\u9009\u8BB0\u5FC6\u6C60\u3011\u4E2D\u7684\u5DF2\u6709\u8BB0\u5FC6\uFF0C\u9010\u6761\u51B3\u5B9A\u5982\u4F55\u5904\u7406\u3002\u5019\u9009\u6C60\u540C\u65F6\u5305\u542B\u4E2A\u4EBA\u8BB0\u5FC6\uFF08persona/episodic/instruction\uFF09\u4E0E\u56E2\u961F\u5DE5\u4F5C\u8BB0\u5FC6\uFF08work_fact/work_task/work_method/work_artifact\uFF09\uFF0C\u5224\u65AD\u65F6\u6309\u5404\u81EA\u8BED\u4E49\u5904\u7406\u3002\n\n**\u8F93\u51FA\u8BED\u8A00**\uFF1A`merged_content` \u4F7F\u7528\u4E0E\u5019\u9009\u6C60\u4E2D\u5DF2\u6709\u8BB0\u5FC6\u76F8\u540C\u7684\u8BED\u8A00\uFF1BJSON \u5B57\u6BB5\u540D\u3001\u679A\u4E3E\u503C\u3001record_id\u3001ISO \u65F6\u95F4\u6233\u4FDD\u6301\u82F1\u6587\u3002\n\n## \u6838\u5FC3\u89C4\u5219\n\n- **\u8DE8 type \u5408\u5E76**\uFF1A\u4E0D\u540C type\uFF08persona / episodic / instruction / work_fact / work_task / work_method / work_artifact\uFF09\u7684\u8BB0\u5FC6\u5982\u679C\u8BED\u4E49\u4E0A\u63CF\u8FF0\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6/\u5DE5\u4F5C\u5BF9\u8C61\uFF0C**\u53EF\u4EE5\u5408\u5E76**\u3002\n- **\u591A\u5BF9\u591A\u5408\u5E76**\uFF1A\u4E00\u6761\u65B0\u8BB0\u5FC6\u53EF\u4EE5\u540C\u65F6\u66FF\u6362/\u5408\u5E76\u5019\u9009\u6C60\u4E2D\u7684**\u591A\u6761**\u5DF2\u6709\u8BB0\u5FC6\uFF08\u901A\u8FC7 target_ids \u6570\u7EC4\u6307\u5B9A\uFF09\u3002\n- \u5408\u5E76\u540E\u4F60\u5FC5\u987B\u5224\u65AD\u65B0\u8BB0\u5FC6\u7684\u6700\u4F73 type\uFF08merged_type\uFF09\u3002\n- \u5DE5\u4F5C\u8BB0\u5FC6\u9ED8\u8BA4\u4F1A\u5728\u9879\u76EE\u56E2\u961F\u5185\u5171\u4EAB\uFF0C\u5408\u5E76\u5185\u5BB9\u5E94\u53EA\u4FDD\u7559\u76F8\u5173\u4FE1\u606F\uFF1B\u4E2A\u4EBA\u8BB0\u5FC6\u4E0E\u5DE5\u4F5C\u8BB0\u5FC6\u63CF\u8FF0\u540C\u4E00\u5E95\u5C42\u4E8B\u5B9E\u65F6\u53EF\u4EE5\u5408\u5E76\uFF0C\u6309\u5408\u5E76\u540E\u5185\u5BB9\u672C\u8D28\u9009\u62E9\u5F52\u5C5E type\u3002\n\n## \u5224\u65AD\u903B\u8F91\n\n1. **\u5206\u8FA8\u8BB0\u5FC6\u6027\u8D28**\uFF1A\n - **\u72B6\u6001\u7C7B**\uFF08persona/instruction\uFF09\uFF1A\u504F\u597D\u3001\u7279\u8D28\u3001\u957F\u671F\u8BBE\u5B9A\u3001\u76F8\u5BF9\u7A33\u5B9A\u7684\u4E8B\u5B9E\u3001\u884C\u4E3A\u89C4\u5219\n - **\u4E8B\u4EF6\u7C7B**\uFF08episodic\uFF09\uFF1A\u4E00\u6B21\u6027\u7ECF\u5386\u3001\u5E26\u65F6\u95F4\u70B9\u7684\u5BA2\u89C2\u8BB0\u5F55\uFF0C\u5EFA\u8BAE\u5408\u5E76\u540C\u4E00\u4EF6\u4E8B\u7684\u524D\u56E0\u540E\u679C\n - **\u5DE5\u4F5C\u4E8B\u5B9E\u7C7B**\uFF08work_fact\uFF09\uFF1A\u9879\u76EE\u4E8B\u5B9E\u3001\u9700\u6C42\u3001\u51B3\u7B56\u3001\u72B6\u6001\u3001\u98CE\u9669\u3001\u7EA6\u675F\u3001\u5B9E\u9A8C\u7ED3\u679C\n - **\u5DE5\u4F5C\u4EFB\u52A1\u7C7B**\uFF08work_task\uFF09\uFF1A\u5F85\u529E\u3001owner\u3001deadline\u3001\u4E0B\u4E00\u6B65\u8BA1\u5212\u3001\u4EFB\u52A1\u72B6\u6001\u53D8\u5316\n - **\u5DE5\u4F5C\u65B9\u6CD5\u7C7B**\uFF08work_method\uFF09\uFF1ASOP\u3001\u7981\u5FCC\u3001\u539F\u5219\u3001\u7ECF\u9A8C\u3001\u8BBE\u8BA1\u601D\u8DEF\u3001\u5224\u65AD\u6807\u51C6\u3001Agent \u884C\u4E3A\u89C4\u5219\n - **\u5DE5\u4F5C\u8D44\u4EA7\u7C7B**\uFF08work_artifact\uFF09\uFF1A\u6587\u6863\u3001PR\u3001Issue\u3001Prompt\u3001\u62A5\u544A\u3001\u4EE3\u7801\u5206\u652F\u3001\u8BBE\u8BA1\u7A3F\u3001\u94FE\u63A5\u7B49\n\n2. **\u5224\u65AD\u662F\u5426\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6/\u5DE5\u4F5C\u5BF9\u8C61**\uFF1A\n - \u4E2A\u4EBA\u8BB0\u5FC6\uFF1A\u4E3B\u4F53\u76F8\u540C\u3001\u4E3B\u9898\u4E00\u81F4\u3001\u65F6\u95F4\u63A5\u8FD1\u3001scene_name \u76F8\u4F3C\n - \u5DE5\u4F5C\u8BB0\u5FC6\uFF1A\u540C\u4E00\u9879\u76EE\u3001\u6A21\u5757\u3001\u9700\u6C42\u3001\u4EFB\u52A1\u3001\u98CE\u9669\u3001\u51B3\u7B56\u3001\u65B9\u6CD5\u3001\u8D44\u4EA7\uFF0C\u6216\u540C\u4E00\u4EFB\u52A1\u7684\u4E0D\u540C\u9636\u6BB5\u3001\u540C\u4E00\u65B9\u6CD5\u7684\u8865\u5145\u3001\u540C\u4E00\u8D44\u4EA7\u7684\u7248\u672C\u53D8\u5316\uFF0C\u901A\u5E38\u53EF\u4EE5\u5408\u5E76\uFF1B\u4EC5\u540C\u5C5E\u4E00\u4E2A\u5927\u9879\u76EE\u4F46\u8BA8\u8BBA\u5BF9\u8C61\u4E0D\u540C\uFF0C\u4E0D\u5E94\u5F3A\u884C\u5408\u5E76\n\n3. **\u9009\u62E9\u52A8\u4F5C**\uFF1A\n - \"store\"\uFF1A\u89C6\u4E3A\u65B0\u4FE1\u606F\uFF0C\u65B0\u589E\u5F53\u524D\u8BB0\u5FC6\u3002\n - \"skip\"\uFF1A\u5DF2\u6709\u8BB0\u5FC6\u66F4\u597D\uFF0C\u65B0\u8BB0\u5FC6\u65E0\u589E\u91CF\u6216\u66F4\u6A21\u7CCA\uFF0C\u5FFD\u7565\u5F53\u524D\u8BB0\u5FC6\u3002\n - \"update\"\uFF1A\u540C\u4E00\u4E8B\u5B9E/\u4E8B\u4EF6/\u5DE5\u4F5C\u5BF9\u8C61\uFF0C\u65B0\u8BB0\u5FC6\u5728\u5185\u5BB9\u6216\u65F6\u95F4\u4E0A\u66F4\u4F18\uFF08\u66F4\u5177\u4F53\u3001\u66F4\u665A\u3001\u66F4\u6743\u5A01\u6216\u7EA0\u9519\uFF09\uFF0C\u4EE5\u65B0\u8BB0\u5FC6\u4E3A\u4E3B\u8986\u76D6\u65E7\u8BB0\u5FC6\uFF0C\u53EF\u4FDD\u7559\u65E7\u8BB0\u5FC6\u4E2D\u4ECD\u6B63\u786E\u7684\u7EC6\u8282\u3002\n - \"merge\"\uFF1A\u540C\u4E00\u4E8B\u5B9E\u3001\u540C\u4E00\u6F14\u5316\u8FC7\u7A0B\u6216\u4FE1\u606F\u4E92\u8865\u4E14\u4E0D\u77DB\u76FE\u7684\u591A\u6761\u8BB0\u5FC6\uFF0C\u5408\u5E76\u6210\u4E00\u6761\u66F4\u5B8C\u6574\u8BB0\u5FC6\uFF0C\u4FE1\u606F\u5C3D\u91CF\u4E0D\u5197\u4F59\u3002\n\n4. **\u7B56\u7565\u503E\u5411**\uFF1A\n - \u72B6\u6001\u7C7B\uFF1A\u591A\u6761\u63CF\u8FF0\u540C\u4E00\u504F\u597D/\u7279\u8D28 \u2192 \u503E\u5411 merge\uFF1B\u65E0\u589E\u91CF \u2192 skip\uFF1B\u660E\u786E\u66F4\u65B0 \u2192 update\n - \u4E8B\u4EF6\u7C7B\uFF1A\u540C\u4E00\u4E8B\u4EF6\u7684\u524D\u56E0\u540E\u679C\u3001\u4E0D\u540C\u9636\u6BB5 \u2192 \u503E\u5411 merge \u4E3A\u4E00\u6761\u5B8C\u6574\u53D9\u8FF0\uFF1B\u5B8C\u5168\u76F8\u540C \u2192 skip\n - work_task\uFF1A\u540C\u4E00\u4EFB\u52A1\u7684 owner\u3001deadline\u3001\u72B6\u6001\u53D8\u5316 \u2192 \u503E\u5411 update\uFF1B\u8865\u5145\u4F9D\u8D56\u6216\u9A8C\u6536\u6807\u51C6 \u2192 \u503E\u5411 merge\n - work_method\uFF1A\u540C\u4E00 SOP\u3001\u7981\u5FCC\u3001\u539F\u5219\u3001\u7ECF\u9A8C\u7684\u8865\u5145 \u2192 \u503E\u5411 merge\uFF1B\u66F4\u6E05\u6670\u901A\u7528\u7684\u8868\u8FF0 \u2192 \u503E\u5411 update\n - work_artifact\uFF1A\u540C\u4E00\u8D44\u4EA7\u7684\u7528\u9014\u3001\u7248\u672C\u3001\u94FE\u63A5\u8865\u5145 \u2192 \u503E\u5411 merge \u6216 update\n - \u8DE8\u7C7B\u578B\u793A\u4F8B\uFF1A\u4E00\u6761 episodic \"\u7528\u6237\u5728 2018 \u5E74\u5F00\u59CB\u505A\u64AD\u5BA2\" + \u4E00\u6761 persona \"\u7528\u6237\u6709\u64AD\u5BA2\u5236\u4F5C\u7ECF\u9A8C\" \u2192 \u53EF merge \u4E3A\u4E00\u6761 persona \u6216 episodic\uFF08\u53D6\u51B3\u4E8E\u4FE1\u606F\u4FA7\u91CD\uFF09\n\n5. **timestamp \u5904\u7406**\uFF1A\n - merge / update \u65F6\uFF0Cmerged_timestamps \u5E94\u5305\u542B**\u6240\u6709\u76F8\u5173\u8BB0\u5FC6\u7684\u65F6\u95F4\u6233\u5E76\u96C6**\uFF08\u53BB\u91CD\u6392\u5E8F\uFF09\n - \u8FD9\u6837\u53EF\u4EE5\u4FDD\u7559\u4E8B\u4EF6\u4E0E\u5DE5\u4F5C\u5BF9\u8C61\u6F14\u5316\u7684\u5B8C\u6574\u65F6\u95F4\u7EBF\n\n## \u8F93\u51FA\u683C\u5F0F\n\n\u4E25\u683C\u8F93\u51FA JSON \u6570\u7EC4\uFF0C\u6BCF\u4E2A\u5143\u7D20\u5BF9\u5E94\u4E00\u6761\u65B0\u8BB0\u5FC6\u7684\u51B3\u7B56\u3002\u4E0D\u8F93\u51FA\u4EFB\u4F55\u5176\u4ED6\u5185\u5BB9\uFF1A\n\n[\n {\n \"record_id\": \"\u65B0\u8BB0\u5FC6\u7684 record_id\",\n \"action\": \"store|update|skip|merge\",\n \"target_ids\": [\"\u8981\u5220\u9664\u7684\u5019\u9009\u8BB0\u5FC6 record_id 1\", \"record_id 2\"],\n \"merged_content\": \"\u5408\u5E76/\u66F4\u65B0\u540E\u7684\u8BB0\u5FC6\u5185\u5BB9\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\",\n \"merged_type\": \"\u5408\u5E76\u540E\u7684\u6700\u4F73 type\uFF1Apersona|episodic|instruction|work_fact|work_task|work_method|work_artifact\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\",\n \"merged_priority\": 85,\n \"merged_timestamps\": [\"\u5408\u5E76\u540E\u7684\u65F6\u95F4\u6233\u6570\u7EC4\uFF0C\u5305\u542B\u6240\u6709\u65B0\u65E7\u8BB0\u5FC6\u65F6\u95F4\u6233\u7684\u5E76\u96C6\uFF08merge/update \u65F6\u5FC5\u586B\uFF09\"]\n }\n]\n\n\u5B57\u6BB5\u8BF4\u660E\uFF1A\n- target_ids\uFF1A\u8981\u5220\u9664\u66FF\u6362\u7684\u65E7\u8BB0\u5FC6 ID **\u6570\u7EC4**\uFF08\u53EF\u4EE5 1 \u6761\u6216\u591A\u6761\uFF09\u3002store/skip \u65F6\u7701\u7565\u6216\u4E3A\u7A7A\u3002\n- merged_content\uFF1Amerge/update \u65F6\u7684\u6700\u7EC8\u8BB0\u5FC6\u6587\u672C\u3002store/skip \u65F6\u7701\u7565\u3002\n- merged_type\uFF1Amerge/update \u540E\u8BB0\u5FC6\u5E94\u5F52\u5C5E\u7684 type\u3002\u6839\u636E\u5408\u5E76\u540E\u5185\u5BB9\u672C\u8D28\u5224\u65AD\u3002\n- merged_priority\uFF1Amerge/update \u540E\u7684\u65B0\u4F18\u5148\u7EA7\uFF080-100 \u6574\u6570\uFF0Cmerge/update \u65F6\u5FC5\u586B\uFF09\u3002\u5408\u5E76\u540E\u4FE1\u606F\u66F4\u5B8C\u6574\u3001\u66F4\u786E\u5B9A\uFF0C\u901A\u5E38\u5E94**\u914C\u60C5\u63D0\u5347** priority\u3002\u53C2\u8003\u6807\u51C6\uFF1A80-100\uFF08\u6838\u5FC3\u7279\u8D28/\u91CD\u8981\u4E8B\u4EF6/\u5173\u952E\u4E8B\u5B9E/\u91CD\u8981\u4EFB\u52A1/\u6838\u5FC3\u65B9\u6CD5/\u91CD\u8981\u8D44\u4EA7\uFF09\uFF0C60-79\uFF08\u4E00\u822C\u4FE1\u606F\uFF09\uFF0C<60\uFF08\u6B21\u8981\u4FE1\u606F\uFF09\u3002\n- merged_timestamps\uFF1A\u5408\u5E76\u540E\u7684\u65F6\u95F4\u6233\u6570\u7EC4\u3002\u6536\u96C6\u65B0\u8BB0\u5FC6 + \u6240\u6709\u88AB\u5408\u5E76\u65E7\u8BB0\u5FC6\u7684\u65F6\u95F4\u6233\uFF0C\u53BB\u91CD\u6392\u5E8F\u3002";
12
+ export declare function getConflictDetectionSystemPrompt(mode: ExtractMode): string;
13
+ export interface CandidateMatch {
14
+ newMemory: ExtractedMemory & {
15
+ record_id: string;
16
+ };
17
+ candidates: MemoryRecord[];
18
+ }
19
+ /**
20
+ * 格式化批量冲突检测 prompt(统一候选池)。
21
+ */
22
+ export declare function formatBatchConflictPrompt(matches: CandidateMatch[]): string;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * 去重 prompt(批量冲突检测:store/update/merge/skip 决策词表)。
3
+ *
4
+ * 净室重写说明:本文件的 prompt 文案按重写规格(Phase 2 决策)逐字沿用——
5
+ * prompt 内容直接决定蒸馏质量,是已发布行为的一部分,不属于可自由重写文本;
6
+ * 代码结构与注释随实现重写。
7
+ */
8
+ export const CONFLICT_DETECTION_SYSTEM_PROMPT = `你是记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。
9
+
10
+ **输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。
11
+
12
+ ## 核心规则
13
+
14
+ - **跨 type 合并**:不同 type(persona / episodic / instruction / work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一事实/事件,**可以合并**。
15
+ - **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。
16
+ - 合并后你必须判断新记忆的最佳 type(merged_type)。
17
+
18
+ ## 判断逻辑
19
+
20
+ 1. **分辨记忆性质**:
21
+ - **状态类**(persona/instruction):偏好、特质、长期设定、相对稳定的事实、行为规则
22
+ - **事件类**(episodic):一次性经历、带时间点的客观记录,建议合并同一件事的前因后果
23
+
24
+ 2. **判断是否同一事实/事件**:主体相同、主题一致、时间接近、scene_name 相似
25
+
26
+ 3. **选择动作**:
27
+ - "store":视为新信息,新增当前记忆。
28
+ - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。
29
+ - "update":同一事实/事件,新记忆在内容或时间上更优(更具体、更晚或纠错),以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。
30
+ - "merge":同一事实或同一演化过程,多条记忆信息互补且不矛盾,合并成一条更完整记忆,信息尽量不冗余。
31
+
32
+ 4. **策略倾向**:
33
+ - 状态类:多条描述同一偏好/特质 → 倾向 merge;无增量 → skip;明确更新 → update
34
+ - 事件类:同一事件的前因后果、不同阶段 → 倾向 merge 为一条完整叙述;完全相同 → skip
35
+ - 跨类型示例:一条 episodic "用户在 2018 年开始做播客" + 一条 persona "用户有播客制作经验" → 可 merge 为一条 persona 或 episodic(取决于信息侧重)
36
+
37
+ 5. **timestamp 处理**:
38
+ - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序)
39
+ - 这样可以保留事件发生的完整时间线
40
+
41
+ ## 输出格式
42
+
43
+ 严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容:
44
+
45
+ [
46
+ {
47
+ "record_id": "新记忆的 record_id",
48
+ "action": "store|update|skip|merge",
49
+ "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"],
50
+ "merged_content": "合并/更新后的记忆内容(merge/update 时必填)",
51
+ "merged_type": "合并后的最佳 type:persona|episodic|instruction|work_fact|work_task|work_method|work_artifact(merge/update 时必填)",
52
+ "merged_priority": 85,
53
+ "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"]
54
+ }
55
+ ]
56
+
57
+ 字段说明:
58
+ - target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。
59
+ - merged_content:merge/update 时的最终记忆文本。store/skip 时省略。
60
+ - merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。
61
+ - merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority(例如两条 priority 70 的记忆合并后可提升到 80)。参考标准:80-100(核心特质/重要事件),60-79(一般偏好/普通活动),<60(次要信息)。
62
+ - merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`;
63
+ export const WORK_CONFLICT_DETECTION_SYSTEM_PROMPT = `你是团队工作记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。
64
+
65
+ **输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。
66
+
67
+ ## 核心规则
68
+
69
+ - **跨 type 合并**:不同 type(work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一工作对象、任务、方法或资产,**可以合并**。
70
+ - **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。
71
+ - 合并后你必须判断新记忆的最佳 type(merged_type)。
72
+ - 记忆默认会在项目团队内共享,合并内容应只保留工作相关信息。
73
+
74
+ ## 判断逻辑
75
+
76
+ 1. **分辨记忆性质**:
77
+ - **工作事实类(work_fact)**:项目事实、需求、决策、状态、风险、约束、实验结果、客户反馈。
78
+ - **工作任务类(work_task)**:待办、owner、deadline、下一步计划、任务状态变化。
79
+ - **工作方法类(work_method)**:SOP、禁忌、原则、经验、设计思路、判断标准、Agent 行为规则。
80
+ - **工作资产类(work_artifact)**:文档、PR、Issue、Prompt、报告、代码分支、设计稿、链接等。
81
+
82
+ 2. **判断是否同一工作对象/演化过程**:
83
+ - 同一项目、模块、需求、任务、风险、决策、方法、资产,且 scene_name 或语义高度相似。
84
+ - 同一任务的不同阶段、同一方法的补充、同一资产的版本或用途变化,通常可以合并。
85
+ - 仅属于同一大项目但讨论对象不同,不应强行合并。
86
+
87
+ 3. **选择动作**:
88
+ - "store":视为新信息,新增当前记忆。
89
+ - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。
90
+ - "update":同一工作对象,新记忆更具体、更新、更权威或纠正旧信息,以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。
91
+ - "merge":同一工作对象或同一演化过程,新旧记忆互补且不矛盾,合并成一条更完整记忆,信息尽量不冗余。
92
+
93
+ 4. **策略倾向**:
94
+ - work_fact:同一事实/决策/状态的补充或修正 → 倾向 update 或 merge。
95
+ - work_task:同一任务的 owner、deadline、状态变化 → 倾向 update;补充依赖或验收标准 → 倾向 merge。
96
+ - work_method:同一 SOP、禁忌、原则、经验的补充 → 倾向 merge;更清晰通用的表述 → 倾向 update。
97
+ - work_artifact:同一文档、PR、Prompt、报告等资产的用途、版本、链接补充 → 倾向 merge 或 update。
98
+ - 跨类型示例:一条 work_fact "团队决定 L1 type 保持少量高层分类" + 一条 work_method "L1 type 不宜过细,否则影响 L2/L3 聚合" → 可 merge 为 work_method。
99
+
100
+ 5. **timestamp 处理**:
101
+ - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序)。
102
+ - 这样可以保留工作事实、任务或方法演化的完整时间线。
103
+
104
+ ## 输出格式
105
+
106
+ 严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容:
107
+
108
+ [
109
+ {
110
+ "record_id": "新记忆的 record_id",
111
+ "action": "store|update|skip|merge",
112
+ "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"],
113
+ "merged_content": "合并/更新后的记忆内容(merge/update 时必填)",
114
+ "merged_type": "合并后的最佳 type:work_fact|work_task|work_method|work_artifact(merge/update 时必填)",
115
+ "merged_priority": 85,
116
+ "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"]
117
+ }
118
+ ]
119
+
120
+ 字段说明:
121
+ - target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。
122
+ - merged_content:merge/update 时的最终记忆文本。store/skip 时省略。
123
+ - merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。
124
+ - merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority。参考标准:80-100(关键事实/重要任务/核心方法/重要资产),60-79(一般工作信息),<60(次要信息)。
125
+ - merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`;
126
+ export const ALL_CONFLICT_DETECTION_SYSTEM_PROMPT = `你是记忆冲突检测器。批量比较多条【新记忆】与【统一候选记忆池】中的已有记忆,逐条决定如何处理。候选池同时包含个人记忆(persona/episodic/instruction)与团队工作记忆(work_fact/work_task/work_method/work_artifact),判断时按各自语义处理。
127
+
128
+ **输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言;JSON 字段名、枚举值、record_id、ISO 时间戳保持英文。
129
+
130
+ ## 核心规则
131
+
132
+ - **跨 type 合并**:不同 type(persona / episodic / instruction / work_fact / work_task / work_method / work_artifact)的记忆如果语义上描述同一事实/事件/工作对象,**可以合并**。
133
+ - **多对多合并**:一条新记忆可以同时替换/合并候选池中的**多条**已有记忆(通过 target_ids 数组指定)。
134
+ - 合并后你必须判断新记忆的最佳 type(merged_type)。
135
+ - 工作记忆默认会在项目团队内共享,合并内容应只保留相关信息;个人记忆与工作记忆描述同一底层事实时可以合并,按合并后内容本质选择归属 type。
136
+
137
+ ## 判断逻辑
138
+
139
+ 1. **分辨记忆性质**:
140
+ - **状态类**(persona/instruction):偏好、特质、长期设定、相对稳定的事实、行为规则
141
+ - **事件类**(episodic):一次性经历、带时间点的客观记录,建议合并同一件事的前因后果
142
+ - **工作事实类**(work_fact):项目事实、需求、决策、状态、风险、约束、实验结果
143
+ - **工作任务类**(work_task):待办、owner、deadline、下一步计划、任务状态变化
144
+ - **工作方法类**(work_method):SOP、禁忌、原则、经验、设计思路、判断标准、Agent 行为规则
145
+ - **工作资产类**(work_artifact):文档、PR、Issue、Prompt、报告、代码分支、设计稿、链接等
146
+
147
+ 2. **判断是否同一事实/事件/工作对象**:
148
+ - 个人记忆:主体相同、主题一致、时间接近、scene_name 相似
149
+ - 工作记忆:同一项目、模块、需求、任务、风险、决策、方法、资产,或同一任务的不同阶段、同一方法的补充、同一资产的版本变化,通常可以合并;仅同属一个大项目但讨论对象不同,不应强行合并
150
+
151
+ 3. **选择动作**:
152
+ - "store":视为新信息,新增当前记忆。
153
+ - "skip":已有记忆更好,新记忆无增量或更模糊,忽略当前记忆。
154
+ - "update":同一事实/事件/工作对象,新记忆在内容或时间上更优(更具体、更晚、更权威或纠错),以新记忆为主覆盖旧记忆,可保留旧记忆中仍正确的细节。
155
+ - "merge":同一事实、同一演化过程或信息互补且不矛盾的多条记忆,合并成一条更完整记忆,信息尽量不冗余。
156
+
157
+ 4. **策略倾向**:
158
+ - 状态类:多条描述同一偏好/特质 → 倾向 merge;无增量 → skip;明确更新 → update
159
+ - 事件类:同一事件的前因后果、不同阶段 → 倾向 merge 为一条完整叙述;完全相同 → skip
160
+ - work_task:同一任务的 owner、deadline、状态变化 → 倾向 update;补充依赖或验收标准 → 倾向 merge
161
+ - work_method:同一 SOP、禁忌、原则、经验的补充 → 倾向 merge;更清晰通用的表述 → 倾向 update
162
+ - work_artifact:同一资产的用途、版本、链接补充 → 倾向 merge 或 update
163
+ - 跨类型示例:一条 episodic "用户在 2018 年开始做播客" + 一条 persona "用户有播客制作经验" → 可 merge 为一条 persona 或 episodic(取决于信息侧重)
164
+
165
+ 5. **timestamp 处理**:
166
+ - merge / update 时,merged_timestamps 应包含**所有相关记忆的时间戳并集**(去重排序)
167
+ - 这样可以保留事件与工作对象演化的完整时间线
168
+
169
+ ## 输出格式
170
+
171
+ 严格输出 JSON 数组,每个元素对应一条新记忆的决策。不输出任何其他内容:
172
+
173
+ [
174
+ {
175
+ "record_id": "新记忆的 record_id",
176
+ "action": "store|update|skip|merge",
177
+ "target_ids": ["要删除的候选记忆 record_id 1", "record_id 2"],
178
+ "merged_content": "合并/更新后的记忆内容(merge/update 时必填)",
179
+ "merged_type": "合并后的最佳 type:persona|episodic|instruction|work_fact|work_task|work_method|work_artifact(merge/update 时必填)",
180
+ "merged_priority": 85,
181
+ "merged_timestamps": ["合并后的时间戳数组,包含所有新旧记忆时间戳的并集(merge/update 时必填)"]
182
+ }
183
+ ]
184
+
185
+ 字段说明:
186
+ - target_ids:要删除替换的旧记忆 ID **数组**(可以 1 条或多条)。store/skip 时省略或为空。
187
+ - merged_content:merge/update 时的最终记忆文本。store/skip 时省略。
188
+ - merged_type:merge/update 后记忆应归属的 type。根据合并后内容本质判断。
189
+ - merged_priority:merge/update 后的新优先级(0-100 整数,merge/update 时必填)。合并后信息更完整、更确定,通常应**酌情提升** priority。参考标准:80-100(核心特质/重要事件/关键事实/重要任务/核心方法/重要资产),60-79(一般信息),<60(次要信息)。
190
+ - merged_timestamps:合并后的时间戳数组。收集新记忆 + 所有被合并旧记忆的时间戳,去重排序。`;
191
+ export function getConflictDetectionSystemPrompt(mode) {
192
+ if (mode === 'auto')
193
+ return ALL_CONFLICT_DETECTION_SYSTEM_PROMPT;
194
+ return mode === 'work' ? WORK_CONFLICT_DETECTION_SYSTEM_PROMPT : CONFLICT_DETECTION_SYSTEM_PROMPT;
195
+ }
196
+ /**
197
+ * 格式化批量冲突检测 prompt(统一候选池)。
198
+ */
199
+ export function formatBatchConflictPrompt(matches) {
200
+ const unifiedPool = new Map();
201
+ const perMemoryCandidateIds = new Map();
202
+ for (const m of matches) {
203
+ const candidateIds = [];
204
+ for (const c of m.candidates) {
205
+ if (!unifiedPool.has(c.id))
206
+ unifiedPool.set(c.id, c);
207
+ candidateIds.push(c.id);
208
+ }
209
+ perMemoryCandidateIds.set(m.newMemory.record_id, candidateIds);
210
+ }
211
+ const poolList = Array.from(unifiedPool.values()).map((c) => ({
212
+ record_id: c.id,
213
+ content: c.content,
214
+ type: c.type,
215
+ priority: c.priority,
216
+ scene_name: c.scene_name,
217
+ timestamps: c.timestamps,
218
+ }));
219
+ let poolSection;
220
+ if (poolList.length === 0) {
221
+ poolSection = '## 统一候选记忆池\n\n(空,没有已有记忆,所有新记忆直接 store)';
222
+ }
223
+ else {
224
+ const poolStr = JSON.stringify(poolList, null, 2);
225
+ poolSection = `## 统一候选记忆池(共 ${poolList.length} 条已有记忆)\n\n${poolStr}`;
226
+ }
227
+ const memoryParts = matches.map((m, idx) => {
228
+ const relatedIds = perMemoryCandidateIds.get(m.newMemory.record_id) ?? [];
229
+ const relatedNote = relatedIds.length > 0 ? JSON.stringify(relatedIds) : '[](无相似候选,直接 store)';
230
+ const memStr = JSON.stringify({
231
+ record_id: m.newMemory.record_id,
232
+ content: m.newMemory.content,
233
+ type: m.newMemory.type,
234
+ priority: m.newMemory.priority,
235
+ scene_name: m.newMemory.scene_name,
236
+ }, null, 2);
237
+ return `### 第 ${idx + 1} 条新记忆 (record_id: ${m.newMemory.record_id})\n${memStr}\n\n【关联候选 ID】${relatedNote}`;
238
+ });
239
+ const newMemoriesText = memoryParts.join('\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n');
240
+ return `**输出语言**:\`merged_content\` 使用与候选池中已有记忆相同的语言。
241
+
242
+ ${poolSection}
243
+
244
+ ${'═'.repeat(50)}
245
+
246
+ ## 待判断的新记忆(共 ${matches.length} 条)
247
+
248
+ ${newMemoriesText}
249
+
250
+ 请逐条判断并输出决策 JSON 数组。当某条新记忆的候选列表为空时,该条直接输出 action=store。`;
251
+ }