weflow-cli 1.8.0 → 1.8.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 (41) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/OPERATIONS.md +42 -6
  3. package/README.en.md +3 -3
  4. package/README.md +3 -3
  5. package/bin/weflow-cli.ts +3 -1
  6. package/dist/bin/weflow-cli.js +3 -1
  7. package/dist/bin/weflow-cli.js.map +1 -1
  8. package/dist/src/panel/server.js +1 -1
  9. package/dist/src/panel/server.js.map +1 -1
  10. package/dist/src/services/assistantPrivacy.d.ts.map +1 -1
  11. package/dist/src/services/assistantPrivacy.js +7 -2
  12. package/dist/src/services/assistantPrivacy.js.map +1 -1
  13. package/dist/src/services/assistantService.d.ts.map +1 -1
  14. package/dist/src/services/assistantService.js +10 -1
  15. package/dist/src/services/assistantService.js.map +1 -1
  16. package/dist/src/services/assistantTools.d.ts +12 -0
  17. package/dist/src/services/assistantTools.d.ts.map +1 -1
  18. package/dist/src/services/assistantTools.js +29 -3
  19. package/dist/src/services/assistantTools.js.map +1 -1
  20. package/dist/src/services/wereadService.d.ts +33 -6
  21. package/dist/src/services/wereadService.d.ts.map +1 -1
  22. package/dist/src/services/wereadService.js +27 -2
  23. package/dist/src/services/wereadService.js.map +1 -1
  24. package/docs/BRANCHES.md +13 -4
  25. package/docs/PROJECT_STATE.md +299 -299
  26. package/package.json +1 -1
  27. package/resources/panel/main.cjs +43 -7
  28. package/resources/panel/mascot-thinking.png +0 -0
  29. package/resources/panel/panel.css +415 -407
  30. package/resources/panel/preload.cjs +79 -72
  31. package/resources/panel/renderer.js +470 -441
  32. package/scripts/_utils.py +23 -1
  33. package/scripts/backfill_articles.py +438 -438
  34. package/scripts/compile_wiki.py +18 -17
  35. package/scripts/wiki_lint.py +429 -397
  36. package/src/panel/server.ts +424 -424
  37. package/src/services/assistantPrivacy.ts +7 -2
  38. package/src/services/assistantService.ts +11 -1
  39. package/src/services/assistantTools.ts +33 -3
  40. package/src/services/wereadService.ts +43 -7
  41. package/resources/panel/mascot-focus.png +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,164 @@ The npm package is published separately from GitHub. It may lag behind the `mast
4
4
 
5
5
  All notable user-facing changes are recorded here. This project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## 1.8.1
8
+
9
+ ### Changed
10
+
11
+ - **The floating ball's "busy" face is now a thinking face, not a contemplative one.** `busy` is
12
+ exactly "a request went out and we are waiting" (`renderer.js`'s `ask()` sets it on entry), so the
13
+ face is drawn as thought: eyes glancing up-left, level brows, a flat mouth line, a paw under the
14
+ chin. The file was renamed `mascot-focus.png` -> `mascot-thinking.png` rather than overwritten -
15
+ a file named `focus` holding a thinking face is the next misreading - and all four places that
16
+ name it were updated together (CSS, the static whitelist, the packaging list, the state-to-file
17
+ assertion). The two reconciliation tests in `panel-packaging.test.ts` went red when the wiring
18
+ landed before the image did, which is how we know they actually watch that gap.
19
+
20
+ It is generated with `gpt-image-2-ca` through `/images/edits`, using the existing mascot as the
21
+ base so only the expression changes - a text-only redraw takes the style and framing with it. The
22
+ first prompt asked for "thinking" and produced **worry** (downturned mouth, lowered brows), which
23
+ collided with the offline face; the second spells out "level brows, flat mouth line, not sad, not
24
+ worried". Geometry is checked rather than eyeballed, because the ball clips to a circle: content
25
+ box 171x212 (the existing faces are 170x214), farthest solid pixel 125.2 against a 125.4 limit,
26
+ and **0 solid pixels outside the inscribed circle**.
27
+
28
+ ### Fixed
29
+
30
+ - **The panel ball went blank after the assistant restarted, and it now repairs itself.** The
31
+ daemon generates a fresh token on every start, and the window's cookie is planted when it opens -
32
+ so a window that outlives a restart holds dead credentials. Every `/panel/*` request then answers
33
+ 401, and because those responses carry `Cache-Control: no-store` the ball's face (`panel.css`
34
+ loads it as a background image) fails to load: **a blank ball, with the window otherwise looking
35
+ normal**. Measured: the window had been open 25 hours against a daemon 25 hours younger, which is
36
+ how this was found - the user reported "my agent icon is gone".
37
+
38
+ The window can now recover by itself. Its status poll already distinguishes the case, and on
39
+ `UNAUTHORIZED` it asks the main process to re-read the endpoint file, refresh the cookie and
40
+ reload. The credentials still never reach the renderer - the main process does the reading, which
41
+ is where they belong; the preload's exposed surface grows by exactly one method and
42
+ `test/panel-packaging.test.ts` pins that list, so widening it stays a deliberate act. Two guards
43
+ keep the cure from being worse than the disease: **the reload only happens when the file's token
44
+ actually differs** from the cookie (otherwise an unrelated 401 would reload forever), and there is
45
+ a five-second floor plus a three-attempt cap in the page, because **a reload storm is harder to
46
+ diagnose than a blank ball**. Verified end to end by restarting the daemon and watching the
47
+ window reload on its own: the log's load counter went 73 → 74 with nobody touching the window.
48
+
49
+ - **The assistant denied that unconfigured capabilities existed.** A tool whose prerequisite key is
50
+ missing is deliberately left out of the tool table - a tool that cannot run makes the model try it
51
+ and apologise with errors in the answer. But the model then has no way to know it exists, so
52
+ asking "what am I reading" produced **"I don't have a get_weread tool"**. That is true about the
53
+ tool table and false about the product: the feature was built, it just was not configured, and the
54
+ user reasonably concluded it had never been built. The system prompt now carries a
55
+ `[本机没启用的能力]` line naming each missing prerequisite and telling the model to answer "this
56
+ part is not configured" with the setting to change, **not** "I don't have that tool". The tools
57
+ stay unavailable - this only stops the model from denying the feature exists. Verified by asking:
58
+ the answer is now "can't do it, the only reason is dashscopeApiKey isn't configured".
59
+
60
+ - **`get_reading_stats`'s description claimed a question it cannot answer.** It said to use it for
61
+ "what have I been reading lately" - which is exactly the WeRead question, so with `get_weread`
62
+ hidden the model reached for this tool and returned public-account push counts ("the most active
63
+ account is 红星新闻"). The tool was right; the sentence had taken over another capability's
64
+ question space. It now says what it covers (public-account pushes and daily-report processing) and
65
+ that it carries no reading data.
66
+
67
+ - **`weflow-cli weread ...` read the key from a different place than the assistant did, so
68
+ configuring it satisfied one and not the other.** The CLI read `WEREAD_API_KEY` from the
69
+ environment (which is what WeRead's own setup page tells you to export); the assistant's
70
+ `get_weread` availability check reads the **config key** `wereadApiKey`. Configure the config key -
71
+ as the CLI's own error message invites you to - and `weread shelf` still answers "not configured".
72
+ Both are accepted now, environment variable first because the official guidance is not to hand the
73
+ key to an AI. The resolution is a pure exported function with a test, rather than a line inside the
74
+ CLI's closure where the last change to it could not be tested.
75
+
76
+ The same session turned up two smaller things. **A tool hidden for a missing key is invisible to
77
+ the model, so the user gets "I don't have that tool"** - which is what happened here: the answer
78
+ said the capability did not exist when it did, unconfigured. That is now written into
79
+ `OPERATIONS.md` next to the filtering rule it belongs to, because "no key" and "no such feature"
80
+ read the same from the outside. And **`wereadService`'s header pointed at
81
+ `~/.claude/skills/weread-skills/`, which does not exist on this machine** (checked, 2026-09-28);
82
+ it now points at `https://weread.qq.com/r/weread-skills`, the page that actually issues the key -
83
+ the key's origin had been recorded nowhere in the repo, which is why finding it took reading a
84
+ stored article from May. `OPERATIONS.md` gained the setup steps, including that **`configService`
85
+ reads the file once at construction, so a running assistant needs a restart** to see a new key.
86
+
87
+ - **Fixing the CLI was not enough, and only an end-to-end run showed that.** With the key configured
88
+ and the assistant restarted, asking "what am I reading" still produced "WEREAD_API_KEY is not set".
89
+ The assistant tool was reading the secret a **third** way: its availability check loads the config
90
+ key - which is why the tool appeared in the table at all - and then calls the module singleton
91
+ `new WereadService()`, whose constructor read only the environment variable. A tool that is visible
92
+ but cannot run is the worst of both designs. The no-argument constructor now resolves both sources
93
+ through the same `resolveWereadApiKey`, which fixes every user of the singleton at once.
94
+
95
+ - **`get_weread`'s notebooks mode printed `undefined` for every book title.** `/user/notebooks` nests
96
+ the title and author inside `item.book`; the item itself carries only `bookId`, `noteCount`,
97
+ `bookmarkCount` and friends. The code read `item.title`, so the output was still well-formed - it
98
+ just had no names in it, which is why nothing failed. Verified against the gateway's real response
99
+ and pinned with a test that stubs **that** shape: the previous test for this branch stubbed an
100
+ empty list, so it could not have caught it.
101
+
102
+ All three of these were found in one session by configuring the key and then actually asking the
103
+ assistant a question, not by reading the code. Two of the three were invisible from any single
104
+ file: the secret had three readers and they disagreed.
105
+
106
+ - **`wiki lint`'s duplicate-title report was rebuilt rather than re-tuned, and the two tiers now
107
+ differ by an order of magnitude in how much you should trust them.** The old rule - a common
108
+ substring of ≥5 characters covering ≥40% of the shorter title - was reasonable at 5,062 pages of
109
+ long Chinese headlines and had become useless at 22,374: it reported **424,697 groups**, starting
110
+ with coincidences like `AI Agent开发框架` against `生物信息学LLM Agent综述` (both contain "Agent").
111
+ A 20-pair sample showed the rule firing correctly on every pair, which is the point: the threshold
112
+ had stopped discriminating, so the implementation was never the problem.
113
+
114
+ Two directions were measured and rejected before settling. **Keeping the long-common-substring
115
+ rule** fails because `Claude Code 上下文窗口` and `Claude Code 联网能力` share six characters and are
116
+ nonetheless different concepts - sharing a product name is not the same as being the same thing,
117
+ and that class dominated the false positives, so no ratio value separates them. **Containment
118
+ without a guard** fails because `DeepSeek` is swallowed by `DeepSeek 融资`, `DeepSeek-V4 涨价` and so
119
+ on; that is a *hub*, not a duplicate (measured: `agent` is contained in 731 names, `模型` in 643).
120
+
121
+ What it reports now: **314 groups that differ only in spacing, hyphens, case or an English plural**
122
+ (`GLM 5.1` / `GLM-5.1`, `AI 编程` / `AI编程`), scored by hand at 24 of 24 being the same node - and
123
+ these use `_utils.normalize_concept_name`, the **same function** `wiki compile --merge-duplicates`
124
+ uses, so the list is directly executable. Measured on the real vault, every group the lint reports
125
+ is one that tool would merge (the extra 14 groups it would act on are same-filename collisions,
126
+ which the lint reports separately). Plus **379 groups where one name is the other plus a qualifier**,
127
+ explicitly labelled a lead rather than a conclusion because hand-sampling puts its precision near
128
+ half - the other half being "hub vs its own subtopic" (`国家自然科学基金` /
129
+ `国家自然科学基金申请书`), which should not be merged.
130
+
131
+ The normaliser moved to `_utils` so the two readers cannot drift apart, and a test now pins the
132
+ **function object** rather than "two copies that look alike". The old caveat that the rule could not
133
+ catch the case it was written for still stands and is still written down: `打虎!陈勇被查` and
134
+ `中建集团副总经理陈勇被查` are the same event with different headlines and share only four characters,
135
+ so no substring rule reaches them - the tier-1 rule replaces "wrong and noisy" with "narrow and
136
+ right", not with "complete".
137
+
138
+ Two silent-failure traps were caught by mutation testing rather than by reading. **The bucketing
139
+ width must stay at or below the shortest shared substring the rule can accept**, or the report
140
+ quietly loses pairs; the first version of the equivalence test read the module constant for both, so
141
+ changing the bucketing width moved the reference implementation with it and the test stayed green -
142
+ it now uses literal contract values. And **the hub filter's count is computed from the same 8-gram
143
+ index** rather than an O(n²) scan, which keeps the whole pass at 0.1s.
144
+
145
+ - **The two tiers compare within a directory, not across the whole vault.** The two concept
146
+ directories are two knowledge bases and `--merge-duplicates` runs per directory, so a group spanning
147
+ them can never be acted on by the tool the report points at. The first version compared across both
148
+ and, after the merge, was still listing `AI 工具` and `GLORIA` as "groups that tool will merge" -
149
+ which was simply false. Cross-line same names are a separate report (`duplicateTitles`).
150
+
151
+ - **`--merge-duplicates` was silently dropping card names from the surviving page's `sources:`.** It
152
+ carried that key over from the main page only, so every merged group lost the absorbed page's
153
+ entries - measured on the real vault, **all 315 groups** lost at least one. The body's source lines
154
+ were always correct (those were unioned); only the frontmatter side was short, and that key is what
155
+ `--relabel` feeds to `source_kinds_for`, so a page could lose a `来源/文章|聊天|收藏` tag. It now
156
+ unions. The 315 pages were repaired from a pre-merge backup, and the `output/` vault is not under
157
+ version control, which is why that backup existed. **The loss turned out to have changed no tag**:
158
+ 0 of 22,033 pages carry sources from more than one line, so this was a latent defect rather than a
159
+ live one - recorded that way rather than as a rescue.
160
+
161
+ - **Merging was run on the 314 same-node groups**: 341 pages absorbed (338 article-line, 3 chat-line)
162
+ out of 22,374, leaving 0 groups in that tier. `wiki lint` still reports **0 broken links** - the
163
+ `aliases` written by the merge are what make that true, and it is why no other file had to change.
164
+
7
165
  ## 1.8.0
8
166
 
9
167
  ### Added
@@ -1974,7 +2132,7 @@ That is the useful form of this result: a decision model proposed 741 merges, a
1974
2132
 
1975
2133
  ### Fixed
1976
2134
 
1977
- - Restored four pieces of matching logic in the HTML exporter that the `90b165a` clone-consolidation merge had silently dropped, all of which lowered media coverage without failing loudly:
2135
+ - Restored four pieces of matching logic in the HTML exporter that the `2d368c6` clone-consolidation merge had silently dropped, all of which lowered media coverage without failing loudly:
1978
2136
  - the `unique:<local_id>` fallback, which matches an image when that id resolves to exactly one distinct picture in the conversation (content-deduped, so the shard-collision risk that rules out a bare `local_id` does not apply);
1979
2137
  - `is_encoded_media_type`, needed because WeChat stores some forwards as high-bit variants of type 49 — the mask turns those into a plain 49, which is a registered type, so the branch guarding them had become unreachable;
1980
2138
  - the guard that keeps a type-49 row carrying a title or url on the link-card path instead of hiding it behind a cached thumbnail;
package/OPERATIONS.md CHANGED
@@ -519,12 +519,19 @@ weflow-cli wiki lint --json # 机器可读
519
519
  - **同名 / 大小写同名**:同一个名字有多张页,或名字只差大小写——Windows 上后者是同一个文件;
520
520
  - **退化字段**:整列几乎同一个值(实测 `topic: AI` 占 18,474/22,374 页)——那个字段等于没携带
521
521
  信息,**别照着信**;
522
- - **近似重名**:**这条不能当清单用**。判据是"公共子串 ≥5 字且占较短标题 ≥40%",是 5,062 张页的
523
- 规模上定的;现在 22,374 张页里短名字很多,它报出 **424,697 组**,前几条是
524
- `AI Agent开发框架` / `生物信息学LLM Agent综述` 这类共享 "Agent" 的巧合。抽 20 组复核判据本身
525
- 都成立(没有假阳性),所以是**门槛失去了分辨力**,不是实现坏了。要按它去重的话,先把门槛收紧
526
- (`scripts/wiki_lint.py` 的 `near_duplicate_titles` 里写了两条已知失效,包括它抓不住自己当初的
527
- 动机用例)。
522
+ - **同一个节点的不同写法**(2026-09-28 起):规范化之后同名,也就是只差空格/连字符/大小写/
523
+ 英文复数尾。判据与 `wiki compile --merge-duplicates` 是**同一份实现**(`_utils.normalize_concept_name`),
524
+ 所以这份名单是**可以直接执行的**。22,374 张页时实测 314 组;同日按这份名单合并掉 **341 张页**
525
+ (文章线 338、聊天线 3),现在剩 **0 组**。合并后 `wiki lint` 的断链仍是 0——`aliases` 把指向
526
+ 旧名字的链接全接住了。**按目录跑**(两个目录是两个知识库),跨线同名由下一节报;
527
+ - **一个名字是另一个加了限定**(同日新增):实测 378 组,**这条是线索不是结论**——人工抽样大约
528
+ 一半是真重复(`Agent Harness` / `Agent Harness研讨会`),另一半是"主概念 vs 它的子话题"
529
+ (`国家自然科学基金` / `国家自然科学基金申请书`),那些**不该合并**。判据里专门挡了"枢纽"
530
+ (短名字已被 ≥5 个别名包含,如 `agent` 被 731 个名字包含),但挡不干净,删哪张由人定。
531
+
532
+ 这两档取代了旧的"标题高度相似"判据:那条(公共子串 ≥5 字且占较短标题 ≥40%)在 5,062 张页
533
+ 时合理,到 22,374 张页时报出 **424,697 组**、完全失去分辨力。换判据的实测记录在
534
+ `near_duplicate_titles` 的 docstring 里(包括被否掉的另外两个方向)。
528
535
 
529
536
  **两条线都是默认增量**:`article-notes` 只处理还没卡的(原笔记改了才重做,`--refresh` 强制重做),
530
537
  `chat-notes` 的卡是滚动快照(本来就该覆盖)。所以"补新的"不会把旧的再问一遍。
@@ -964,6 +971,35 @@ weflow-cli config set assistantFastRoute off # 关(默认)
964
971
  缺了就不出现在工具表里——摆一个跑不了的工具,模型会去试、拿一个错回来(实测出现过答复里带着
965
972
  "两个检索工具都跑不通")。这只挡"跑不了",不挡"暂时没数据":后者工具自己会说该运行什么。
966
973
 
974
+ **副作用要知道**:工具被藏起来之后,模型**看不见它存在**,所以问"你在读什么书"时它只会答
975
+ "我没有这个工具"。2026-09-28 实测就是这么一轮对话——用户以为功能没做,其实做了、只是没配 key。
976
+ 所以缺 key 时它给的那句"没有"要按"没配",不是"没有这功能"来读。
977
+
978
+ ### 微信读书的 key 从哪来
979
+
980
+ 登录 **https://weread.qq.com/r/weread-skills**,点「快速配置」,页面上会给出你的 `api-key`
981
+ (形如 `wrk-…`)。它调的是官方网关 `https://i.weread.qq.com/api/agent/gateway`。
982
+
983
+ 拿到之后两种存法**都认**:
984
+
985
+ ```powershell
986
+ weflow-cli config set-env wereadApiKey WEREAD_API_KEY # 从环境变量读,秘密不进命令参数
987
+ weflow-cli config set wereadApiKey wrk-xxxx # 或者直接给值
988
+ ```
989
+
990
+ - 本机的配置项是**密文落盘**(`lock:` 开头),与其它密钥一样;
991
+ - 环境变量 `WEREAD_API_KEY` **优先于**配置项——官方文档建议不要把 key 直接交给 AI;
992
+ - 两条路都认是有原因的:助手的 `get_weread` 判据读**配置项**,而 `weflow-cli weread …` 早先
993
+ 只读**环境变量**。2026-09-28 之前只认一个,用户按 CLI 的提示配好配置项,助手能用了、
994
+ CLI 却回"未设置"。
995
+
996
+ **改了配置要重启守护进程**:`configService` 在构造时读一次盘,跑着的助手不会自己发现新 key。
997
+
998
+ ```powershell
999
+ weflow-cli assistant stop
1000
+ weflow-cli assistant start
1001
+ ```
1002
+
967
1003
  ### 看助手"刚才怎么想的"(决策轨迹)
968
1004
 
969
1005
  助手对外只吐一句答复,中间的判断过程默认看不见。要看得两条路:
package/README.en.md CHANGED
@@ -245,7 +245,7 @@ Then just talk to ClawBot on your phone. The assistant queries local chats and f
245
245
  - "Draft a reply to Lao Wang" — first judges what the other side wants, the risk, and whether substance should be given, then offers a few candidates. **Text only, never sent for you**; when money or high risk is involved it withholds drafts and only says what to confirm first
246
246
  - "Remember: my project is called weflow-cli" — writes to long-term memory
247
247
 
248
- How it works: the daemon long-polls WeChat's official bot channel (iLink); the agent loop, three-tier memory (working window / rolling summary / long-term facts), and all database queries run on your machine. Only the final question and reply texts go to the configured LLM; phone numbers, emails, and links in tool output are redacted by default (`config set assistantPrivacy strict` for stronger masking; a local `ollama` engine keeps everything offline). Sessions expire after 24h of inactivity, and proactive replies per window are capped by official limits.
248
+ How it works: the daemon long-polls WeChat's official bot channel (iLink); the agent loop, three-tier memory (working window / rolling summary / long-term facts), and all database queries run on your machine. Only the final question and reply texts go to the configured LLM; the shipped default is `assistantPrivacy=strict`: phone numbers, emails, and links in tool output are redacted, and third-party chat bodies never leave the machine (`balanced` lets bodies out but still masks PII, `open` masks nothing; a local `ollama` engine keeps everything offline). Sessions expire after 24h of inactivity, and proactive replies per window are capped by official limits.
249
249
 
250
250
  Cost guardrails: a built-in daily cap of 100 processed messages (send "记忆" in WeChat to check usage). The assistant denies all incoming users by default; configure an explicit whitelist before AI calls are enabled:
251
251
 
@@ -279,7 +279,7 @@ Management: `weflow-cli assistant status` / `log` / `stop`; send "帮助" in WeC
279
279
  | Digest & reader | `weflow-cli daily` · `weflow-cli daily-server` · `weflow-cli review` |
280
280
  | Moments cache | `weflow-cli sns timeline` · `weflow-cli sns users` · `weflow-cli sns stats` |
281
281
  | WeChat favorites | `weflow-cli fav list` · `weflow-cli fav export markdown` · `weflow-cli fav set-key` |
282
- | WeRead | `weflow-cli weread shelf` · `notes` · `search` · `stats` |
282
+ | WeRead | `weflow-cli weread shelf` · `notes` · `search` · `stats` (get the key from https://weread.qq.com/r/weread-skills; see [OPERATIONS.md](./OPERATIONS.md)) |
283
283
  | Knowledge base | `weflow-cli vault` · `weflow-cli wiki` · `weflow-cli search <query>` · `weflow-cli chat` (semantic search needs `config set dashscopeApiKey` first) |
284
284
  | Reports & tasks | `weflow-cli report` · `annual-report` · `todos` |
285
285
  | Second-brain assistant | `weflow-cli assistant start` · `status` · `log` · `stop` |
@@ -320,7 +320,7 @@ python scripts/fav_server.py --date YYYY-MM-DD
320
320
 
321
321
  ![WeFlow CLI architecture](./docs/images/weflow-architecture-gpt-image-2.png)
322
322
 
323
- The project is split into four clearly bounded parts:
323
+ The project is split into five clearly bounded parts:
324
324
 
325
325
  | Directory | Responsibility |
326
326
  | --- | --- |
package/README.md CHANGED
@@ -239,7 +239,7 @@ weflow-cli panel # 打开本机面板窗口 (不扫码也能用)
239
239
  **只产出文本,不会替你发送**;涉钱或风险很高时不给草稿,只说明该先确认什么
240
240
  - 「记住:我的项目叫 weflow-cli」— 写入长期记忆
241
241
 
242
- 运行机制:守护进程通过微信官方 Bot 通道(iLink)长轮询收发消息,Agent 循环、三层记忆(工作窗口 / 滚动摘要 / 长期事实)、数据库查询全部在本机执行;仅最终提问与回复文本会发送给所配置的 LLM,工具输出中的电话/邮箱/链接等 PII 默认自动脱敏(`config set assistantPrivacy strict` 可加强,`ollama` 本地引擎则完全不出网)。会话 24 小时未活跃需重新扫码,单窗口内主动回复有官方条数限制。
242
+ 运行机制:守护进程通过微信官方 Bot 通道(iLink)长轮询收发消息,Agent 循环、三层记忆(工作窗口 / 滚动摘要 / 长期事实)、数据库查询全部在本机执行;仅最终提问与回复文本会发送给所配置的 LLM,新装默认就是 `assistantPrivacy=strict`:工具输出中的电话/邮箱/链接等 PII 自动脱敏,且第三方聊天正文不出境(`balanced` 放宽到正文可出境、只对 PII 打码,`open` 全不打码;`ollama` 本地引擎则完全不出网)。会话 24 小时未活跃需重新扫码,单窗口内主动回复有官方条数限制。
243
243
 
244
244
  成本护栏:内置每日 100 条处理上限(防异常流量烧钱,微信内发「记忆」可查用量);助手默认拒绝所有来信,必须明确配置白名单后才会触发 AI 调用:
245
245
 
@@ -273,7 +273,7 @@ weflow-cli config set assistantGroupRequireMention true
273
273
  | 公众号日报与阅读器 | `weflow-cli daily` · `weflow-cli daily-server` · `weflow-cli review` |
274
274
  | 朋友圈缓存 | `weflow-cli sns timeline` · `weflow-cli sns users` · `weflow-cli sns stats` |
275
275
  | 微信收藏 | `weflow-cli fav list` · `weflow-cli fav export markdown` · `weflow-cli fav set-key` |
276
- | 微信读书 | `weflow-cli weread shelf` · `notes` · `search` · `stats` |
276
+ | 微信读书 | `weflow-cli weread shelf` · `notes` · `search` · `stats`(key 从 https://weread.qq.com/r/weread-skills 拿,见 [OPERATIONS.md](./OPERATIONS.md)) |
277
277
  | 知识库 | `weflow-cli vault` · `weflow-cli wiki` · `weflow-cli search <query>` · `weflow-cli chat`(语义检索需先 `config set dashscopeApiKey`)|
278
278
  | 总结与任务 | `weflow-cli report` · `annual-report` · `todos` |
279
279
  | 第二大脑助手 | `weflow-cli assistant start` · `status` · `log` · `stop` |
@@ -314,7 +314,7 @@ python scripts/fav_server.py --date YYYY-MM-DD
314
314
 
315
315
  ![WeFlow CLI architecture](./docs/images/weflow-architecture-gpt-image-2.png)
316
316
 
317
- 项目分为四个边界清晰的部分:
317
+ 项目分为五个边界清晰的部分:
318
318
 
319
319
  | 目录 | 职责 |
320
320
  | --- | --- |
package/bin/weflow-cli.ts CHANGED
@@ -4781,8 +4781,10 @@ program
4781
4781
  }
4782
4782
 
4783
4783
  async function getWereadService() {
4784
- const key = process.env.WEREAD_API_KEY || ''
4784
+ // key 的两个来源与优先级见 `resolveWereadApiKey`(抽出去是为了能被测到 ——
4785
+ // 只读环境变量那版害得用户配好配置项之后 `weread shelf` 仍然报"未设置")。
4785
4786
  const mod = await import('../src/services/wereadService.js')
4787
+ const key = mod.resolveWereadApiKey(process.env.WEREAD_API_KEY, configService.get('wereadApiKey'))
4786
4788
  return new mod.WereadService(key)
4787
4789
  }
4788
4790
 
@@ -5008,8 +5008,10 @@ async function runPythonCmd(script, args, apiKey, apiKeyVariable = 'DEEPSEEK_API
5008
5008
  }
5009
5009
  }
5010
5010
  async function getWereadService() {
5011
- const key = process.env.WEREAD_API_KEY || '';
5011
+ // key 的两个来源与优先级见 `resolveWereadApiKey`(抽出去是为了能被测到 ——
5012
+ // 只读环境变量那版害得用户配好配置项之后 `weread shelf` 仍然报"未设置")。
5012
5013
  const mod = await import('../src/services/wereadService.js');
5014
+ const key = mod.resolveWereadApiKey(process.env.WEREAD_API_KEY, configService.get('wereadApiKey'));
5013
5015
  return new mod.WereadService(key);
5014
5016
  }
5015
5017
  // semantic-search