weflow-cli 1.6.4 → 1.8.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 (205) hide show
  1. package/ARCHITECTURE.md +3 -2
  2. package/CHANGELOG.md +1966 -0
  3. package/OPERATIONS.md +808 -0
  4. package/README.en.md +93 -7
  5. package/README.md +31 -6
  6. package/bin/weflow-cli.ts +1245 -26
  7. package/dist/bin/weflow-cli.js +1349 -72
  8. package/dist/bin/weflow-cli.js.map +1 -1
  9. package/dist/mcp-server/index.js +39 -13
  10. package/dist/mcp-server/index.js.map +1 -1
  11. package/dist/src/core/appMsgFormat.d.ts +37 -0
  12. package/dist/src/core/appMsgFormat.d.ts.map +1 -0
  13. package/dist/src/core/appMsgFormat.js +106 -0
  14. package/dist/src/core/appMsgFormat.js.map +1 -0
  15. package/dist/src/core/ntCore.d.ts +43 -0
  16. package/dist/src/core/ntCore.d.ts.map +1 -1
  17. package/dist/src/core/ntCore.js +31 -0
  18. package/dist/src/core/ntCore.js.map +1 -1
  19. package/dist/src/core/sqlcipherCore.d.ts +0 -12
  20. package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
  21. package/dist/src/core/sqlcipherCore.js +5 -64
  22. package/dist/src/core/sqlcipherCore.js.map +1 -1
  23. package/dist/src/core/wcdbCore.d.ts +1 -1
  24. package/dist/src/core/wcdbCore.d.ts.map +1 -1
  25. package/dist/src/core/wcdbCore.js +5 -1
  26. package/dist/src/core/wcdbCore.js.map +1 -1
  27. package/dist/src/panel/client.d.ts +25 -0
  28. package/dist/src/panel/client.d.ts.map +1 -0
  29. package/dist/src/panel/client.js +34 -0
  30. package/dist/src/panel/client.js.map +1 -0
  31. package/dist/src/panel/endpoint.d.ts +45 -0
  32. package/dist/src/panel/endpoint.d.ts.map +1 -0
  33. package/dist/src/panel/endpoint.js +132 -0
  34. package/dist/src/panel/endpoint.js.map +1 -0
  35. package/dist/src/panel/launch.d.ts +50 -0
  36. package/dist/src/panel/launch.d.ts.map +1 -0
  37. package/dist/src/panel/launch.js +74 -0
  38. package/dist/src/panel/launch.js.map +1 -0
  39. package/dist/src/panel/server.d.ts +23 -0
  40. package/dist/src/panel/server.d.ts.map +1 -0
  41. package/dist/src/panel/server.js +413 -0
  42. package/dist/src/panel/server.js.map +1 -0
  43. package/dist/src/panel/userId.d.ts +37 -0
  44. package/dist/src/panel/userId.d.ts.map +1 -0
  45. package/dist/src/panel/userId.js +56 -0
  46. package/dist/src/panel/userId.js.map +1 -0
  47. package/dist/src/services/assistantDaemon.d.ts +20 -3
  48. package/dist/src/services/assistantDaemon.d.ts.map +1 -1
  49. package/dist/src/services/assistantDaemon.js +46 -9
  50. package/dist/src/services/assistantDaemon.js.map +1 -1
  51. package/dist/src/services/assistantEval.d.ts +141 -0
  52. package/dist/src/services/assistantEval.d.ts.map +1 -0
  53. package/dist/src/services/assistantEval.js +497 -0
  54. package/dist/src/services/assistantEval.js.map +1 -0
  55. package/dist/src/services/assistantMemory.d.ts +106 -4
  56. package/dist/src/services/assistantMemory.d.ts.map +1 -1
  57. package/dist/src/services/assistantMemory.js +396 -61
  58. package/dist/src/services/assistantMemory.js.map +1 -1
  59. package/dist/src/services/assistantPrivacy.d.ts +15 -2
  60. package/dist/src/services/assistantPrivacy.d.ts.map +1 -1
  61. package/dist/src/services/assistantPrivacy.js +18 -4
  62. package/dist/src/services/assistantPrivacy.js.map +1 -1
  63. package/dist/src/services/assistantRouter.d.ts +41 -0
  64. package/dist/src/services/assistantRouter.d.ts.map +1 -0
  65. package/dist/src/services/assistantRouter.js +151 -0
  66. package/dist/src/services/assistantRouter.js.map +1 -0
  67. package/dist/src/services/assistantService.d.ts +209 -3
  68. package/dist/src/services/assistantService.d.ts.map +1 -1
  69. package/dist/src/services/assistantService.js +590 -75
  70. package/dist/src/services/assistantService.js.map +1 -1
  71. package/dist/src/services/assistantTools.d.ts +205 -1
  72. package/dist/src/services/assistantTools.d.ts.map +1 -1
  73. package/dist/src/services/assistantTools.js +1089 -58
  74. package/dist/src/services/assistantTools.js.map +1 -1
  75. package/dist/src/services/assistantTrace.d.ts +47 -0
  76. package/dist/src/services/assistantTrace.d.ts.map +1 -0
  77. package/dist/src/services/assistantTrace.js +150 -0
  78. package/dist/src/services/assistantTrace.js.map +1 -0
  79. package/dist/src/services/chatService.d.ts +13 -0
  80. package/dist/src/services/chatService.d.ts.map +1 -1
  81. package/dist/src/services/chatService.js +23 -0
  82. package/dist/src/services/chatService.js.map +1 -1
  83. package/dist/src/services/configService.d.ts +9 -0
  84. package/dist/src/services/configService.d.ts.map +1 -1
  85. package/dist/src/services/configService.js +15 -8
  86. package/dist/src/services/configService.js.map +1 -1
  87. package/dist/src/services/messageQuery.d.ts +35 -0
  88. package/dist/src/services/messageQuery.d.ts.map +1 -1
  89. package/dist/src/services/messageQuery.js +61 -0
  90. package/dist/src/services/messageQuery.js.map +1 -1
  91. package/dist/src/services/pythonBridge.d.ts +41 -0
  92. package/dist/src/services/pythonBridge.d.ts.map +1 -0
  93. package/dist/src/services/pythonBridge.js +119 -0
  94. package/dist/src/services/pythonBridge.js.map +1 -0
  95. package/dist/src/services/syncService.d.ts +127 -0
  96. package/dist/src/services/syncService.d.ts.map +1 -0
  97. package/dist/src/services/syncService.js +263 -0
  98. package/dist/src/services/syncService.js.map +1 -0
  99. package/dist/src/services/syncState.d.ts +113 -0
  100. package/dist/src/services/syncState.d.ts.map +1 -0
  101. package/dist/src/services/syncState.js +214 -0
  102. package/dist/src/services/syncState.js.map +1 -0
  103. package/dist/src/services/wechatMessageService.d.ts +25 -0
  104. package/dist/src/services/wechatMessageService.d.ts.map +1 -1
  105. package/dist/src/services/wechatMessageService.js +69 -7
  106. package/dist/src/services/wechatMessageService.js.map +1 -1
  107. package/dist/src/utils/atomicWrite.d.ts +4 -0
  108. package/dist/src/utils/atomicWrite.d.ts.map +1 -0
  109. package/dist/src/utils/atomicWrite.js +60 -0
  110. package/dist/src/utils/atomicWrite.js.map +1 -0
  111. package/dist/src/utils/dateRange.d.ts +15 -0
  112. package/dist/src/utils/dateRange.d.ts.map +1 -1
  113. package/dist/src/utils/dateRange.js +47 -0
  114. package/dist/src/utils/dateRange.js.map +1 -1
  115. package/dist/src/utils/text.d.ts +18 -0
  116. package/dist/src/utils/text.d.ts.map +1 -0
  117. package/dist/src/utils/text.js +20 -0
  118. package/dist/src/utils/text.js.map +1 -0
  119. package/docs/AI_INTERFACE.md +2 -0
  120. package/docs/DECISIONS.md +1484 -20
  121. package/docs/EXTENDING.md +187 -0
  122. package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +338 -0
  123. package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +310 -0
  124. package/docs/MCP.md +23 -4
  125. package/docs/PROJECT_STATE.md +299 -96
  126. package/docs/SETUP.md +22 -0
  127. package/docs/SYNC_CONTRACT.md +180 -0
  128. package/mcp-server/index.ts +681 -657
  129. package/package.json +84 -83
  130. package/resources/panel/ball-position.cjs +153 -0
  131. package/resources/panel/index.html +46 -0
  132. package/resources/panel/main.cjs +447 -0
  133. package/resources/panel/mascot-focus.png +0 -0
  134. package/resources/panel/mascot-happy.png +0 -0
  135. package/resources/panel/mascot-sorry.png +0 -0
  136. package/resources/panel/mascot-tired.png +0 -0
  137. package/resources/panel/mascot.png +0 -0
  138. package/resources/panel/package.json +7 -0
  139. package/resources/panel/panel.css +407 -0
  140. package/resources/panel/preload.cjs +72 -0
  141. package/resources/panel/quick-menu.cjs +197 -0
  142. package/resources/panel/renderer.js +441 -0
  143. package/resources/panel/tray-menu.cjs +40 -0
  144. package/resources/panel/tray.png +0 -0
  145. package/scripts/_utils.py +403 -54
  146. package/scripts/annual_report.py +4 -2
  147. package/scripts/article_notes.py +423 -0
  148. package/scripts/backfill_articles.py +438 -0
  149. package/scripts/biz_daily.py +590 -105
  150. package/scripts/chat_notes.py +470 -0
  151. package/scripts/classify_daily.py +10 -18
  152. package/scripts/compile_wiki.py +1059 -46
  153. package/scripts/create_reading_notes.py +199 -14
  154. package/scripts/decide.py +253 -0
  155. package/scripts/draft_reply.py +671 -0
  156. package/scripts/export_chat_html.py +212 -46
  157. package/scripts/extract_todos.py +31 -5
  158. package/scripts/fav_notes.py +318 -0
  159. package/scripts/fix_topics.py +2 -1
  160. package/scripts/generate_ai_report.py +214 -9
  161. package/scripts/generate_html.py +18 -3
  162. package/scripts/generate_review.py +2 -2
  163. package/scripts/jev_client.py +294 -0
  164. package/scripts/jev_probe.py +440 -0
  165. package/scripts/nt_common.py +82 -0
  166. package/scripts/nt_decrypt.py +1755 -1352
  167. package/scripts/pipeline.py +27 -2
  168. package/scripts/promote_ideas.py +19 -4
  169. package/scripts/quality_eval.py +707 -0
  170. package/scripts/rag_chat.py +4 -4
  171. package/scripts/read_image.py +187 -0
  172. package/scripts/reply_debt.py +488 -0
  173. package/scripts/route_cards.py +453 -0
  174. package/scripts/semantic_search.py +208 -49
  175. package/scripts/user_notes.py +315 -0
  176. package/scripts/vault_rag.py +13 -7
  177. package/scripts/vault_search.py +21 -4
  178. package/scripts/wiki_lint.py +397 -0
  179. package/src/core/appMsgFormat.ts +117 -0
  180. package/src/core/ntCore.ts +67 -0
  181. package/src/core/sqlcipherCore.ts +5 -61
  182. package/src/core/wcdbCore.ts +6 -2
  183. package/src/panel/client.ts +49 -0
  184. package/src/panel/endpoint.ts +144 -0
  185. package/src/panel/launch.ts +101 -0
  186. package/src/panel/server.ts +424 -0
  187. package/src/panel/userId.ts +72 -0
  188. package/src/services/assistantDaemon.ts +57 -9
  189. package/src/services/assistantEval.ts +611 -0
  190. package/src/services/assistantMemory.ts +413 -57
  191. package/src/services/assistantPrivacy.ts +18 -4
  192. package/src/services/assistantRouter.ts +191 -0
  193. package/src/services/assistantService.ts +671 -74
  194. package/src/services/assistantTools.ts +1118 -57
  195. package/src/services/assistantTrace.ts +175 -0
  196. package/src/services/chatService.ts +30 -0
  197. package/src/services/configService.ts +403 -385
  198. package/src/services/messageQuery.ts +97 -0
  199. package/src/services/pythonBridge.ts +139 -0
  200. package/src/services/syncService.ts +363 -0
  201. package/src/services/syncState.ts +275 -0
  202. package/src/services/wechatMessageService.ts +73 -7
  203. package/src/utils/atomicWrite.ts +56 -0
  204. package/src/utils/dateRange.ts +46 -0
  205. package/src/utils/text.ts +20 -0
package/docs/SETUP.md CHANGED
@@ -81,6 +81,26 @@ weflow-cli daily-server --date YYYY-MM-DD --open
81
81
  weflow-cli config set dailyAiEnabled false
82
82
  ```
83
83
 
84
+ 文章的主题与相关度默认可以交给 TypeSafe 的 Jev 决策模型判断(返回概率而不是一段要解析的文字)。这一步可选:**不配就沿用原来的 LLM 解析路径,行为不变。**
85
+
86
+ ```powershell
87
+ weflow-cli config set typesafeApiKey "..." # 机器绑定加密保存,和 deepseekApiKey 一样
88
+ weflow-cli config set typesafeApiKey "" # 清空即回到 LLM 解析路径
89
+ ```
90
+
91
+ 不想在日报里看到某一类(如新闻/投资/学术):
92
+
93
+ ```powershell
94
+ weflow-cli config set dailyExcludeTopics "新闻,投资,学术" # 留空即不排除
95
+ ```
96
+
97
+ **这是展示层开关,不是抓取层的**:正文照常抓取归档,只是不展示,改主意不用重抓。
98
+ 为什么不干脆不抓:拉之前可用的信息(来源+标题+摘要)判类型实测只有 60% 一致率,
99
+ 而排除不可逆。详见 OPERATIONS.md 的「不想看某一类:排除主题」。
100
+ ```
101
+
102
+ 配了它之后,文章标题与正文会同时发给 DeepSeek 和 `api.typesafe.ai` 两处(出网范围见下节)。
103
+
84
104
  日报输出和阅读器均为本地文件/回环服务。阅读器默认地址是 `http://127.0.0.1:8765/`,不要把端口暴露到局域网或公网。
85
105
 
86
106
  ## 配置与隐私
@@ -89,6 +109,8 @@ weflow-cli config set dailyAiEnabled false
89
109
 
90
110
  以下内容禁止提交:数据库、导出聊天、日报正文、`.env`、MCP 配置中的密钥、账号标识、完整日志和真实截图。MCP 客户端配置前确认其工作目录和权限;云端 AI 只处理明确选择的内容,敏感聊天优先用本地模型。
91
111
 
112
+ 日报出网的对象有两个:生成摘要的 LLM(DeepSeek 或你配置的 OpenAI 兼容端点),以及配置了 `typesafeApiKey` 之后判断文章主题与相关度的 TypeSafe Jev(`api.typesafe.ai`)。两者都会看到文章标题与正文。不需要任何出网时用 `weflow-cli daily --no-ai`。
113
+
92
114
  ## 最小验收
93
115
 
94
116
  ```powershell
@@ -0,0 +1,180 @@
1
+ # 同步与任务状态契约
2
+
3
+ 本文冻结两个内部状态 schema:`weflow-sync/v1`(跨运行的同步检查点)与 `weflow-job/v1`
4
+ (单次运行的记录)。二者都在本机 `~/.weflow-cli/` 下,**不对外暴露**。
5
+
6
+ 它们是 [FUNCTIONAL-IMPROVEMENT-SPEC.md](FUNCTIONAL-IMPROVEMENT-SPEC.md) §4 与 §5 的落地,
7
+ 边界受 [DECISIONS.md](DECISIONS.md) 约束(尤其 D-014、D-019、D-025、D-027)。
8
+
9
+ ## 为什么是两个文件
10
+
11
+ 生命周期不同,不是猜测性抽象:
12
+
13
+ | 文件 | 生命周期 | 记录的是 |
14
+ | --- | --- | --- |
15
+ | `sync/<source>-<name>-<hash>.json` | **跨运行**,每次同步覆盖 | 已覆盖到哪、分片读没读全 |
16
+ | `jobs/<jobId>.json` | **单次运行** | 这次运行的起止、状态、结果 |
17
+
18
+ 一次运行写一个 job 记录;同步检查点记录"到目前为止覆盖范围"这一长期事实。
19
+
20
+ ## `weflow-sync/v1`
21
+
22
+ 路径:`~/.weflow-cli/sync/<source>-<sanitize(talker)>-<sha256(talker)[0:8]>.json`
23
+
24
+ **按会话分文件**,不是一个全局文件。理由:全局文件要求跨会话读改写并合并,而分文件
25
+ 让每次写入都是整文件原子替换;同时文件名里的 hash 后缀让 `wxid_a@chatroom` 与
26
+ `wxid_a_chatroom` 落到不同文件,而显示名不会出现在目录列表里。
27
+
28
+ ```json
29
+ {
30
+ "schema": "weflow-sync/v1",
31
+ "schemaVersion": 1,
32
+ "source": "wechat-nt",
33
+ "scope": "<显示名,未知时等于 talker>",
34
+ "talker": "<会话标识,不是路径>",
35
+ "lastSuccessfulRun": "2026-09-19T09:00:00+08:00",
36
+ "lastAttempt": "2026-09-19T09:02:10+08:00",
37
+ "coveredFrom": "2026-09-01T00:00:00+08:00",
38
+ "coveredTo": "2026-09-19T08:59:59+08:00",
39
+ "checkpoint": { "newestCreateTime": 1789000000, "overlapSeconds": 300 },
40
+ "recordsRead": 1200,
41
+ "recordsDeduplicated": 14,
42
+ "recordsReturned": 1186,
43
+ "shardsRead": 4,
44
+ "shardsFailed": 0,
45
+ "shards": {
46
+ "scanned": 4, "opened": 4, "failed": 0,
47
+ "items": [
48
+ { "name": "message_0.db", "opened": true, "hasTalkerTable": true,
49
+ "rowsForTalker": 8123, "reason": null, "missingColumns": [] }
50
+ ]
51
+ },
52
+ "coverage": "complete",
53
+ "mayHaveMore": false,
54
+ "warnings": [],
55
+ "updatedAt": "2026-09-19T09:02:10+08:00"
56
+ }
57
+ ```
58
+
59
+ ### 两个时间戳必须分开
60
+
61
+ | 字段 | 何时推进 |
62
+ | --- | --- |
63
+ | `lastAttempt` | **每次尝试都推进**,无论结果 |
64
+ | `lastSuccessfulRun` | **仅在 `coverage !== 'partial'` 时推进** |
65
+
66
+ 只有一个的话,"跑了但部分完成"会和"跑成功了"混为一谈——而这正是方案 §5.2 明令禁止的
67
+ (部分完成不能返回成功状态)。
68
+
69
+ ### `coverage` 三档
70
+
71
+ | 值 | 含义 |
72
+ | --- | --- |
73
+ | `complete` | 扫到的分片全部打开、打开的全读成功(无 `reason`,允许有 `missingColumns`),且 `mayHaveMore` 为 false |
74
+ | `unverified` | 有分片**打不开**,但打开的都读成功。已读部分是可信的,未打开的分片是否含本会话消息**无法判断** |
75
+ | `partial` | 某个**已打开**的分片带 `reason`,或 `mayHaveMore` 为 true,或分页在到达窗口起点前就短页结束 |
76
+
77
+ `unverified` 不是失败:`connect_message_shards` 一直把打不开的分片当作可跳过的(这是有意设计),
78
+ CLI 无法知道那个分片里有没有本会话的消息。如实标成 `unverified` + `warnings` 比直接失败更有用,
79
+ 也让自动化不至于卡死。
80
+
81
+ `partial` 的判据是**已打开的分片带 reason**,而不是某个特定的 reason 值。除 `READ_FAILED`(读到一半
82
+ 出错)外还有两种同样意味着"这个分片的这段范围没读到"的情况:
83
+
84
+ | `reason` | 含义 |
85
+ | --- | --- |
86
+ | `READ_FAILED` | 已打开,读的过程中出错 |
87
+ | `SCHEMA_MISMATCH` | 已打开,但表里**没有一个**读者认得的列,这些行无法变成消息 |
88
+ | `WINDOW_UNAVAILABLE` | 已打开,但表里没有 `create_time`,请求的时间窗无法表达。**宁可拒绝也不近似**——不筛就返回会让调用方把没读的范围记成已覆盖 |
89
+
90
+ 把三者都算 `partial` 是必须的:它们描述的都是"这些行没读",而这正是 `coverage` 要回答的问题。
91
+
92
+ **`missingColumns` 是另一回事,不算 partial。** 它的行**读回来了**,只是少了字段(如缺 `server_id`),
93
+ 所以 `coverage` 仍是 `complete`,另发一条 `shard-columns-missing:<分片>:<列+列>` 警告。
94
+ 把两者混为一谈,会让这类数据库上的每次同步永远报 `partial`,`lastSuccessfulRun` 永远不推进。
95
+ 列名是 schema 不是内容,写进状态文件不违反脱敏约束。
96
+
97
+ `partial` 则**必须失败**(`code: 'SYNC_PARTIAL'`,退出码 1):状态文件照写(方案 §5.2 要求
98
+ 部分完成不得删除已生成的可用结果),但 `lastSuccessfulRun` 不推进。
99
+
100
+ ### 时间窗下推:是优化,不是正确性来源
101
+
102
+ `sync` 会把窗口下界作为**提示**传给后端(NT 走 `nt_decrypt.py messages --from`),SQL 侧因此不必
103
+ 把整段会话取回来再丢弃。但**窗口的真正执行者是 `syncService` 自己**:它在读回之后再按
104
+ `createTime >= from` 过滤一遍。这样划分的原因是**非 NT 后端没有范围下推**,若把正确性寄托在下推上,
105
+ 这些后端上的同步会静默地整段读、却报告成"按窗口读了"。
106
+
107
+ **实测(2026-09-20,本机真实数据,925 条 4 分片)**:下推带来的收益**观测不到**——
108
+ 无窗口 519 ms / `--from=最新` 514 ms,两者在噪声内。这个耗时的绝大部分是进程启动 + 每个分片的
109
+ PBKDF2(256000 轮),跟搬多少行无关。所以下推的意义是**渐近的**(会话越大越有用),
110
+ 不是当前就能测到的时间节省;这也意味着**不要**拿它当性能优化写进发布说明。
111
+
112
+ ### 身份
113
+
114
+ 按方案 §4.2,`serverId` 非零时优先;**`localId` 绝不单独作为全局身份**(D-014 同此约束)。
115
+
116
+ ```
117
+ talker|s:<serverId> serverId 非零
118
+ talker|l:<localId>|t:<createTime> 否则
119
+ ```
120
+
121
+ **与方案 §4.2 的一处有意偏离**:键里**不含 `shard`**。消息载荷里没有 shard 字段
122
+ (`nt_decrypt._message_dict` 不产出它),加进去要动 `export json` 的消息契约,而 D-016/D-027
123
+ 把那条线封住了。它对本契约要解决的**重叠去重**也不必要——同一条消息重读两次,得到的
124
+ `serverId` 或 `(localId, createTime)` 本来就一样。只有跨分片的 `(localId, createTime)` 撞车
125
+ 才需要它,而 `talker` + `createTime` 已让这种撞车极其遥远。**真观测到再加,不预先加。**
126
+
127
+ ### 明确不做的事
128
+
129
+ **`mayHaveMore` 不是稳定游标。** 按 D-027,稳定增量游标必须在所有相关后端都支持后才能
130
+ 对外宣称;在此之前只提供重叠时间窗 + 本地去重,由下游决定重复容忍度。
131
+
132
+ **不得写入**:数据库密钥、`x'<key><salt>'`、聊天正文、绝对路径(D-001 / D-002 / D-025)。
133
+ 状态服务在写盘前统一过一遍脱敏,不指望每个调用方都记得。
134
+
135
+ ## `weflow-job/v1`
136
+
137
+ 路径:`~/.weflow-cli/jobs/<jobId>.json`。`jobId` 形如 `job_20260919_0001`。
138
+
139
+ ```json
140
+ {
141
+ "schema": "weflow-job/v1",
142
+ "jobId": "job_20260919_0001",
143
+ "kind": "sync.messages",
144
+ "state": "running",
145
+ "createdAt": "2026-09-19T09:00:00+08:00",
146
+ "updatedAt": "2026-09-19T09:02:10+08:00",
147
+ "window": { "from": 1788000000, "to": 1789000000 },
148
+ "progress": { "completed": 800, "total": 1200 },
149
+ "resumeable": true,
150
+ "result": { "records": 800, "recordsDeduplicated": 14, "shardsFailed": 0 },
151
+ "error": null
152
+ }
153
+ ```
154
+
155
+ 状态机:`planned → waiting_confirmation → running → paused`,终态 `completed | partial | failed | cancelled`。
156
+ 本轮只实现 `running | completed | partial | failed`,其余是预留(不实现的不写进状态机文档当既有能力)。
157
+
158
+ `partial` 是本项目新增的一档,**不算成功**。`error` 只放既有的大写错误码
159
+ (`NOT_INITIALIZED`、`CONFIRMATION_REQUIRED`、`INVALID_ARGUMENT`、`SYNC_PARTIAL`、
160
+ `DATABASE_CONNECTION_FAILED` 等),不写中文长句供机器解析。
161
+
162
+ **幂等**:同一范围重复提交应返回已有任务或明确新建,不得静默重复写入。本轮用
163
+ `kind + talker + 窗口` 生成幂等提示,命中未完成的同键任务时返回该任务而非新建。
164
+
165
+ > 本轮 `sync` 是 job 记录的唯一写入者。若日后认为不值得单独一个文件,
166
+ > **回退路径**:删掉 job 存储、把那个 `lastJob` 对象并入各会话的 `weflow-sync/v1` 文件。
167
+ > 因为此刻还没有第二个消费者,这是一次单文件改动,无兼容性影响。
168
+
169
+ ## 版本升级策略
170
+
171
+ - `schema` 变化 = 不兼容变更,需要迁移说明。
172
+ - 只增字段 = 兼容变更;读方必须容忍未知字段(与 `DATA_CONTRACT.md` 对下游的要求一致)。
173
+ - 读到未知 `schema` 时**不得覆盖**:报明确错误并保留原文件。
174
+
175
+ ## 相关文档
176
+
177
+ - [功能增强技术方案](FUNCTIONAL-IMPROVEMENT-SPEC.md)
178
+ - [消息数据契约](DATA_CONTRACT.md)
179
+ - [技术决策记录](DECISIONS.md)
180
+ - [项目当前状态](PROJECT_STATE.md)