weflow-cli 1.8.2 → 1.10.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 +1290 -0
- package/OPERATIONS.md +144 -11
- package/README.en.md +20 -4
- package/README.md +30 -5
- package/bin/weflow-cli-electron.cjs +2 -0
- package/bin/weflow-cli.ts +996 -118
- package/dist/bin/weflow-cli.js +1038 -146
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/http.d.ts +14 -0
- package/dist/mcp-server/http.d.ts.map +1 -0
- package/dist/mcp-server/http.js +84 -0
- package/dist/mcp-server/http.js.map +1 -0
- package/dist/mcp-server/httpConfig.d.ts +50 -0
- package/dist/mcp-server/httpConfig.d.ts.map +1 -0
- package/dist/mcp-server/httpConfig.js +104 -0
- package/dist/mcp-server/httpConfig.js.map +1 -0
- package/dist/mcp-server/index.d.ts +13 -1
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +58 -374
- 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 +61 -1
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/services/assistantEval.d.ts +69 -0
- package/dist/src/services/assistantEval.d.ts.map +1 -1
- package/dist/src/services/assistantEval.js +270 -8
- package/dist/src/services/assistantEval.js.map +1 -1
- package/dist/src/services/assistantScenes.d.ts +81 -0
- package/dist/src/services/assistantScenes.d.ts.map +1 -0
- package/dist/src/services/assistantScenes.js +322 -0
- package/dist/src/services/assistantScenes.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +33 -4
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +102 -6
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantSkills.d.ts +94 -0
- package/dist/src/services/assistantSkills.d.ts.map +1 -0
- package/dist/src/services/assistantSkills.js +330 -0
- package/dist/src/services/assistantSkills.js.map +1 -0
- package/dist/src/services/assistantTools.d.ts +34 -1
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +931 -88
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/assistantTrace.d.ts +4 -0
- package/dist/src/services/assistantTrace.d.ts.map +1 -1
- package/dist/src/services/assistantTrace.js +9 -0
- package/dist/src/services/assistantTrace.js.map +1 -1
- package/dist/src/services/configService.d.ts +9 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +5 -2
- package/dist/src/services/configService.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/outboundSend.d.ts +53 -0
- package/dist/src/services/outboundSend.d.ts.map +1 -0
- package/dist/src/services/outboundSend.js +90 -0
- package/dist/src/services/outboundSend.js.map +1 -0
- package/dist/src/services/pythonBridge.d.ts.map +1 -1
- package/dist/src/services/pythonBridge.js +1 -0
- package/dist/src/services/pythonBridge.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/dist/src/utils/python.js +2 -2
- package/dist/src/utils/python.js.map +1 -1
- package/docs/BRANCHES.md +37 -0
- package/docs/CONTACT_DB_SCHEMA.md +1085 -0
- package/docs/DECISIONS.md +1624 -0
- package/docs/EXTENDING.md +98 -10
- package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +6 -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 +58 -4
- package/docs/PROJECT_STATE.md +180 -11
- package/docs/RELEASING.md +115 -1
- package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
- package/logo.png +0 -0
- package/mcp-server/http.ts +108 -0
- package/mcp-server/httpConfig.ts +126 -0
- package/mcp-server/index.ts +359 -681
- 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/ball-position.cjs +48 -2
- package/resources/panel/index.html +18 -4
- package/resources/panel/main.cjs +137 -5
- package/resources/panel/mascot-base.png +0 -0
- package/resources/panel/mascot-blink-1.png +0 -0
- package/resources/panel/mascot-blink-2.png +0 -0
- package/resources/panel/mascot-iris.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-peek.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 +205 -59
- package/resources/panel/preload.cjs +26 -0
- package/resources/panel/renderer.js +646 -17
- package/scripts/_utils.py +1203 -931
- package/scripts/backfill_articles.py +202 -21
- package/scripts/biz_daily.py +26 -34
- package/scripts/bonds.py +331 -0
- package/scripts/chat_notes.py +561 -470
- package/scripts/chat_stats.py +8 -7
- package/scripts/check_readme_commands.py +426 -0
- package/scripts/compile_wiki.py +187 -59
- package/scripts/contact_schema.py +339 -0
- package/scripts/data/wechat_emoji.json +225 -0
- package/scripts/export_chat_html.py +3 -1
- 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/mcp_bridge.py +6 -7
- 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 +130 -22
- package/scripts/vault_rag.py +42 -16
- package/scripts/vault_search.py +44 -8
- package/scripts/wechat_emoticon.py +6 -0
- package/scripts/wechat_voice.py +9 -0
- package/scripts/wiki_lint.py +124 -24
- package/src/core/dbPathService.ts +22 -4
- package/src/panel/server.ts +58 -1
- package/src/services/assistantEval.ts +323 -12
- package/src/services/assistantScenes.ts +342 -0
- package/src/services/assistantService.ts +107 -6
- package/src/services/assistantSkills.ts +364 -0
- package/src/services/assistantTools.ts +2452 -1640
- package/src/services/assistantTrace.ts +9 -0
- package/src/services/configService.ts +415 -403
- package/src/services/messageQuery.ts +29 -0
- package/src/services/outboundSend.ts +126 -0
- package/src/services/pythonBridge.ts +1 -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/src/utils/python.ts +2 -2
- 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
|
@@ -6,7 +6,7 @@ one of two things, and they are not equivalent:
|
|
|
6
6
|
|
|
7
7
|
1. **In-repo** - add the capability to this source tree (fork + PR). Every recipe below is about this path, and each
|
|
8
8
|
one lists the places you must touch **and the test that will catch you if you miss one**.
|
|
9
|
-
2. **Out-of-process** - call the project from a program you keep elsewhere, through **MCP** (`mcp-server/`,
|
|
9
|
+
2. **Out-of-process** - call the project from a program you keep elsewhere, through **MCP** (`mcp-server/`, **32 tools**: the 28 the assistant exposes minus the three it withholds from this path, plus four hand-written `wechat.*` ones - the table in `docs/MCP.md` is the list, and `test/tool-registry.test.ts` fails if it drifts)
|
|
10
10
|
or the CLI's `--json` surface. This is the only boundary that exists for code that does not live in this repository.
|
|
11
11
|
|
|
12
12
|
## Why there is no plugin loader
|
|
@@ -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
|
|
|
@@ -176,12 +238,38 @@ These hold for any extension, and the full statements live in `AGENTS.md` and `C
|
|
|
176
238
|
reporting a real change.
|
|
177
239
|
- Do not record a guess as a verified fact. When something has not been measured, say so where the claim is made.
|
|
178
240
|
|
|
241
|
+
## Skills are content, not an extension point
|
|
242
|
+
|
|
243
|
+
A skill is a directory containing `SKILL.md` (`name`/`description` frontmatter, optionally `version`, `triggers`,
|
|
244
|
+
`enabled`), scanned from `skillDirs` (default `~/.claude/skills`, `~/.weflow-cli/skills`). **It does not follow any
|
|
245
|
+
recipe above.** Nothing here is code that joins the process:
|
|
246
|
+
|
|
247
|
+
- **It adds no tool.** The model sees a catalogue in the system prompt and can call `read_skill` to read one body. The
|
|
248
|
+
tool table is unchanged, so installing a skill cannot widen what the assistant is able to do.
|
|
249
|
+
- **Its content is never executed.** No scripts, no dependencies, no installation step - the "installer" is the user
|
|
250
|
+
putting a directory where the scanner looks.
|
|
251
|
+
- **It cannot read anything.** `read_skill` only ever returns a file named `SKILL.md` that the directory scan already
|
|
252
|
+
produced; an id shaped like a path is not a path. That is the P4 acceptance criterion (extensions must not read
|
|
253
|
+
configuration, databases or arbitrary files) satisfied by construction rather than by a check.
|
|
254
|
+
- **It is disableable alone** (`config set skillDisabled <id>` or `enabled: false` in the file itself).
|
|
255
|
+
- Bodies enter the prompt through `frameLocalData`, exactly like memory and scene text, because a `SKILL.md` is
|
|
256
|
+
local material that can contain a closing frame tag.
|
|
257
|
+
|
|
258
|
+
The rules that *are* worth knowing when authoring one: read only the leading `---` block (bodies contain lines that look
|
|
259
|
+
like frontmatter), block scalars are supported (`description: >-` with indented lines), and ids should stay
|
|
260
|
+
lowercase-with-hyphens if the skill is meant to travel to another agent - this project keeps and warns about
|
|
261
|
+
non-conforming ids (`clz_docx_to_mp` is in use here) rather than refusing them. `weflow-cli skill check` reports
|
|
262
|
+
unreadable frontmatter, cross-root collisions, non-conforming ids and disabled skills.
|
|
263
|
+
|
|
179
264
|
## What does not exist yet
|
|
180
265
|
|
|
181
266
|
Named here so that the gaps are not mistaken for omissions:
|
|
182
267
|
|
|
183
|
-
- **A plugin loader / adapter interface.** Roadmap, not implemented (see above).
|
|
268
|
+
- **A plugin loader / adapter interface.** Roadmap, not implemented (see above). Skills exist and are deliberately
|
|
269
|
+
*not* it: they are prompt-time material with no code path into this process.
|
|
184
270
|
- **A public import surface.** No `exports`, `main` or `types`; the published package is a CLI, and `src/` is shipped
|
|
185
271
|
without a stability promise.
|
|
186
272
|
- **A versioned extension contract.** The versioned contracts that exist are data contracts (`weflow-message/v1`,
|
|
187
|
-
`weflow-sync/v1`), not code contracts.
|
|
273
|
+
`weflow-sync/v1`, `weflow-scenes/v1`), not code contracts.
|
|
274
|
+
- **A skill installer.** There is no install/update/uninstall path and no per-agent deployment; the roots are read,
|
|
275
|
+
never written.
|
|
@@ -324,6 +324,12 @@ 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` 的判据再动 —— 否则把帮助信息弄坏的后果比这点收益大得多。
|
|
327
333
|
|
|
328
334
|
首轮完成后,再根据实际失败记录决定是否进入阅读状态、AI 执行层和助手收发队列,避免在数据边界尚未稳定时继续扩大功能面。
|
|
329
335
|
|
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
|
@@ -50,6 +50,52 @@ The generated server starts with Node.js and `tsx`:
|
|
|
50
50
|
|
|
51
51
|
Copy this entry into your MCP client's configuration and ensure `cwd` points to the cloned WeFlow CLI directory. Restart the client after saving the configuration.
|
|
52
52
|
|
|
53
|
+
## HTTP transport (opt-in)
|
|
54
|
+
|
|
55
|
+
The server speaks **stdio by default** and stays that way unless you ask for the other transport. Adding `--http`
|
|
56
|
+
(or `WEFLOW_MCP_HTTP=1`) serves the **same tools** over Streamable HTTP - the tool table is derived from one place
|
|
57
|
+
(`TOOL_DEFS`), so the two transports cannot drift apart:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" # make a token
|
|
61
|
+
npx tsx mcp-server/index.ts --http --token=<that token>
|
|
62
|
+
# WeFlow MCP 已监听 http://127.0.0.1:8790/mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Point an HTTP-capable client at `http://127.0.0.1:8790/mcp` with `Authorization: Bearer <token>`.
|
|
66
|
+
|
|
67
|
+
Four boundaries, enforced in `mcp-server/httpConfig.ts` and `mcp-server/http.ts` (pure decisions, offline tests in
|
|
68
|
+
`test/mcp-http.test.ts`, each one mutation-checked):
|
|
69
|
+
|
|
70
|
+
| Boundary | Why |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| **Off unless asked** | No `--http` means stdio, byte for byte what it did before. |
|
|
73
|
+
| **A token is required, and there is no default** | A silently generated token looks configured. Without one the server refuses to start (exit 1) and tells you how to make one. |
|
|
74
|
+
| **Loopback only** (`127.0.0.1` / `localhost` / `::1`) | `0.0.0.0` is rejected on purpose. For another machine use a tunnel (`ssh -L 8790:127.0.0.1:8790 <host>`) or your own reverse proxy in front of it - widening the bind is not something this command should do for you. |
|
|
75
|
+
| **DNS-rebinding protection on** | The SDK defaults it to `false`. The realistic attack on a loopback server is a web page you happen to visit reaching `127.0.0.1`, so the `Host` header must match and any `Origin` must be allowlisted. |
|
|
76
|
+
|
|
77
|
+
Port defaults to 8790 (`--http=9100`, `--port=`, or `WEFLOW_MCP_HTTP_PORT`); `--http=0` lets the OS pick one, which
|
|
78
|
+
is what the tests do. Requests are stateless - one server per request, no session state to leak between clients -
|
|
79
|
+
and **the token is never written to a log**.
|
|
80
|
+
|
|
81
|
+
An HTTP-capable client points at the URL instead of a command. The server is one you start (and stop) yourself, so
|
|
82
|
+
the client never manages the process:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"weflow": {
|
|
88
|
+
"type": "http",
|
|
89
|
+
"url": "http://127.0.0.1:8790/mcp",
|
|
90
|
+
"headers": { "Authorization": "Bearer <the token you started it with>" }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Keep the token out of a file you commit: put it in an environment variable your client expands, or in a
|
|
97
|
+
client-local config that is not in version control.
|
|
98
|
+
|
|
53
99
|
## Available tools
|
|
54
100
|
|
|
55
101
|
| Tool | Purpose | Local data required |
|
|
@@ -72,15 +118,20 @@ Copy this entry into your MCP client's configuration and ensure `cwd` points to
|
|
|
72
118
|
| `wechat.get_sns` | Read the Moments timeline or statistics. | Decrypted WeChat database |
|
|
73
119
|
| `wechat.get_weread` | Read WeRead shelf, notebooks, or search books. | `wereadApiKey` config |
|
|
74
120
|
| `wechat.get_todos` | List todos extracted from chat history. | `scripts/extract_todos.py`, Python |
|
|
75
|
-
| `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 |
|
|
76
122
|
| `wechat.search_memory` | Search long-term assistant memory. | `~/.weflow-cli/assistant_memory.json` |
|
|
77
123
|
| `wechat.search_chats` | Find which conversation discussed something (literal words). | Decrypted WeChat database, **words leave the machine - `confirm: true` required** |
|
|
78
|
-
| `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** |
|
|
79
125
|
| `wechat.who_owes_reply` | Rank the conversations waiting on a reply. | Decrypted WeChat database, **text leaves the machine - `confirm: true` required** |
|
|
80
126
|
| `wechat.draft_reply` | Draft candidate replies. | Decrypted WeChat database, **text leaves the machine - `confirm: true` required** |
|
|
81
127
|
| `wechat.export_chat` | Export one conversation to HTML, txt, json or excel. | Decrypted WeChat database, **writes under `output/exports/`** |
|
|
82
128
|
| `wechat.export_messages` | Return a conversation's local messages as `weflow-message/v1` JSON. Read-only; returns no database path, key or configuration. | Decrypted WeChat database |
|
|
83
129
|
| `wechat.get_reading_stats` | Which sources push most and what the daily processes. | `output/biz-daily/` |
|
|
130
|
+
| `wechat.list_skills` | List skill packages installed on this machine (`SKILL.md` bundles). | Skill directories (`skillDirs` config) |
|
|
131
|
+
| `wechat.read_skill` | Read one skill's `SKILL.md` body. Read-only; only files under the configured skill directories, never arbitrary paths. | Skill directories (`skillDirs` config) |
|
|
132
|
+
| `wechat.check_skills` | Report skill problems: unreadable frontmatter, cross-directory collisions, non-conforming ids, disabled skills. | Skill directories (`skillDirs` config) |
|
|
133
|
+
| `wechat.list_contacts` | List contact names from the local address book. **Names only** - no `wxid`, alias or other local identifiers. | Decrypted WeChat database |
|
|
134
|
+
| `wechat.lint_wiki` | Knowledge-base health check: broken links, orphan pages, empty pages, duplicate titles. Read-only; changes nothing. | Vault Wiki output |
|
|
84
135
|
|
|
85
136
|
This table lists the tools shipped today; `weflow-cli mcp-config` prints the authoritative list for the
|
|
86
137
|
installed version. **`wechat.look_at_image` is deliberately not on it**: that tool works by handing the image to
|
|
@@ -92,9 +143,9 @@ offered on it (the same rule `unavailableToolReason` applies elsewhere). The cha
|
|
|
92
143
|
|
|
93
144
|
- The server inherits the permissions of the MCP client. Only add it to a client you trust.
|
|
94
145
|
- Article and knowledge-base tools read files under the current project directory. Keep `cwd` scoped to your intended WeFlow CLI checkout.
|
|
95
|
-
- Chat-data tools (`list_sessions`, `get_messages`, `search_favorites`, `get_sns`, `get_todos`) read your locally decrypted WeChat database. Only run this MCP server on machines where that is acceptable, and never expose the stdio server over a network.
|
|
146
|
+
- Chat-data tools (`list_sessions`, `get_messages`, `search_favorites`, `get_sns`, `get_todos`, `list_contacts`) read your locally decrypted WeChat database. Only run this MCP server on machines where that is acceptable, and never expose the stdio server over a network.
|
|
96
147
|
- `read_favorite`, `fetch_article`, and `search_public` make network requests; `read_favorite` rejects private/loopback URLs.
|
|
97
|
-
- The MCP surface is **derived from the assistant's tool table minus `save_memory
|
|
148
|
+
- The MCP surface is **derived from the assistant's tool table minus the excluded tools** (`save_memory`, `look_at_image`, `set_todo_status`), not a hand-written read-only list - so it is *not* read-only, and this line used to claim it was. What it actually contains, declared in `capabilities --json` under `safety.mcpSurface`: one tool that writes (`export_chat`, new directories under `output/exports/` only) and four that send user data to cloud models (`who_owes_reply` and `search_chats` send chat text to the decision model, `search_semantic` sends the query for embedding and the hits for reranking, `draft_reply` sends a conversation to two models - and **all four** require `confirm: true`, not just `draft_reply`; that is what `safety.mcpSurface.requiresConfirm` lists, and it is now checked against the source rather than restated). Publishing, sending messages, mutating todos, changing configuration and writing assistant memory stay out.
|
|
98
149
|
- **Four tools on this surface send user data to cloud models, and each of them requires an explicit
|
|
99
150
|
confirmation from the machine caller.** They are `who_owes_reply` (every conversation's text, one request
|
|
100
151
|
each), `search_chats` (your question plus words extracted from the chats - not the message bodies),
|
|
@@ -110,5 +161,8 @@ offered on it (the same rule `unavailableToolReason` applies elsewhere). The cha
|
|
|
110
161
|
| Symptom | Check |
|
|
111
162
|
| --- | --- |
|
|
112
163
|
| Server fails to start | Run `npm install` in the configured `cwd`, then run `npx tsx mcp-server/index.ts`. |
|
|
164
|
+
| HTTP mode refuses to start | The message says why: no token (`--token=` / `WEFLOW_MCP_TOKEN`), a token under 24 characters, or a bind address that is not loopback. |
|
|
165
|
+
| HTTP client gets 401 | The `Authorization: Bearer <token>` header is missing or wrong - it must be the token the server was started with. |
|
|
166
|
+
| HTTP client cannot connect from another machine | Working as intended: the bind is loopback. Tunnel to it (`ssh -L 8790:127.0.0.1:8790 <host>`) rather than opening the port. |
|
|
113
167
|
| No articles found | Generate a daily collection first, then confirm `output/biz-daily/` exists under `cwd`. |
|
|
114
168
|
| Client cannot find `npx` | Configure an absolute Node.js command path or install Node.js 22.13+. |
|