weflow-cli 1.8.0 → 1.8.2
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/CHANGELOG.md +176 -1
- package/OPERATIONS.md +42 -6
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/bin/weflow-cli.ts +3 -1
- package/dist/bin/weflow-cli.js +3 -1
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/src/panel/server.js +1 -1
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -1
- package/dist/src/services/assistantPrivacy.js +7 -2
- package/dist/src/services/assistantPrivacy.js.map +1 -1
- package/dist/src/services/assistantService.d.ts.map +1 -1
- package/dist/src/services/assistantService.js +10 -1
- package/dist/src/services/assistantService.js.map +1 -1
- package/dist/src/services/assistantTools.d.ts +12 -0
- package/dist/src/services/assistantTools.d.ts.map +1 -1
- package/dist/src/services/assistantTools.js +29 -3
- package/dist/src/services/assistantTools.js.map +1 -1
- package/dist/src/services/wereadService.d.ts +33 -6
- package/dist/src/services/wereadService.d.ts.map +1 -1
- package/dist/src/services/wereadService.js +27 -2
- package/dist/src/services/wereadService.js.map +1 -1
- package/docs/BRANCHES.md +13 -4
- package/docs/PROJECT_STATE.md +299 -299
- package/package.json +1 -1
- package/resources/panel/main.cjs +43 -7
- package/resources/panel/mascot-thinking.png +0 -0
- package/resources/panel/panel.css +424 -407
- package/resources/panel/preload.cjs +79 -72
- package/resources/panel/renderer.js +470 -441
- package/scripts/_utils.py +23 -1
- package/scripts/backfill_articles.py +438 -438
- package/scripts/compile_wiki.py +18 -17
- package/scripts/wiki_lint.py +429 -397
- package/src/panel/server.ts +424 -424
- package/src/services/assistantPrivacy.ts +7 -2
- package/src/services/assistantService.ts +11 -1
- package/src/services/assistantTools.ts +33 -3
- package/src/services/wereadService.ts +43 -7
- package/resources/panel/mascot-focus.png +0 -0
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* - 本地推理引擎 (Ollama 等) 不出境, 跳过脱敏以保留完整能力
|
|
8
8
|
*
|
|
9
9
|
* 模式 (config: assistantPrivacy):
|
|
10
|
-
* - open
|
|
11
|
-
* - balanced
|
|
10
|
+
* - open 不脱敏 (自担风险)
|
|
11
|
+
* - balanced 工具输出中的 PII (电话/证件/邮箱/密钥/链接) 打码
|
|
12
12
|
* - strict balanced 基础上, 第三方聊天正文不出境 (仅保留时间/方向/类型)
|
|
13
|
+
*
|
|
14
|
+
* **实测(2026-09-28):新装用户的默认是 `strict`,不是 `balanced`。** 这个键由
|
|
15
|
+
* `configService` 出厂就写成 `'strict'`(`configService.ts` 的默认表与迁移那行都是),
|
|
16
|
+
* 下面 `mode()` 里的 `balanced` 只是**值缺失或不认识时**的兜底。原注释写成「balanced 默认」,
|
|
17
|
+
* 与出厂值矛盾——README 两版跟着写成「strict 可加强」,读起来像默认更弱。两处都已按实际改写。
|
|
13
18
|
*/
|
|
14
19
|
import { join } from 'path'
|
|
15
20
|
import { existsSync, mkdirSync, appendFileSync } from 'fs'
|
|
@@ -13,7 +13,8 @@ import { selectFactsForInjection, frameLocalData } from './assistantMemory.js'
|
|
|
13
13
|
import { configService } from './configService.js'
|
|
14
14
|
import { AssistantMemory, type ChatTurn } from './assistantMemory.js'
|
|
15
15
|
import { privacyGate } from './assistantPrivacy.js'
|
|
16
|
-
import { TOOL_DEFS, availableToolDefs, unavailableToolReason,
|
|
16
|
+
import { TOOL_DEFS, availableToolDefs, unavailableCapabilities, unavailableToolReason,
|
|
17
|
+
executeTool } from './assistantTools.js'
|
|
17
18
|
import type { AttachedImage, ToolContext } from './assistantTools.js'
|
|
18
19
|
import { producedContent } from './assistantTools.js'
|
|
19
20
|
import { recordTurn, describeForChat, summarizeArgs, clipReasoning } from './assistantTrace.js'
|
|
@@ -327,6 +328,15 @@ export class AssistantService {
|
|
|
327
328
|
// 当前时间:**没有它,任何相对时间都是猜**。"上周三""昨天""这周"要变成工具能用的
|
|
328
329
|
// 日期,模型得先知道今天是几号(`get_messages` 的 since/until 就是这么用的)。
|
|
329
330
|
const parts = [BASE_PROMPT, `[当前时间] ${nowLine()}(本机时区)`, this.privacyStateLine()]
|
|
331
|
+
// **没启用的能力要说出来**,否则模型的工具表里看不到它们,用户一问就得到"我没有这个工具"
|
|
332
|
+
// —— 那是把"没配置"说成了"没这功能"(2026-09-28 实测:用户因此以为微信读书没做)。
|
|
333
|
+
// 注意这只是**告诉**它这些存在,它依然调不到;见 `unavailableCapabilities` 的说明。
|
|
334
|
+
const missing = unavailableCapabilities()
|
|
335
|
+
if (missing.length) {
|
|
336
|
+
parts.push('[本机没启用的能力]' + SEP + missing.map(line => `· ${line}`).join(SEP)
|
|
337
|
+
+ SEP + '(这些工具**不在你的工具表里**,但你调不到的原因只是没配置,**不是这个功能不存在**。'
|
|
338
|
+
+ '用户问到时直说"这块还没配置"并给出上面那个配置项,**不要说"我没有这个工具"**。)')
|
|
339
|
+
}
|
|
330
340
|
const summary = this.memory.summary(userId)
|
|
331
341
|
if (summary) {
|
|
332
342
|
parts.push('[此前对话摘要]' + SEP + frameLocalData('memory.summary', summary))
|
|
@@ -785,8 +785,13 @@ export const TOOL_DEFS: ToolDef[] = [
|
|
|
785
785
|
type: 'function',
|
|
786
786
|
function: {
|
|
787
787
|
name: 'get_reading_stats',
|
|
788
|
-
description: '
|
|
789
|
-
+ '
|
|
788
|
+
description: '**公众号**推送与日报处理的统计:哪些号发得多、哪些被日报处理得多。'
|
|
789
|
+
+ '适合"哪个号发得最多""日报处理了多少篇"这类问题。'
|
|
790
|
+
// 原文写的是「回答"我最近都在读什么/哪个号发得最多"这类问题时用它」——
|
|
791
|
+
// 而"我最近在读什么"**正是微信读书的问题**。2026-09-28 实测:用户问"我在读什么书",
|
|
792
|
+
// 模型抓住这一句用了它,返回一串公众号推送量("发得最多的是红星新闻"),
|
|
793
|
+
// 用户看到的就是一个答非所问的答复。工具没错,错在这句说明侵占了读书的问题空间。
|
|
794
|
+
+ '**它统计的是公众号推送与日报,不含读书数据**——"我在读什么书"那类问题不要用它。'
|
|
790
795
|
+ '**日报没在跑的时候它会直说**(那不是"没内容")。',
|
|
791
796
|
parameters: {
|
|
792
797
|
type: 'object',
|
|
@@ -918,6 +923,25 @@ export function unavailableToolReason(name: string, config: (key: any) => any =
|
|
|
918
923
|
return String(config(need.key) || '').trim() ? null : need.why
|
|
919
924
|
}
|
|
920
925
|
|
|
926
|
+
/**
|
|
927
|
+
* 这台机器上**没启用**的能力,一句话一条,给系统提示词用。
|
|
928
|
+
*
|
|
929
|
+
* 为什么需要它:工具被藏起来之后,模型**看不见它存在**,于是用户问"我在读什么书"时它答
|
|
930
|
+
* "我没有 get_weread 这个工具" —— 2026-09-28 实测就是这么一轮对话,用户因此以为功能没做
|
|
931
|
+
* (其实做了、只是没配 key)。把"存在但没启用"告诉模型,它就能给出正确的下一步,
|
|
932
|
+
* 而不是否认这个功能存在。
|
|
933
|
+
*
|
|
934
|
+
* **这不等于把工具摆回去**:模型依然调不到它们,只是知道它们存在。把跑不了的工具放进工具表
|
|
935
|
+
* 的代价是实测过的(模型会去试,答复里带着一串报错,见 `availableToolDefs` 的说明)。
|
|
936
|
+
*/
|
|
937
|
+
export function unavailableCapabilities(
|
|
938
|
+
config: (key: any) => any = configService.get.bind(configService),
|
|
939
|
+
): string[] {
|
|
940
|
+
return Object.entries(TOOL_REQUIREMENTS)
|
|
941
|
+
.filter(([name]) => unavailableToolReason(name, config))
|
|
942
|
+
.map(([, need]) => need.why)
|
|
943
|
+
}
|
|
944
|
+
|
|
921
945
|
/** 这台机器上真正可用的工具表。快路径派发也要过同一道判据(见 `unavailableToolReason`)。 */
|
|
922
946
|
export function availableToolDefs(config?: (key: any) => any): ToolDef[] {
|
|
923
947
|
return TOOL_DEFS.filter(def => !unavailableToolReason(def.function.name, config))
|
|
@@ -1224,8 +1248,14 @@ export async function executeTool(name: string, args: Record<string, any>, ctx:
|
|
|
1224
1248
|
if (mode === 'notebooks') {
|
|
1225
1249
|
const r = await wereadService.notebooks(20)
|
|
1226
1250
|
if (!r.ok || !r.data?.books?.length) return `(暂无读书笔记: ${r.error || ''})`
|
|
1251
|
+
// `/user/notebooks` 把书名与作者**嵌在 `book` 里**,条目顶层没有这两个字段 ——
|
|
1252
|
+
// 实测(2026-09-28):直接读 `b.title` 会得到 `undefined`,而输出照样成立,
|
|
1253
|
+
// 只是每本书都没名字。顶层那两个是兜底,网关换版本时用得上。
|
|
1254
|
+
const titleOf = (b: any) => b?.book?.title || b?.title || '(未给书名)'
|
|
1255
|
+
const authorOf = (b: any) => b?.book?.author || b?.author || ''
|
|
1227
1256
|
return `有笔记的书 ${r.data.books.length} 本:\n` +
|
|
1228
|
-
r.data.books.slice(0, 15).map((b: any) =>
|
|
1257
|
+
r.data.books.slice(0, 15).map((b: any) =>
|
|
1258
|
+
`· ${titleOf(b)} (${authorOf(b)}) — ${b.noteCount || 0} 条笔记`).join('\n')
|
|
1229
1259
|
}
|
|
1230
1260
|
if (mode === 'search') {
|
|
1231
1261
|
const kw = String(args.keyword || '')
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 微信读书 Agent API Gateway 封装
|
|
3
3
|
*
|
|
4
|
-
* 接口文档:
|
|
4
|
+
* 接口文档: https://weread.qq.com/r/weread-skills —— 登录后点「快速配置」,页面上给出
|
|
5
|
+
* `api-key`,并提供一个官方 skill 包(`https://cdn.weread.qq.com/skills/weread-skills.zip`)。
|
|
6
|
+
* **原来的注释指的是 `~/.claude/skills/weread-skills/`,那个目录在本机已不存在**
|
|
7
|
+
* (2026-09-28 核实),照它去找是找不到的。
|
|
5
8
|
* Gateway: https://i.weread.qq.com/api/agent/gateway
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import https from 'node:https'
|
|
12
|
+
import { configService } from './configService.js'
|
|
9
13
|
|
|
10
14
|
const GATEWAY = 'https://i.weread.qq.com/api/agent/gateway'
|
|
11
15
|
const SKILL_VERSION = '1.0.3'
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* 从两个来源里挑出可用的 key(环境变量优先,配置项兜底)。
|
|
19
|
+
*
|
|
20
|
+
* **为什么是两个来源**:助手的 `get_weread` 判据读**配置项** `wereadApiKey`
|
|
21
|
+
* (`assistantTools.ts` 的 `TOOL_REQUIREMENTS`),而 CLI 的 `weread` 命令一直只读
|
|
22
|
+
* **环境变量** `WEREAD_API_KEY`(官方那篇配置文档给的写法就是 export 环境变量)。
|
|
23
|
+
* 只认一个的后果是:用户按 CLI 的提示配好配置项,助手能用、`weread shelf` 却回你
|
|
24
|
+
* "未设置 WEREAD_API_KEY" —— 2026-09-28 实测踩到。
|
|
25
|
+
*
|
|
26
|
+
* 环境变量优先,因为官方文档明确建议"出于安全考虑,最好不要把 api-key 直接扔给 AI"。
|
|
27
|
+
* 两边都空白时返回空串,调用方按"没配"处理(`config set` 允许把值清空)。
|
|
28
|
+
*/
|
|
29
|
+
export function resolveWereadApiKey(fromEnv?: string | null, fromConfig?: string | null): string {
|
|
30
|
+
return String(fromEnv || '').trim() || String(fromConfig || '').trim()
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
// weread.qq.com 服务端不支持 TLS 1.3,需要强制使用 TLS 1.2
|
|
14
34
|
const httpsAgent = new https.Agent({ maxVersion: 'TLSv1.2' })
|
|
15
35
|
|
|
@@ -64,13 +84,20 @@ export interface ReadDetail {
|
|
|
64
84
|
|
|
65
85
|
export interface NotebookItem {
|
|
66
86
|
bookId: string
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
/**
|
|
88
|
+
* **书名与作者嵌在这里,条目顶层没有。** 实测(2026-09-28,`/user/notebooks` 的真实响应):
|
|
89
|
+
* 条目只有 `bookId` / `noteCount` / `bookmarkCount` / `readingProgress` / `sort` / `markedStatus`
|
|
90
|
+
* 等,读 `item.title` 会安静地拿到 `undefined` —— 工具照常输出,只是每本书都没名字。
|
|
91
|
+
*/
|
|
92
|
+
book?: { title?: string; author?: string; cover?: string }
|
|
93
|
+
/** 顶层这两个字段**不是**接口给的,留作兜底:网关换版本时形状可能不同。 */
|
|
94
|
+
title?: string
|
|
95
|
+
author?: string
|
|
96
|
+
cover?: string
|
|
71
97
|
noteCount: number
|
|
72
98
|
bookmarkCount: number
|
|
73
|
-
|
|
99
|
+
readingProgress?: number
|
|
100
|
+
sort?: number
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
export interface NoteDetail {
|
|
@@ -165,7 +192,16 @@ export class WereadService {
|
|
|
165
192
|
private apiKey: string
|
|
166
193
|
|
|
167
194
|
constructor(apiKey?: string) {
|
|
168
|
-
|
|
195
|
+
// 不传 key 时按 `resolveWereadApiKey` 的顺序自己找:环境变量 → 配置项。
|
|
196
|
+
//
|
|
197
|
+
// **配置项这一路是 2026-09-28 补的,而且是端到端才发现的。** 助手的 `get_weread`:
|
|
198
|
+
// 可用性判据读配置项(所以配好之后工具会出现在工具表里),执行时用的却是模块单例
|
|
199
|
+
// `new WereadService()` —— 那个单例只读环境变量。于是工具"看得见、调不动",回一句
|
|
200
|
+
// "未设置 WEREAD_API_KEY"。只改 CLI 那处是不够的:同一个密钥在当时有**三种读法**。
|
|
201
|
+
this.apiKey = apiKey || resolveWereadApiKey(
|
|
202
|
+
process.env.WEREAD_API_KEY,
|
|
203
|
+
String(configService.get('wereadApiKey') || ''),
|
|
204
|
+
)
|
|
169
205
|
}
|
|
170
206
|
|
|
171
207
|
private async call<T>(apiName: string, params: Record<string, any> = {}): Promise<WereadResult<T>> {
|
|
Binary file
|