weflow-cli 1.9.0 → 1.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.
- package/ARCHITECTURE.md +62 -26
- package/CHANGELOG.md +1051 -0
- package/OPERATIONS.md +99 -13
- package/README.en.md +14 -5
- package/README.md +23 -8
- package/bin/weflow-cli.ts +364 -40
- package/dist/bin/weflow-cli.js +381 -69
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +19 -1
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/dbPathService.d.ts +12 -0
- package/dist/src/core/dbPathService.d.ts.map +1 -1
- package/dist/src/core/dbPathService.js +20 -3
- package/dist/src/core/dbPathService.js.map +1 -1
- package/dist/src/panel/server.d.ts.map +1 -1
- package/dist/src/panel/server.js +35 -0
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/services/assistantService.d.ts +19 -0
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +20 -0
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +27 -1
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +169 -35
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/configService.d.ts +2 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +3 -2
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/exportService.d.ts +43 -0
- package/dist/src/services/exportService.d.ts.map +1 -1
- package/dist/src/services/exportService.js +116 -11
- package/dist/src/services/exportService.js.map +1 -1
- package/dist/src/services/messageQuery.d.ts +18 -0
- package/dist/src/services/messageQuery.d.ts.map +1 -1
- package/dist/src/services/messageQuery.js +29 -0
- package/dist/src/services/messageQuery.js.map +1 -1
- package/dist/src/services/wechatClient.d.ts +125 -0
- package/dist/src/services/wechatClient.d.ts.map +1 -0
- package/dist/src/services/wechatClient.js +344 -0
- package/dist/src/services/wechatClient.js.map +1 -0
- package/dist/src/services/wechatMedia.d.ts +44 -0
- package/dist/src/services/wechatMedia.d.ts.map +1 -0
- package/dist/src/services/wechatMedia.js +155 -0
- package/dist/src/services/wechatMedia.js.map +1 -0
- package/dist/src/services/wechatMessageService.d.ts +11 -0
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +55 -11
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/types.d.ts +4 -0
- package/dist/src/types.d.ts.map +1 -1
- package/docs/CONTACT_DB_SCHEMA.md +1085 -0
- package/docs/DECISIONS.md +1274 -0
- package/docs/EXTENDING.md +69 -7
- package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +27 -0
- package/docs/HEALTH-CHECK.md +12 -7
- package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +2 -2
- package/docs/MCP.md +2 -2
- package/docs/PROJECT_STATE.md +64 -5
- package/docs/RELEASING.md +128 -1
- package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
- package/logo.png +0 -0
- package/mcp-server/index.ts +18 -3
- package/package.json +5 -3
- package/resources/js/graph3d/NOTICE.txt +35 -0
- package/resources/js/graph3d/d3-binarytree.min.js +2 -0
- package/resources/js/graph3d/d3-dispatch.min.js +2 -0
- package/resources/js/graph3d/d3-force-3d.min.js +2 -0
- package/resources/js/graph3d/d3-octree.min.js +2 -0
- package/resources/js/graph3d/d3-quadtree.min.js +2 -0
- package/resources/js/graph3d/d3-timer.min.js +2 -0
- package/resources/js/graph3d/layout.mjs +63 -0
- package/resources/js/graph3d/three.min.js +6 -0
- package/resources/js/graph3d/viewer2d.js +448 -0
- package/resources/panel/index.html +12 -0
- package/resources/panel/mascot-blink-1.png +0 -0
- package/resources/panel/mascot-blink-2.png +0 -0
- package/resources/panel/mascot-lift-down-1.png +0 -0
- package/resources/panel/mascot-lift-down-2.png +0 -0
- package/resources/panel/mascot-lift-down-3.png +0 -0
- package/resources/panel/mascot-lift-down-4.png +0 -0
- package/resources/panel/mascot-lift-down-5.png +0 -0
- package/resources/panel/mascot-lift-down-6.png +0 -0
- package/resources/panel/mascot-lift-up-1.png +0 -0
- package/resources/panel/mascot-lift-up-2.png +0 -0
- package/resources/panel/mascot-lift-up-3.png +0 -0
- package/resources/panel/mascot-lift-up-4.png +0 -0
- package/resources/panel/mascot-lift-up-5.png +0 -0
- package/resources/panel/mascot-lift-up-6.png +0 -0
- package/resources/panel/mascot-tickle-1.png +0 -0
- package/resources/panel/mascot-tickle-2.png +0 -0
- package/resources/panel/mascot-tickle-3.png +0 -0
- package/resources/panel/mascot-tickle-4.png +0 -0
- package/resources/panel/panel.css +111 -0
- package/resources/panel/renderer.js +480 -8
- package/scripts/_utils.py +1203 -977
- package/scripts/biz_daily.py +25 -10
- package/scripts/chat_notes.py +15 -5
- package/scripts/check_readme_commands.py +426 -0
- package/scripts/compile_wiki.py +163 -57
- package/scripts/contact_schema.py +339 -0
- package/scripts/data/wechat_emoji.json +225 -0
- package/scripts/export_chat_html.py +346 -6
- package/scripts/fav_notes.py +4 -1
- package/scripts/graph_2d.py +151 -0
- package/scripts/graph_3d.py +646 -0
- package/scripts/health_check.py +163 -26
- package/scripts/nt_decrypt.py +3 -3
- package/scripts/panel_frames.py +683 -0
- package/scripts/pipeline.py +21 -10
- package/scripts/quality_eval.py +5 -0
- package/scripts/rag_chat.py +33 -16
- package/scripts/reply_debt.py +26 -6
- package/scripts/semantic_search.py +212 -58
- package/scripts/vault_rag.py +42 -16
- package/scripts/vault_search.py +44 -8
- package/scripts/wechat_emoticon.py +6 -0
- package/scripts/wiki_lint.py +94 -17
- package/src/core/dbPathService.ts +22 -4
- package/src/panel/server.ts +33 -0
- package/src/services/assistantService.ts +26 -0
- package/src/services/assistantTools.ts +2452 -2319
- package/src/services/configService.ts +5 -2
- package/src/services/exportService.ts +129 -12
- package/src/services/messageQuery.ts +29 -0
- package/src/services/wechatClient.ts +380 -0
- package/src/services/wechatMedia.ts +166 -0
- package/src/services/wechatMessageService.ts +62 -11
- package/src/types.ts +4 -0
- package/dist/src/utils/wechatEmoji.d.ts +0 -5
- package/dist/src/utils/wechatEmoji.d.ts.map +0 -1
- package/dist/src/utils/wechatEmoji.js +0 -35
- package/dist/src/utils/wechatEmoji.js.map +0 -1
package/docs/EXTENDING.md
CHANGED
|
@@ -71,8 +71,8 @@ interactive menu - `showInteractiveMenu()` plus the `switch (action)` that maps
|
|
|
71
71
|
|
|
72
72
|
`test/cli-menu.test.ts` keeps those in sync: menu entries and `switch` cases must match **in both directions**, and
|
|
73
73
|
every `runCmd('x')` / `runSubCmd('p','c')` the menu calls must name a command that actually exists (that test asks the
|
|
74
|
-
CLI's own `--help`, because the source cannot tell top-level commands from subcommands -
|
|
75
|
-
as of 2026-
|
|
74
|
+
CLI's own `--help`, because the source cannot tell top-level commands from subcommands - 114 `.command('...')` call sites
|
|
75
|
+
as of 2026-10-07, against however many real commands `--help` reports). This matters because both failure modes are silent: a menu entry with no `case` does nothing
|
|
76
76
|
when picked, and `runCmd` is written as `if (cmd) await ...`, so a renamed command makes the entry do nothing too.
|
|
77
77
|
|
|
78
78
|
Two conventions that hold across the command surface: every command that a script or an AI might drive has a `--json`
|
|
@@ -98,13 +98,35 @@ prints JSON). Three rules that this project has been bitten by:
|
|
|
98
98
|
gives a **GBK** stdout: Chinese comes out as GBK bytes (mojibake if you were expecting UTF-8) and a character
|
|
99
99
|
outside GBK (`✓`, an emoji) raises `UnicodeEncodeError` and kills the run. 34 of the 55 scripts call
|
|
100
100
|
`sys.stdout.reconfigure(encoding='utf-8', errors='replace')` in `main()` so both paths behave the same; if you add a
|
|
101
|
-
script you intend to run by hand, do the same
|
|
102
|
-
that
|
|
101
|
+
script you intend to run by hand, do the same - and note this is no longer advice: `test/script_stdout_encoding_test.py`
|
|
102
|
+
fails any script that prints a character GBK cannot encode without calling it (40 of the 60 scripts call it as of
|
|
103
|
+
2026-10-07; the rest print nothing that GBK cannot encode). A probe that forgets to set that env var will show you a decoding bug
|
|
104
|
+
that does not exist in the real path. **The same variable also hides a real one**: because this environment (and the
|
|
105
|
+
bridge) export `PYTHONIOENCODING=utf-8`, a by-hand `python scripts/x.py > out.txt` never crashes *here* - but a
|
|
106
|
+
user's own shell has stdout at the locale encoding (measured with `python -E`: `gbk`, locale cp936), where the first
|
|
107
|
+
character GBK cannot encode (`✓`, `⚠️`, `⇒`) raises `UnicodeEncodeError` and kills the run. So a
|
|
108
|
+
"spawn it and see if it crashes" test is a false negative on this machine; `test/script_stdout_encoding_test.py`
|
|
109
|
+
inspects the sources statically instead, and requires `sys.stdout.reconfigure(encoding='utf-8', errors='replace')`
|
|
110
|
+
in any script whose stdout literals contain a character GBK cannot encode. The same test covers the other half -
|
|
111
|
+
**text-mode file I/O** - and requires `encoding='utf-8'` on every builtin `open()` (and `write_text`/`read_text`):
|
|
112
|
+
without it a Chinese Windows box reads and writes those files as GBK while everything downstream decodes UTF-8.
|
|
113
|
+
(`Image.open()`, `tarfile.open()` and friends are exempt by construction - they are binary decoders, which is why
|
|
114
|
+
the check matches the *builtin* `open` only.)
|
|
103
115
|
- **Fail loudly.** Exit non-zero, or return a JSON object whose `success` is false with an `error` string. "No JSON on
|
|
104
116
|
stdout" must never be read as "no results" - that conflation is why `get_todos` distinguishes "no pending todos" from
|
|
105
117
|
"extraction has never been run".
|
|
106
118
|
- **State goes in over stdin, not argv.** Command lines are visible in the process list; the drafting tool passes the
|
|
107
119
|
conversation over stdin for exactly that reason, and `semantic_search.py` takes the query through the environment.
|
|
120
|
+
- **A generator whose output must open without a server has to carry its own libraries.** `scripts/graph_3d.py`
|
|
121
|
+
inlines three.js and six d3 modules into the page, because a `file://` page cannot `fetch` its data (CORS) and a CDN
|
|
122
|
+
reference would make a local-first feature need the network. Those files live in `resources/js/graph3d/` (shipped
|
|
123
|
+
via the `resources/` entry in package.json's `files`), the Node layout helper that loads **the same** UMDs sits
|
|
124
|
+
beside them, and `NOTICE.txt` there records versions and licences. Rename or upgrade one and you must change the
|
|
125
|
+
list in **two** places - `graph_3d.py`'s `LIB_D3` and `layout.mjs`'s `LIBS` - which is why
|
|
126
|
+
`test/graph-3d-cli.test.ts` asserts the two agree and that the emitted page contains no external reference at all.
|
|
127
|
+
That script is also the one to copy if your generator's cache is big: `--cache` exists so tests can point it at a
|
|
128
|
+
temporary directory instead of overwriting the real one (the first version of the test did exactly that, on top of a
|
|
129
|
+
background run).
|
|
108
130
|
|
|
109
131
|
Tests for Python live in `test/*_test.py` and run on the same filesystem as everything else (`npm test` covers
|
|
110
132
|
TypeScript; `python -m unittest discover -s test -p '*_test.py'` covers these).
|
|
@@ -140,14 +162,48 @@ description is the material for the page.
|
|
|
140
162
|
`Wiki/Concepts` for the article line, `Chat/Concepts` for the conversation line - so a source that writes into a
|
|
141
163
|
directory nobody reads is invisible, and `compile_wiki` writes whichever line you point `--output` at (one line per
|
|
142
164
|
run, by design). If your source belongs to an existing line, you are done; if it deserves its own, it goes into
|
|
143
|
-
`_utils.
|
|
144
|
-
|
|
145
|
-
|
|
165
|
+
`_utils.KNOWLEDGE_LINES` (**as of 2026-10-10 each entry is a line id plus its own root plus the concepts directory
|
|
166
|
+
inside it**, so a new line is a new tuple, not a new path string) and into every other declaration that
|
|
167
|
+
`test/concept-dirs-agreement.test.ts` pins (that test counts the sites itself rather than trusting a number written
|
|
168
|
+
here) - that test fails until every reader of concept pages knows about the new directory, which is the point.
|
|
169
|
+
|
|
170
|
+
Two things come for free and one does not. For free: graph node ids become line-qualified (`concept_key`), link
|
|
171
|
+
disambiguation follows automatically (`_utils.LinkNamer` reads the other lines' names and only qualifies names that
|
|
172
|
+
more than one line has), and `wiki lint` starts reporting the new line's overlap with the others under
|
|
173
|
+
`crossLineSameName`. Not for free: a
|
|
174
|
+
**display name** - add it to `_utils.LINE_LABELS` and to `assistantTools.VAULT_LINE_LABELS`, which the same test
|
|
175
|
+
pins against each other. Without them the line shows up in reports as a raw directory name, which is not a crash
|
|
176
|
+
and therefore easy to miss.
|
|
146
177
|
|
|
147
178
|
The guard to copy: `test/chat_notes_test.py` writes a card and then runs the **real consumer**
|
|
148
179
|
(`compile_wiki.scan_articles`) over it, asserting the links and descriptions come back. That is the shape of every
|
|
149
180
|
producer/consumer pair in this project - do not assert on the markdown text, assert that the other side reads it.
|
|
150
181
|
|
|
182
|
+
## Recipe G: a new panel face or frame asset
|
|
183
|
+
|
|
184
|
+
The floating ball's artwork lives in `resources/panel/` and is served from there at runtime (the daemon's static
|
|
185
|
+
whitelist, not `dist/`). Adding one is four wiring points plus a contract, and **three of the four fail silently**:
|
|
186
|
+
|
|
187
|
+
1. `resources/panel/<file>.png` - 256x256, 8-bit RGBA, non-interlaced. The repo's only PNG decoder lives in
|
|
188
|
+
`test/panel-tray-pixels.test.ts` and rejects anything else; a frame returned as RGB means the generation is bad, not
|
|
189
|
+
that a background needs removing.
|
|
190
|
+
2. `PANEL_FILES` in `test/panel-packaging.test.ts` - the directory is reconciled against that hand-written list, so a new
|
|
191
|
+
file fails the suite until it is registered.
|
|
192
|
+
3. `STATIC_FILES` in `src/panel/server.ts` - this is not packaging: the page fetches through this whitelist, and a missing
|
|
193
|
+
name 404s at runtime. The symptom is the ball flashing empty for one frame, with `PANEL_ASSET_MISSING` visible only in
|
|
194
|
+
the HTTP response body, where the page cannot show it.
|
|
195
|
+
4. `panel.css` - a `body.<class> #ball .face { background-image: url('/panel/<file>.png') }` rule, plus the prefetch list
|
|
196
|
+
in `renderer.js` if the face can appear on press. A face fetched on demand costs about 15 ms - one 60 Hz frame - so the
|
|
197
|
+
first press after opening would show an empty ball.
|
|
198
|
+
|
|
199
|
+
**If the asset is a frame of an animation, the contract applies:** everything inside the inscribed circle (0 solid pixels
|
|
200
|
+
past r=128, farthest solid distance under 0.98 x 128 measured **from the canvas centre** with solid = alpha >= 128), the
|
|
201
|
+
apex anchored to the resting frame, and the scale taken from **the character's own bounding box** rather than the canvas,
|
|
202
|
+
which also carries the speech bubble and any markings. `scripts/panel_frames.py --check` enforces it, `--normalize` applies
|
|
203
|
+
it, and `--check-raw` compares a raw generation's silhouette against the resting frame before you accept it. Frames that form an ordered animation go in `SCALES` (the contract also asserts the shrink is monotonic); a family of poses that share one size goes in `TICKLE` - `all_frames()` is what the generator, the normalizer and the gates iterate. Re-run all
|
|
204
|
+
three after any regeneration: a frame that is no longer the same character passes every other assertion in the suite, which
|
|
205
|
+
is exactly what happened with the first attempt.
|
|
206
|
+
|
|
151
207
|
## What will catch you
|
|
152
208
|
|
|
153
209
|
| Invariant | Test |
|
|
@@ -159,7 +215,13 @@ producer/consumer pair in this project - do not assert on the markdown text, ass
|
|
|
159
215
|
| A config key exists in all five places | `test/config-keys.test.ts` |
|
|
160
216
|
| The MCP subset contains no write/send/publish tool | `test/assistant-tools.test.ts` |
|
|
161
217
|
| Panel IPC surface is exactly the declared method list | `test/panel-packaging.test.ts` |
|
|
218
|
+
| A panel asset is registered, whitelisted, served, and still the same character | `test/panel-packaging.test.ts`, `test/panel-lift-frames.test.ts` |
|
|
162
219
|
| Interactive-menu entries match `switch` cases, and the commands they call exist | `test/cli-menu.test.ts` |
|
|
220
|
+
| Every flag declared on a Python-backed command is **actually forwarded** to the script (the declaration is TS, the parsing is Python, the step between is hand-written - a dropped flag silently runs the command in its default mode) | `test/contact-schema-cli.test.ts` |
|
|
221
|
+
| The row above, **upside down**: a flag forwarded in the argument list but never declared. Then the machine form is the only output, asking for it by name is `unknown option`, and the script's human mode is unreachable - nothing is dropped, so nothing looks wrong (found on `vault search` on 2026-10-11) | `test/vault-search-cli.test.ts` |
|
|
222
|
+
| ...and that check is **per command, not repo-wide, on purpose.** A hand sweep of all 26 python-backed commands on 2026-10-11 found no further instances; the five it flagged are by design, and generalizations of the check hit all five: conversation identifiers travel through the environment rather than argv, `--engine` / `--interest` / `--date` are values **the CLI computes** (not flags a user types), `--pick` / `--rate-max` / `--rate-window` are enforced in TS (`outboundSend`), and `chat`'s `--interactive` is derived from "no question was given". So a blanket rule would need an allowlist entry for each, and an allowlist that long stops being read. When adding a python-backed command, copy the two-direction check from `test/vault-search-cli.test.ts` | (see the two rows above) |
|
|
223
|
+
| Every **leaf** command (one with an `.action`) declares `--json` - the convention two rows up, finally enforced. Measured 2026-10-11: 113 leaves, 111 declared, 2 allowlisted in the test **with their reason** (their action is a convenience table; the machine path is the `list` subcommand). The allowlist is itself checked for staleness, so a fixed exception cannot rot | `test/json-surface.test.ts` |
|
|
224
|
+
| ...and when a `--json` command rejects an argument, the error is JSON too, not colored prose on stdout. Use `requireCliChoice` / `parseCliInteger` / `requireCliDate` rather than writing the branch by hand - the four hand-written copies of the `--line` check are why three of them had lost the `--json` half. The **argument-validation family is audited and guarded**: 80 sites in three spellings (helpers, `includes`/`Set.has`, regex `.test`), 0 defects (2026-10-11), and every helper call in a `--json` command must pass `opts.json` (its default is `json = false`, so forgetting is silent). Define that family by *behaviour*, not by one spelling: the first pass counted 74 and missed a `Set.has` check and five regex fields. **The ~100 runtime error sites are NOT audited** ("not configured", "no WeChat process", permission denied) - whether each is reachable under `--json` depends on that command's interactive gate, so each needs reading. The one family found and fixed this way (2026-10-12) was `weread`'s nine `!res.ok` one-liners. Two blind spots of the filter that nearly hid them, both worth remembering: it treated a line as handled when an **unrelated** `opts.json` sat within five lines (a `parseCliInteger` call), and it missed sites whose variable is `!infoRes.ok` because the pattern named `res`. A window around a line cannot tell "mentions `opts.json`" from "is governed by `opts.json`" | `test/json-error-surface.test.ts` |
|
|
163
225
|
| A knowledge source's output is readable by the wiki aggregator, links and descriptions intact | `test/chat_notes_test.py`, `test/compile_wiki_test.py` |
|
|
164
226
|
| Every declaration of the concept directories agrees, and nothing that imports the constant stops iterating it | `test/concept-dirs-agreement.test.ts` |
|
|
165
227
|
|
|
@@ -324,6 +324,33 @@ AI 调用前应生成不含正文的预览,说明数据范围、是否联网
|
|
|
324
324
|
5. 为搜索结果增加消息/文章引用对象,不改变现有原始数据字段。
|
|
325
325
|
6. 为 MCP 增加不透明游标和稳定错误码。
|
|
326
326
|
7. 使用合成数据建立同步、搜索、导出和 MCP 的端到端验收样例。
|
|
327
|
+
8. 让**参数解析错误**在 `--json` 下也走 JSON。现在漏了必需选项时 commander 直接往 stderr 打一句、stdout 是**空的**
|
|
328
|
+
(2026-10-12 用"未配置状态驱动全部 `--json` 叶子命令"量到:83 条里 2 条命中,`scene add` / `scene bind`)。
|
|
329
|
+
做法大致是给 `program` 装一个会抛的出口(`exitOverride` + `configureOutput`),捕获后若 argv 里有 `--json`
|
|
330
|
+
就发 `{success:false, code:'INVALID_ARGUMENT', error:'<commander 那句>'}`。**代价**是 `--help` / `--version`
|
|
331
|
+
也走同一条出口,而全库的 `--help` 调用面很宽(含 `scripts/check_readme_commands.py` 那 277+ 条文档命令),
|
|
332
|
+
所以要先有一条覆盖 `--help`/`--version` 的判据再动 —— 否则把帮助信息弄坏的后果比这点收益大得多。
|
|
333
|
+
9. ~~**markdown 导出(连图一起)**~~ **已做(2026-10-13)**:`export … md` 走 Python 那条路
|
|
334
|
+
(`--format md`),图片落盘到 `<输出>/<会话>_assets/`、正文用相对路径引用,并写一份 `manifest.json`;
|
|
335
|
+
同目录里再导出 `json`/`txt`/`excel` 会自动带上同样的路径(`media` 字段 / `[图片: …]` / 「媒体」列)。
|
|
336
|
+
实现上**没有**在 TS 里重写匹配逻辑 —— 那样两份实现必然漂移。
|
|
337
|
+
落地时踩到两件值得记住的事:清单原本按 `local_id` 做键,而**它在分片之间会重复**(1,914 条里 763 条
|
|
338
|
+
撞上 522 个条目 ⇒ 非 md 格式会引用到别人的图),改成 `local_id:create_time` 后四种格式一致在 556;
|
|
339
|
+
以及 **Python 失败时 TS 回退成简版 HTML、CLI 仍打印「✓ 导出成功」**(失败伪装成成功)—— md 现在没有
|
|
340
|
+
静默回退,HTML 的回退会往 stderr 说明它丢了图片。
|
|
341
|
+
10. ~~**导出文件本体(`.pdf`/`.docx`/`.xlsx`…)**~~ **已做(2026-10-13)**:`md` 与 `json|txt|excel --media`
|
|
342
|
+
会把文档本体**按原名与扩展名**拷进 `<输出>/<会话>_assets/files/`,md 正文按原名链接到 `files/` 下,
|
|
343
|
+
其余格式经同一份清单带上(`mediaKind`/`mediaName`、`[文件: …]`、「媒体」列)。
|
|
344
|
+
"消息 → 文件"的链路实测清楚了,与图片那套**不同**:文件消息(appmsg `type=6`)只有名字/md5/大小,
|
|
345
|
+
本体在账号目录 `msg/file/YYYY-MM/` 下且**与 talker 无关**(按月分目录、目录里没有会话痕迹),
|
|
346
|
+
所以索引是**只 stat 的** `(归一化名, 字节数)`,多候选才用 md5 消歧(磁盘名常带微信加的 `(1)`)。
|
|
347
|
+
本机实测 1,855 条文件消息,1,489 条唯一命中 + 154 条 md5 认下 = **88.6%**,212 条本地没有
|
|
348
|
+
(未下载/已清理)—— 不猜,计数照实报。
|
|
349
|
+
两处坑:`<refermsg>`(引用原文)嵌在 `<appmsg>` **内部**,满文档搜首个同名标签会把引用里的
|
|
350
|
+
`<type>6</type>` 当成这条消息的文件(首次测量 7,083 条"像文件"里绝大多数是引用)⇒ 改为只取
|
|
351
|
+
`<appmsg>` 的**直接子元素**;以及"新拷贝数"被当成"导出数"(第二次跑打印「0 document(s)」而 67 份
|
|
352
|
+
就在磁盘上)⇒ 拆成 `exported` 与 `written`。
|
|
353
|
+
仍然**不做**的:HTML 不嵌文件本体(单文件可分享的用法下,内嵌几 MB 的 PDF 没有意义),语音/视频仍只有标签。
|
|
327
354
|
|
|
328
355
|
首轮完成后,再根据实际失败记录决定是否进入阅读状态、AI 执行层和助手收发队列,避免在数据边界尚未稳定时继续扩大功能面。
|
|
329
356
|
|
package/docs/HEALTH-CHECK.md
CHANGED
|
@@ -23,6 +23,7 @@ WeFlow 健康检查 · 2026-09-19 10:04
|
|
|
23
23
|
|
|
24
24
|
[OK ] 版本一致 CLI X.Y.Z / package X.Y.Z
|
|
25
25
|
[OK ] 环境自检 已初始化=True 消息库=True
|
|
26
|
+
[OK ] 微信客户端 4.1.15.13(D:\WeiXin\4.1.15.13) ⚠ 残留版本目录(没有 Weixin.dll,加载会失败): 4.1.15.11(1 个文件)
|
|
26
27
|
[OK ] Python 依赖 齐全
|
|
27
28
|
[OK ] 会话新鲜度 最新会话 2026-09-19 10:03(0.0 天前)
|
|
28
29
|
[OK ] 读取路径一致 (wxid_6xx1ehe4h…) json 最新 2026-09-19 10:03 / html 最新 2026-09-19 10:03:12
|
|
@@ -31,7 +32,7 @@ WeFlow 健康检查 · 2026-09-19 10:04
|
|
|
31
32
|
[OK ] 导出格式 excel 6788 字节
|
|
32
33
|
[OK ] 计划任务 均在位且上次成功
|
|
33
34
|
|
|
34
|
-
全部通过(
|
|
35
|
+
全部通过(10 项)
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
全程约 1 分钟,其中大部分是 `export html` 扫描媒体索引的固定开销。
|
|
@@ -42,11 +43,13 @@ WeFlow 健康检查 · 2026-09-19 10:04
|
|
|
42
43
|
| --- | --- |
|
|
43
44
|
| **版本一致** | `1.6.0` 的 `--version` 写死成 `1.5.1`,导致「我到底跑的哪个版本」无法回答,Issue #8 的诊断因此卡住 |
|
|
44
45
|
| **环境自检** | 未初始化 / 缺 Python 依赖时,命令报的是「数据库连接失败」,指向错误的方向 |
|
|
46
|
+
| **微信客户端** | 体检从没报过客户端版本,而这是"数据读不出来 / 客户端起不来"时第一个要问的。判据只能是**活进程命令行的 `--user-lib-dir`** —— exe 里那个版本字面量只是**兜底**(实测它恰好与目录同名,所以"读字面量"是个看着会对的错判据);磁盘上**有**版本目录也不代表能用(本机就有一个只剩 1 个文件的残留目录,指向它的加载会失败)。同一份进程表还给出:**客户端自报的数据目录**(与配置推出来的根**按真身比**,本机那条是软链)、**崩溃记录**(只报条数与最近时间)、以及 `update\update.data` 在场时那句"命令行给的版本未必是加载的那个"。**取不到就写"未知"**,并把这个字段整个缺失当成**不通过**(那意味着 CLI 没重建) |
|
|
45
47
|
| **Python 依赖** | `nt_decrypt.py` 是子进程,缺 `sqlcipher3` 的表现和密码错一模一样 |
|
|
46
|
-
| **会话新鲜度** |
|
|
47
|
-
|
|
|
48
|
+
| **会话新鲜度** | 读取只覆盖部分分片时,会话列表停在十几天前,而每条命令都返回「成功」。⚠️ **但"这段时间没聊天"是同一个观测** —— 所以提示里两种解释都写,并点出判别器在隔壁:`读取路径一致` 绿着多半是没聊天,两项一起红才更像读取断了 |
|
|
49
|
+
| **选择样本会话**(下面两项的前置) | 挑不出样本会话时,`读取路径一致` 与 `导出格式` 会**整段不跑**。没有会话时会报红;**有会话、却一条都拿不到 `username`**(字段长变了、列表是半截的)原来直接返回空 ⇒ 那两项静默跳过、报告照样写"全部通过",只是项数悄悄少了两个。现在这一支也报红并说明"这两项没跑,不代表通过" |
|
|
50
|
+
| **读取路径一致** | 同一会话,`export json` 走 NT 路径、`export html` 走 Python 路径。二者曾差 1114 条消息——json 只读到第一个分片。**它曾经会假红**:判据是"两边报的最新消息精确到分钟相同",而 json 先导、html 后导(中间一两分钟)⇒ 检查期间来一条新消息就必然不一致。放宽容差会把这一项唯一要抓的东西(分片半读,繁忙群里轮转就在分钟级)放过去,所以改成**不一致时再读一次 json 仲裁**:那一读能看到 html 那条 ⇒ 是"消息是新的"(判过并说明);看不到 ⇒ 两条路径确实不一致(判红)。仲裁只在本来就不一致时才多花一次导出 |
|
|
48
51
|
| **导出格式 json/txt/excel** | `excel` 曾**从未成功过**,报错只有「导出失败」四个字 |
|
|
49
|
-
| **计划任务** |
|
|
52
|
+
| **计划任务** | 提醒脚本静默停跑,没有任何人会知道。**它还会假绿**:判据是从 `schtasks` 的文本输出里找 `Last Result` / `上次运行结果` 那一行,而系统语言或输出格式一变就找不到它 —— 那时两个清单都是空的,它会报"均在位且上次成功"。现在读不出来就报**"没有验证"**(红),与"取不到就说未知"是同一条纪律。**同一条纪律还修了另一处(2026-10-11)**:任务刚注册、还没到第一次触发时,`schtasks` 报的是 `SCHED_S_TASK_HAS_NOT_RUN`(`267011` / `0x41303`)—— 原来它落进"上次运行失败"那一支,提示还写着"脚本可能抛异常了",会把刚注册的人引去查一个并不存在的异常。现在它是**自己的状态**("还没跑过"),**仍然算不通过**:这一项要的是"跑过一次且成功",而"一直没跑过"正是它要防的。(这两个 watcher 任务的**注册命令**此前从没写下来过,只在 `RELEASING.md` 的「新动态邮件提醒」一节里有名字 —— 已补上。)|
|
|
50
53
|
|
|
51
54
|
### 「读取路径一致」为什么用时间戳而不是条数
|
|
52
55
|
|
|
@@ -85,12 +88,14 @@ schtasks /create /tn "WeFlow Health Check" ^
|
|
|
85
88
|
## 目前已知、但**未修**的问题
|
|
86
89
|
|
|
87
90
|
体检脚本覆盖不到这些;它们需要人判断,所以留在这里而不是塞进自动检查。
|
|
91
|
+
**修好的行留在表里并标"已修"** —— 这一节的价值一半在教训,其中一行正是因为没人回头核而多挂了些日子。
|
|
88
92
|
|
|
89
93
|
| 问题 | 现象 | 为什么没自动修 |
|
|
90
94
|
| --- | --- | --- |
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
| `
|
|
95
|
+
| 判断层按**地区**不可用 | `decide.py` 与日报的 Jev 分类都拿到 `HTTP 451: {"title":"Typesafe is not available in your region."}` | **不能修**(服务端策略;本机 2026-10-10 实测)。功能不坏,是**逐篇/整轮退回老路径**:助手按设计回退(日志里那行 `判断层不可用: HTTP 451…`),日报每篇退回 LLM 解析。**已改**:同一条失败在日报日志里**折叠成一次**(`另有 N 篇因**同一条**失败被折叠`),此前上百篇会刷上百行同一句话;要连这一次尝试也省掉就用 `--classifier llm`(`OPERATIONS.md` 里已写明)。 |
|
|
96
|
+
| 未知 talker 的退出码不一致 | `messages <拼错的wxid>` 退出 0 且 `success:true`;同样输入 `export` 退出 1 | **退出码刻意不动**("没有消息"不是错误,脚本与 MCP 依赖它),但**歧义已经说出来**(2026-10-09):`messages` 遇到 `wxid_`/`@chatroom`/`@openim` 形状、而会话与联系人都不认识它时,人看的输出补一句「这个 ID 既不在会话里、也不在联系人里……先确认有没有拼错」,`--json` 里多一个可选的 `note` 字段(`success` 与退出码都不变)。真实命令跑过:`messages wxid_zzz_not_a_real_id --json` → `{"success":true,…,"note":"这个 ID 既不在会话里、也不在联系人里:…"}`,退出码 0。判定逻辑是纯函数 `emptyMessagesNote`(`src/services/messageQuery.ts`),接线处有守卫测试 |
|
|
97
|
+
| `whitelist list` / `blacklist list` 空列表 | 曾只输出一个换行,没有「为空」提示 | **已修**(2026-10-09 复核):不带参数时打印 `白名单为空` / `黑名单为空` 加一条「怎么添加」(`bin/weflow-cli.ts:2269`、`:2457`);`whitelist list --json` 与 `blacklist list --json` 都返回 `{"success":true,"entries":[]}` —— 三条都用真实命令跑过,不是读代码推断的 |
|
|
98
|
+
| `dashscopeApiKey` **曾**无法通过 `config set` 写入 | 脚本会读它,但它当时不在 `CliConfig` 里 ⇒ `config set` 拒绝、只能手改 config.json | **已修**(`favPassphrase` 是同一轮发现的):键已声明、加密存储、`config set` 可写;守卫见 `test/config_keys_declared_test.py` |
|
|
94
99
|
| 朋友圈/收藏密钥缺失 | `fav list` 曾把「没配密钥」报成「需 4.x NT 连接」——已修,现在会明确说缺密钥 | — |
|
|
95
100
|
|
|
96
101
|
## 相关文档
|
|
@@ -187,7 +187,7 @@ python scripts/quality_eval.py score <该文件> # 准确率 + 校准曲线 +
|
|
|
187
187
|
- 怎么重排:`sort(key=lambda t: (t[0] is None, -(t[0] or 0.0), t[1]))`(`460`)——**没拿到分的按原序垫到最后,不当 0 分**。新分写进 `rerankScore`(仅非 None 时),原始 `score` 保留。
|
|
188
188
|
- 回退全是 fail-soft(这是设计,活体跑出过 `HTTP 529`):没有客户端或候选 <2 → 原样返回(`420-421`);调用异常 → WARN + 原顺序(`442-444`);全部返回 None → WARN + 原顺序(`455-457`)。
|
|
189
189
|
- **它唯一的真实风险是候选编号错位**:模型拿 `c5` 的内容回答 `c3` 时,最该被选中的那条会**无声下沉**,没有任何报错。`docs/PROJECT_STATE.md:83` 记了这一条。
|
|
190
|
-
-
|
|
190
|
+
- ✅ **`--no-rerank` 现在两边都有**(2026-10-11 接上的通道):CLI 的 `search` 把它转发给脚本,`--dry-run --json` 的预览里也带 `noRerank`。此前它只在 Python 侧 —— 而 `docs/OPERATIONS.md` 已经在教用户敲 `weflow-cli search "..." --no-rerank`,用户会拿到 `unknown option`。**是文档命令面那道检查发现的**(`scripts/check_readme_commands.py` 扫文档代码块):它按"今天真实的 CLI"校验,于是把这条缺口指了出来 —— 而只扫两份 README 时它扫不到。新增 `test/search-cli.test.ts` 钉住这类(声明的旗标必须被分类、被读到、该转发的真的转发)。
|
|
191
191
|
|
|
192
192
|
### 4.7 通用原则(六个触点共同的形状)
|
|
193
193
|
|
|
@@ -203,7 +203,7 @@ python scripts/quality_eval.py score <该文件> # 准确率 + 校准曲线 +
|
|
|
203
203
|
| D-032 | `:506` | 决策模型做 rerank:一次请求、取 top 20 |
|
|
204
204
|
| D-033 | `:549` | 给人看的机器判断都要带证据(`reply_debt` 的五问 + 两条通用规则) |
|
|
205
205
|
| D-034 | `:646` | 把决策模型作为本地原语暴露,但**不**经 MCP |
|
|
206
|
-
| D-035 | `:697` |
|
|
206
|
+
| D-035 | `:697` | 助手的单轮快路:**已发、默认关**(`assistantFastRoute` = `off`|`log`|`on`;2026-09-22 起 Status: Active) |
|
|
207
207
|
| D-036 | `:759` | 检索用决策模型选查询词,**不**用它排会话 |
|
|
208
208
|
|
|
209
209
|
---
|
package/docs/MCP.md
CHANGED
|
@@ -118,10 +118,10 @@ client-local config that is not in version control.
|
|
|
118
118
|
| `wechat.get_sns` | Read the Moments timeline or statistics. | Decrypted WeChat database |
|
|
119
119
|
| `wechat.get_weread` | Read WeRead shelf, notebooks, or search books. | `wereadApiKey` config |
|
|
120
120
|
| `wechat.get_todos` | List todos extracted from chat history. | `scripts/extract_todos.py`, Python |
|
|
121
|
-
| `wechat.search_knowledge` | Fuzzy-search concept pages in the knowledge base. | Vault Wiki output |
|
|
121
|
+
| `wechat.search_knowledge` | Fuzzy-search concept pages in the knowledge base. Searches **one of the two lines by default** (the article one); pass `line: "chat"` or `"all"` for the conversation line. | Vault Wiki output |
|
|
122
122
|
| `wechat.search_memory` | Search long-term assistant memory. | `~/.weflow-cli/assistant_memory.json` |
|
|
123
123
|
| `wechat.search_chats` | Find which conversation discussed something (literal words). | Decrypted WeChat database, **words leave the machine - `confirm: true` required** |
|
|
124
|
-
| `wechat.search_semantic` | Find chat messages by meaning, not literal words. | `dashscopeApiKey` config, network - **query and hits leave the machine, `confirm: true` required** |
|
|
124
|
+
| `wechat.search_semantic` | Find chat messages by meaning, not literal words. Defaults to the article line; pass `line: "chat"` or `"all"`. | `dashscopeApiKey` config, network - **query and hits leave the machine, `confirm: true` required** |
|
|
125
125
|
| `wechat.who_owes_reply` | Rank the conversations waiting on a reply. | Decrypted WeChat database, **text leaves the machine - `confirm: true` required** |
|
|
126
126
|
| `wechat.draft_reply` | Draft candidate replies. | Decrypted WeChat database, **text leaves the machine - `confirm: true` required** |
|
|
127
127
|
| `wechat.export_chat` | Export one conversation to HTML, txt, json or excel. | Decrypted WeChat database, **writes under `output/exports/`** |
|