feishu-user-plugin 1.3.11 → 1.3.13
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/.claude-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +2 -2
- package/.mcpb/manifest.json +3 -3
- package/CHANGELOG.md +159 -8
- package/README.en.md +130 -413
- package/README.md +69 -259
- package/package.json +2 -2
- package/scripts/check-description-drift.js +73 -0
- package/scripts/check-docs-sync.js +7 -16
- package/scripts/check-scopes.js +99 -0
- package/scripts/check-tool-count.js +4 -3
- package/scripts/sync-claude-md.sh +3 -4
- package/scripts/verify-app-name.js +64 -0
- package/skills/feishu-user-plugin/SKILL.md +3 -3
- package/skills/feishu-user-plugin/references/search.md +3 -3
- package/src/auth/credentials-monitor.js +185 -0
- package/src/auth/identity-state.js +209 -0
- package/src/auth/uat.js +49 -35
- package/src/cli.js +87 -0
- package/src/clients/official/base.js +170 -14
- package/src/clients/official/calendar.js +3 -1
- package/src/clients/official/im.js +76 -2
- package/src/clients/official/okr.js +2 -1
- package/src/error-codes.js +40 -0
- package/src/events/lockfile.js +40 -4
- package/src/events/owner.js +11 -2
- package/src/index.js +1 -1
- package/src/logger.js +11 -5
- package/src/oauth.js +65 -14
- package/src/server.js +76 -37
- package/src/test-all.js +41 -0
- package/src/test-cli-tool.js +87 -0
- package/src/test-credentials-monitor.js +124 -0
- package/src/test-display-label.js +88 -0
- package/src/test-error-codes.js +85 -0
- package/src/test-identity-state.js +177 -0
- package/src/test-lark-desktop.js +1 -0
- package/src/test-lockfile-pid.js +90 -0
- package/src/test-lru-cache.js +145 -0
- package/src/test-negative-cache.js +85 -0
- package/src/test-populate-sender-names.js +98 -0
- package/src/test-search-messages.js +101 -0
- package/src/test-send-shape.js +115 -0
- package/src/test-via-user.js +94 -0
- package/src/test-with-uat-retry.js +135 -0
- package/src/tools/_registry.js +24 -1
- package/src/tools/calendar.js +5 -5
- package/src/tools/im-read.js +52 -4
- package/src/tools/messaging-user.js +1 -1
- package/src/utils.js +83 -0
- package/skills/feishu-user-plugin/references/CLAUDE.md +0 -524
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "All-in-one Feishu
|
|
3
|
+
"version": "1.3.13",
|
|
4
|
+
"description": "All-in-one Feishu MCP server + CLI tool for Claude Code — send messages as yourself, read chats (auto-expanded merge_forward), manage docs / bitable / wiki (full CRUD) / drive / OKR (with progress writes) / calendar (read+write) / Tasks v2 / multi-profile auto-switch / real-time WS events. 85 tools + 9 prompts, 3 auth layers.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "EthanQC"
|
|
7
7
|
},
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
3
|
"displayName": "Feishu MCP for Claude Code & Codex",
|
|
4
|
-
"description": "All-in-one Feishu MCP server for Claude Code
|
|
5
|
-
"version": "1.3.
|
|
4
|
+
"description": "All-in-one Feishu MCP server + CLI tool for Claude Code / Codex / Cursor / scripts — 85 tools across 3 auth layers (cookie / app / OAuth). Send as you, read groups, manage docs / bitable / wiki / drive / calendar / tasks / OKR.",
|
|
5
|
+
"version": "1.3.13",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "EthanQC"
|
|
8
8
|
},
|
package/.mcpb/manifest.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "feishu-user-plugin",
|
|
4
4
|
"display_name": "Feishu MCP for Claude Code & Codex",
|
|
5
|
-
"version": "1.3.
|
|
6
|
-
"description": "All-in-one Feishu MCP server for Claude Code
|
|
7
|
-
"long_description": "feishu-user-plugin is a local stdio MCP server that bridges Feishu / Lark and any MCP client (Claude Code, Codex, Cursor, Windsurf, OpenClaw, Claude Desktop). It exposes
|
|
5
|
+
"version": "1.3.13",
|
|
6
|
+
"description": "All-in-one Feishu MCP server + CLI tool for Claude Code / Codex / Cursor / scripts — 85 tools across 3 auth layers (cookie / app / OAuth). Send as you, read groups, manage docs / bitable / wiki / drive / calendar / tasks / OKR.",
|
|
7
|
+
"long_description": "feishu-user-plugin is a local stdio MCP server (and shell CLI tool) that bridges Feishu / Lark and any MCP client (Claude Code, Codex, Cursor, Windsurf, OpenClaw, Claude Desktop). It exposes 85 tools across three auth layers: cookie + protobuf for sending messages as the real user (a capability not available through the official bot API), Feishu Open Platform app credentials for groups / docs / bitable / wiki / drive / calendar / tasks / OKR, and user OAuth (UAT) for P2P chat reading and user-owned resource creation.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "EthanQC",
|
|
10
10
|
"url": "https://github.com/EthanQC"
|
package/CHANGELOG.md
CHANGED
|
@@ -4,23 +4,125 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.3.13] - 2026-05-16
|
|
8
|
+
|
|
9
|
+
紧急 patch — v1.3.12 release 后 Codex + Copilot PR #103 review 发现 1 P1 + 2 P2 + 5 polish,followup 又跑 5-agent 全仓 audit 找出 2 P1 (security) + 多个 doc/compliance 漂移。本版集中修复全部 issue + 把 fixture-based unit tests 拉进 CI gate。
|
|
10
|
+
|
|
11
|
+
> **包含 v1.3.12 全部能力**(4 个 architectural root cause 收口 + `search_messages` UAT-only 工具 + CLI 工具模式 + SEO 改造 + 工程质量 + 战略性微调,85 工具)+ 以下修复。建议跳过 v1.3.12 直接升 v1.3.13。
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
|
|
15
|
+
- **oauth.js token leak(P1)**:`exchangeCode()` 之前会 `console.log('Token exchange raw response:', raw.slice(0, 500))` —— 完整 access_token + refresh_token 进 stderr。`saveToken()` 失败 fallback 路径还会 `console.error(' ${k}=${v}')` 把整个 token 字符串 dump 出来。改成只 log HTTP status + body 长度;fallback 用 `slice(0,6)…(N chars)` redact pattern(同 credentials.js migrate 风格)。
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **P1 — UAT-success 路径错标 viaUser:false(影响 v1.3.12 全部 UAT 写工具)**:`src/auth/identity-state.js` withIdentityFallback UAT 成功路径返回 shallow clone of response,但漏 set `_viaUser: true`。15+ `_asUserOrApp` callsites (calendar/docs/bitable/wiki/okr/tasks/drive) 读 `res._viaUser` 决定显示,没设的话全部 v1.3.12 UAT-owned 写显示 `viaUser:false` + 无 fallbackWarning,用户误以为 bot 创建。Fix:shallow-clone 时加 `_viaUser: true`,加 test-identity-state 断言 pin contract。
|
|
20
|
+
|
|
21
|
+
- **P2 — credentials hot-reload 启动期空窗**:server.js main() 现在启动时调一次 `credMonitor.sync()`(在 verifyApp() 拿 officialClient 之后)。Pre-fix 第一次 sync 永远 silent baselining;server boot 跟 first tool call 之间用户跑 oauth 的话,会被错认为初始 baseline,hook 不 fire。
|
|
22
|
+
|
|
23
|
+
- **P2 — cookie rotation 不 hot-reload**:server.js 现注册 `onCookieChange` hook 把 userClient 设 null。Pre-fix monitor detect LARK_COOKIE 变化但 server.js 没 hook,rotation 后 cookie-based 工具 (send_to_user / search_contacts / get_login_status / send_as_user / batch_send) 继续用 stale cookie 直到重启。
|
|
24
|
+
|
|
25
|
+
- **read_messages via_user=true 错标 via='bot'**:`readMessagesWithFallback` 的 skipBot 分支默认 `via='bot'`,Path B (cookie 解析) 标 `via='contacts'` + reason='contacts_resolved_external'。via_user=true 显式调用混进了 contacts_resolved_external reason。Fix:handler 显式 pass `via: 'user'`;readMessagesWithFallback skipBot 分支按 `via === 'contacts'` 显式判断。
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **observability — _populateSenderNames 加 unresolved id log**:getUserById / getAppName 失败时 return null 而 **不** reject,原 Promise.allSettled rejection log 漏掉这种 case。现在每个 batch 后单独 log 未解析 ids: `sender name unresolved (cached null) for N id(s): ou_xxx, ...`(与 v1.3.12 的 negative-cache sentinel 配合)。
|
|
30
|
+
|
|
31
|
+
### CI / Process
|
|
32
|
+
|
|
33
|
+
- **validate.yml 加 `npm test` + `check-changelog.js`**:之前 14 个 fixture-based unit tests 不在 PR gate 里,任何破坏它们的 PR 都能进 main;CHANGELOG section 缺失也不挡。现在两者都是 PR check 的一部分。
|
|
34
|
+
- **test-lark-desktop.js 接入 npm test**:原是孤儿 standalone script,现在 export run() 被 test-all.js require。
|
|
35
|
+
|
|
36
|
+
### Docs
|
|
37
|
+
|
|
38
|
+
- CLAUDE.md 删除 stale "未实现:search_messages"(v1.3.12 已实装),换成"已删除"段(md ↔ wiki 双向同步 + Mermaid → 画板 都已删)。
|
|
39
|
+
- CLAUDE.md 工具大类计数 reconcile 到 85:Drive 5 → 4,加 "跨域 Uploads (3)" 行,"插件层 4" → "多 profile 3" + 实时事件 2。
|
|
40
|
+
- docs/REFACTOR-NOTES.md tools/ 子树补 tasks.js + events.js(v1.3.7 / v1.3.9 加但 doc 一直漏);smoke 契约 "当前 84" → "当前 85";events/ 子树第 layout 段已在 v1.3.12 加入。
|
|
41
|
+
- docs/TOOLS.md IM section 工具列表补 `search_messages`;Drive section 拆成 Drive 4 + Uploads 3 跟实际 src/tools/ 一致。
|
|
42
|
+
- docs/COMPARISON.md / CONTRIBUTING.md / .github/pull_request_template.md:84 → 85;COMPARISON.md "本仓:最新 v1.3.11" → v1.3.12。
|
|
43
|
+
|
|
44
|
+
### 其他 polish
|
|
45
|
+
|
|
46
|
+
- docs/CLIENT-COMPAT.md(v1.3.12 加的):标题 "5 客户端" → "7 客户端";Tools 列 ✓ 84 → ✓ 85;`feishu-user-plugin-1.3.11.mcpb` 改成 version-agnostic placeholder。
|
|
47
|
+
- scripts/verify-app-name.js:错误 URL 插入实际 appId(之前是 `<appId>` 字面)。
|
|
48
|
+
- src/test-lru-cache.js:fix stale header 引用 `src/utils/lru-cache.js` → `src/utils.js`。
|
|
49
|
+
|
|
50
|
+
### Test scenarios
|
|
51
|
+
|
|
52
|
+
- `npm test`:14 个 fixture-based test 全 pass(包含 v1.3.13 加的 `test-lark-desktop` wiring + identity-state `_viaUser=true` 断言)
|
|
53
|
+
- `node scripts/verify-app-name.js`:当前 APP self_manage scope 已开 → 输出 `OK — app name resolves to "Claude聊天助手"`;错误路径打印的修复 URL 含实际 cli_xxx appId
|
|
54
|
+
- 重启 Claude Code / Codex 后跑任何 UAT-owned 写工具(如 `create_doc` / `create_bitable` / `create_calendar_event` / `update_task`)→ 响应里 `viaUser:true`(而非 v1.3.12 的 `viaUser:false`)
|
|
55
|
+
- 跑 `npx feishu-user-plugin oauth` 后**不重启**,下次 `get_login_status` 立即 Valid(hot-reload 启动期空窗已修)
|
|
56
|
+
- 改 credentials.json 里的 LARK_COOKIE 字段后**不重启**,下次 cookie 工具如 `send_to_user` 会用新 cookie(onCookieChange hook 已注册)
|
|
57
|
+
|
|
58
|
+
## [1.3.12] - 2026-05-15
|
|
59
|
+
|
|
60
|
+
主线:4 个 architectural root cause(A scope drift / B silent fallback / C LLM-unfriendly 数据 / D hot-reload 缺失)一次性收口 + 1 个新工具 `search_messages`(B.5 Protobuf 阶段二)+ CLI 工具模式(`tool` 子命令,复用 85 工具)+ SEO 改造(README h1 + repo description + 4 GitHub topics)+ 5 项工程质量(gitleaks 防 secret 误提交 / CHANGELOG 回填 v1.3.0-v1.3.2 / 客户端兼容矩阵 / 战略性微调 ×2)。工具数 84 → 85。
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- **`search_messages` 工具(v1.3.12 B.5, UAT-only)**:包 `POST /open-apis/search/v2/message`。Probe 2026-05-15 确认飞书暴露该 endpoint,需 OAuth scope `search:message`(同期加入 `src/oauth.js` SCOPES + `docs/AUTH-SETUP.md` 表)。Filter 支持 `chat_ids` / `from_ids` / `at_user_ids` / `message_types` / `from_types` + 分页。返回 `{items, pageToken, hasMore}` 的 message-id 指针(不是 full bodies)—— 跨多群搜索时 response token 友好;caller 再调 `read_messages(chat_id)` 拿 full content。99991679 error 给明确 scope 指引("re-run npx feishu-user-plugin oauth")。
|
|
65
|
+
- **CLI 工具模式 `npx feishu-user-plugin tool …`(v1.3.12 形态扩展)**:复用 `src/server.js` HANDLERS + 新 export 的 `buildCtx()`,CLI 跟 MCP 走同一代码同一 ctx 装配。3 个子命令:`tool list`(列 85 工具名)/ `tool help <name>`(schema + description)/ `tool <name> '<json-args>'`(dispatch + 输出 response text 到 stdout)。新 `docs/CLI.md` 文档(用法 / cron / pipeline / 已知限制)。`src/logger.js` stdout guard 从 module-load side-effect 改成 opt-in `installStdoutGuard()`,CLI 模式不调它 → 结构化输出走真 stdout 供 `jq` / shell pipe 用;MCP server 模式(`src/index.js`)依旧首行调用 guard。
|
|
66
|
+
- **IdentityState 状态机 + `withIdentityFallback`(v1.3.12 root cause B)**:新模块 `src/auth/identity-state.js` —— 6 态枚举(VALID_USER / UAT_EXPIRED / UAT_REVOKED / UAT_MISSING_SCOPE / BOT_ONLY / NO_CREDENTIALS)+ `resolveIdentity(client)` 30s cache + `withIdentityFallback({client, uatFn, botFn, label})` 返回 `{data, via, viaReason, identity, fallbackWarning}`。`asUserOrApp` 内部 routes 过去;外部 shape 完全不变(`_viaUser` / `_fallbackWarning` / `.uatSummary` / `.appError` alias 都保留),15+ callsite 在 calendar / docs / bitable / wiki / okr / tasks / drive / im 无感升级。失败时 UAT 端返回 20064 → `UAT_REVOKED`、99991668 → `UAT_MISSING_SCOPE`、99991663 → `UAT_EXPIRED`,bot 端 fallback 时给 LLM 一行明确的 `viaReason` 而非静默吞错。
|
|
67
|
+
- **CredentialsMonitor hot-reload(v1.3.12 root cause D)**:新模块 `src/auth/credentials-monitor.js` —— factory `createCredentialsMonitor({path?})`,每个 tool-call entry 跑 `sync()`,stat mtime + 对 active profile 的 UAT / refresh / cookie 字段 hash 比对。Diff 触发 4 hook 之一:`onUatChange(env)` reload `officialClient._uat/_uatRefresh/_uatExpires` + invalidate identity cache;`onCookieChange(env)`(暂 no-op,userClient 下次自然 re-init);`onProfileSwitch({from,to,env})` flip in-memory `currentProfile` + null client + clear resolver wiki cache;`onCacheInvalidate(env)` 清 identity-state cache。取代了 v1.3.9 的 `_syncActiveProfileFromDisk`(只看 active 字段)+ 取代了"重启 Claude Code 才能 reload UAT"的人工补救。`switch_profile` handler 改调 `credMonitor.sync()` 让 cross-process 同步走统一 baseline。
|
|
68
|
+
- **LRUCache 跨切面 helper(v1.3.12 root cause D 配套)**:`src/utils.js` 新 class,max + ttlMs(默认 500 / 10 min)。完整 Map-shaped API:`set`/`get`/`has`/`delete`/`clear` + `Symbol.iterator` + `entries`/`keys`/`values` generators(自动跳过 expired)。替代 `_userNameCache` / `_appNameCache` 之前的 unbounded `new Map()`。Negative-cache sentinel(v1.3.12 self-review followup):失败 lookup 写 null sentinel 防止下次 read_messages 重发 N 个 API 调用。
|
|
69
|
+
- **PID liveness check for ws-owner lock(v1.3.12 root cause D 配套)**:`src/events/lockfile.js::acquireLongLived` 在 mtime fresh 时也读 body pid + `process.kill(pid, 0)`:ESRCH 立即 steal(之前要等 60s mtime 超时)。`src/events/owner.js::readOwnerInfo` 综合 `alive = mtimeFresh && pidAlive` —— SIGKILL'd owner 的 lock 在下个 30s takeover poll 就被回收。malformed body / 缺 pid 字段 fall back to mtime-only check(向后兼容)。
|
|
70
|
+
- **scope drift CI guard(v1.3.12 root cause A)**:`scripts/check-scopes.js` 从 `src/oauth.js` SCOPES 提 token list + 跟 `docs/AUTH-SETUP.md` 校对每条 scope 都 mentioned + banlist 已知错名(`calendar:calendar.event:write` / `okr:okr.content:write`)。修正 4 个 calendar scope(write → create / update / delete / reply)+ okr write → writeonly + 加 `im:resource` / `contact:contact.base:readonly` / `search:message`。完整 30 OAuth + 1 tenant-only 表落地 `docs/AUTH-SETUP.md`。CI `.github/workflows/validate.yml` 每个 PR 跑 check-scopes。
|
|
71
|
+
- **`displayLabel` + sender semantics pack(v1.3.12 root cause C)**:`read_messages` / `read_p2p_messages` 每条消息新增 `displayLabel`(如 `周宇` / `[Bot] Claude聊天助手` / `[Bot] (cli_xxx)` / `[匿名]` / `[系统]` / `[已撤回] 怪兽`)+ `senderIdType` / `senderTenantKey` / `isExternal` / `isRecalled` / `isThreadReply`。merge_forward children 加 `forwardedFromChatName`(im.js best-effort getChatInfo on each `originChatId`)。`_populateSenderNames` 新 Step 0 mention-name harvest(零 API 成本)+ Step 1 lazy self tenant_key resolve + Step 4 fill new fields。新 method `getAppName(cli_xxx)` 用 tenant-side `application:application:self_manage` scope 解析 bot 显示名(免审 scope,自助开通)。
|
|
72
|
+
- **gitleaks 防 cookie 误提交**:`.gitleaks.toml` 4 rules(`LARK_APP_SECRET` / `LARK_COOKIE` / `LARK_USER_ACCESS_TOKEN` / `LARK_USER_REFRESH_TOKEN`)+ allowlist 文档示例 / 测试 fixture。`.husky/pre-commit` 装了就跑(macOS `brew install gitleaks`),CI 工作流自 curl install gitleaks v8.30.1 binary。verified clean across 266 commits / 3.76 MB。
|
|
73
|
+
- **client compat matrix `docs/CLIENT-COMPAT.md`**:7 个 MCP 客户端 × 9 prompts × 85 tools 兼容表 + 已知 client-specific caveat(Codex 不支持 Claude Code skill;VS Code key 是 `servers` 不是 `mcpServers`;Cursor / Windsurf prompt UI 还在迭代等)+ 3 步复测 procedure(`/status` + `/send` + `list_chats`)。
|
|
74
|
+
- **`docs/ARCHITECTURE-NOTES.md` reference 永久化**:B / D 设计文档(5 态枚举 / 30s cache / 4 hook registry)作为实施 reference 永久保留 + 给未来类似 root cause 留 reusable pattern。14 根因清单全表 ⏸→✅。
|
|
75
|
+
- **CHANGELOG v1.3.0 / v1.3.1 / v1.3.2 entries 回填**:从 `git log --format='%H %ci %s' v1.3.0..v1.3.2` + tag commit body 重建 3 个 entry,跟 v1.3.6+ 同结构。CHANGELOG 现在从 v1.3.0 起连续。
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- **`send_*_as_user` 8 工具统一返回 shape**:`{ok, viaUser, description?, status?, messageId?, fallbackWarning?}`(pre-v1.3.12 是 plain text "Text sent as user to oc_xxx",LLM / 脚本要 regex)。`sendResult` 接受 options form `sendResult(r, {desc, viaUser, fallbackWarning})`,back-compat 旧 `sendResult(r, descString)`。`send_card_as_user` 标 `viaUser: false`(cookie 不发卡片,仅 bot)。
|
|
80
|
+
- **`read_messages` 加 `via_user: boolean`**:`true` skip bot 直接 UAT;`false` skip UAT fallback bot-only(之前没此选项);undefined = 现有 auto-fallback。`readMessagesWithFallback` 加 `skipUat` 配套(与 `skipBot` 互斥)。Path B (cookie search_contacts 解析) 在 `via_user=false` 时短路报错而非继续走 UAT。
|
|
81
|
+
- **`withUAT` retry 集合扩展**:在 `classifyError(thrown).action === 'retry'` 时一次性重试(同 UAT),coverage 涵盖 ECONNRESET / fetch timeout / JSON parse error。原 99991668 / 99991663 / 99991677 refresh-and-retry 路径不变。
|
|
82
|
+
- **FAILURE_MAP 扩展**:加 20064 (uat_revoked, symmetry-only — 真触发在 identity-state)、91403 (bot_cross_tenant)、1254000/1/301/400 (upload_transient)。TRANSIENT_PATTERNS 加 JSON parse error 识别,`classifyError` 输出独立 reason `response_parse_error` 让监控可区分。
|
|
83
|
+
- **`_userNameCache` / `_appNameCache` Map → LRUCache**:500 / 10min(用户)+ 100 / 10min(app)。原 unbounded Map 在长跑 server 上无限增长 + 永不过期,rename 的用户名字一周后还显示旧的。
|
|
84
|
+
- **README h1 + repo description + GitHub topics 加 cli/mcp**:h1 「飞书 MCP 服务器 + CLI 工具」+ description 加"CLI tool" + topics 加 `cli` / `feishu-cli` / `mcp-server` / `feishu-mcp`。
|
|
85
|
+
- **工具数 84 → 85**:仅加 `search_messages`,其他都是已有 schema 改 description / field(不算 schema 新增)。
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **`Promise.allSettled` 不读 status 导致 sender 解析失败被静默吞**(root cause #8):`_populateSenderNames` 现读每个 result.status,rejected ids 进 stderr `[feishu-user-plugin] sender name lookup failed for N/M ids: ou_xxx(<reason>)...`。同一份 fix 同时对 user 路径 + app 路径生效。
|
|
90
|
+
- **解析失败的 sender id 不 cache 导致下次 read_messages 重复 API call**(pre-existing perf bug, v1.3.12 self-review followup):`_populateSenderNames` 在每个 Promise.allSettled 后给未解析的 id 写 null sentinel。同 LRU TTL 10 min 给 rename 的 sender 一个 re-resolve 窗口。
|
|
91
|
+
- **oauth.js race condition (PR #45 P2)**:`saveToken` 在 OAuth callback 时不再重读 `getActiveProfileName()`,而是用 module-init 时 captured 的 `RESOLVED_PROFILE`。原 race:OAuth 期间另一进程 `switch_profile` 切了 active,token 会写错 profile。
|
|
92
|
+
- **`oauth.js::getAppInfo` silent catch 99991672**:原 `try {...} catch {}` 完全不报;改成 stderr warn 指明缺 tenant-side `application:application:self_manage` scope + 影响 `displayLabel`。新 `scripts/verify-app-name.js` 一次性诊断脚本可手动验证 scope 已开(exit 0 / 1 / 2 三种状态)。
|
|
93
|
+
- **server.js `onUatChange` 直接裸写 client 内部字段**(v1.3.12 self-review followup):改调 `loadUATFromEnv(officialClient, env)` helper(已存在,statup 用同一个)。`loadUATFromEnv` 扩展为支持 clear-on-empty(env 无 token → 主动 nil 内存里的 stale token)。
|
|
94
|
+
- **`LRUCache` 缺 `Symbol.iterator`**(v1.3.12 self-review followup):原注释说 "API-compatible with the old Map" 但 spread / for-of 会 TypeError。加 iterator + entries/keys/values generators(自动跳过 expired)。
|
|
95
|
+
|
|
96
|
+
### Test scenarios
|
|
97
|
+
|
|
98
|
+
- 重启后跑 `read_messages` 看每条消息有 `displayLabel` + bot 消息形如 `[Bot] Claude聊天助手`(非 cli_xxx)+ recall 消息有 `[已撤回] ` 前缀 + cross-tenant 有 `isExternal: true`
|
|
99
|
+
- 跑 `npx feishu-user-plugin oauth`,**不重启**,下次 `get_login_status` UAT 应立即 Valid(D2 CredentialsMonitor onUatChange hook 工作)
|
|
100
|
+
- 在 Lark Desktop / shell 操作让 multiple MCP server 进程并发 → 它们的 in-memory UAT 通过 credentials.json 自动同步(D2 + B identity refine)
|
|
101
|
+
- 模拟 SIGKILL'd MCP server(kill -9 持有 ws-owner.lock 的进程)→ 另一进程在 30s 内 takeover(D4 PID liveness check)
|
|
102
|
+
- `gitleaks detect --config .gitleaks.toml`:跑过 266 commits / 3.76 MB,0 leaks
|
|
103
|
+
- `npx feishu-user-plugin tool list`:返回 85 tool names exit 0
|
|
104
|
+
- `npx feishu-user-plugin tool get_login_status '{}'`:返回 status 文本到 stdout
|
|
105
|
+
- `npx feishu-user-plugin tool search_messages '{"query":"周报"}'`:需 user 先 `npx oauth` 拿 `search:message` scope;之后 dispatch 该 endpoint 返回 items
|
|
106
|
+
- `node scripts/verify-app-name.js` 当前 APP 已开 self_manage scope → 输出 `OK — app name resolves to "Claude聊天助手"`
|
|
107
|
+
- `node src/test-all.js` 跑 19 个 fixture-based unit test 全 pass(含 v1.3.12 新加的 9 个:test-error-codes / test-identity-state / test-with-uat-retry / test-populate-sender-names / test-credentials-monitor / test-lru-cache / test-lockfile-pid / test-negative-cache / test-send-shape / test-via-user / test-search-messages / test-cli-tool)
|
|
108
|
+
|
|
7
109
|
## [1.3.11] - 2026-05-09
|
|
8
110
|
|
|
9
|
-
|
|
111
|
+
主线:Lark Desktop 多账号无感切换 — 在 Feishu Desktop 切账号,MCP 在 ~15 s 内自动跟到对应 profile。同期完成三项上架基建:MCP Registry CI 自动 publish 在 v1.3.11 头号 release 已自动跑通(`registry.modelcontextprotocol.io` 现 isLatest=1.3.11);Anthropic `.mcpb` 包 + `PRIVACY.md` 与 Cursor `.cursor-plugin/plugin.json` 仓库材料就绪、已上 npm,剩余只待用户去外部平台填表单。工具数 84 不变。
|
|
10
112
|
|
|
11
113
|
### Added
|
|
12
|
-
- **Lark Desktop 多账号无感切换 (A)**:用户在 Feishu Desktop 切换账号 → MCP
|
|
13
|
-
-
|
|
14
|
-
- **`.
|
|
15
|
-
-
|
|
16
|
-
- **MCP Registry CI 自动 publish(v1.4 prep)**:`.github/workflows/publish.yml` 增 mcp-publisher 步骤,`v*` tag 触发 → curl 安装 `mcp-publisher` → `login github-oidc`(runner OIDC token,无需 PAT)→ `publish mcp-registry.json`。`scripts/check-mcp-registry-version.js` 校验 `mcp-registry.json::version` + `packages[0].version` 与 `package.json::version` 一致;接进 `publish.yml`(pre-publish)+ `validate.yml`(PR-time)。从下个 release 起 Registry 同步零人工。
|
|
114
|
+
- **Lark Desktop 多账号无感切换 (A)**:用户在 Feishu Desktop 切换账号 → MCP 自动跟到对应 profile,零 CLI 命令、零工具调用。`credentials.json::profiles[*].larkHash` 字段绑 profile ↔ Lark `~/Library/.../sdk_storage/<hash>/`;owner heartbeat (15 s) `stat cookie_store.db` mtime,最近活跃 hash 与当前 active 不一致 + mtime 推进时调 `setActiveProfile`(5 s debounce)。`setup` 在 `fresh` / `update` 模式下自动绑定(单账号直接绑、多账号在交互模式下用户选 / 非交互取最近活跃 + 在 stderr 列出其它),新 flag `--bind-hash <hex>` 显式绑定 / `--no-bind-hash` 跳过。未绑定但活跃的 hash 在 stderr 打一次性提示带 `setup --profile <name> --bind-hash <hash>` 命令。Lark 加密 `cookie_store.db` 全程不读不解密;cookie 仍由 `LARK_COOKIE` 按 profile 单独提供。macOS-only;Linux / Windows 默认 no-op。新模块 `src/auth/lark-desktop.js`(`getSdkStorageDir` / `listAccountHashes` / `mostRecentHash` / `detectSwitch`)+ `credentials.js` 新 API(`getProfileLarkHash` / `setProfileLarkHash` / `findProfileByHash`)+ `server.js` heartbeat 反应器接入;13 个 fixture-based 单元测试 `src/test-lark-desktop.js` 不依赖真 Lark Desktop 安装即可跑。
|
|
115
|
+
- **`.mcpb` 桌面扩展打包 + `PRIVACY.md`**:仓库具备 Anthropic Connectors Directory 收录所需所有材料;`node scripts/build-mcpb.js` 产出 `dist/feishu-user-plugin-1.3.11.mcpb`(250 KB),可上传 https://clau.de/desktop-extention-submission(剩余待用户填表单,ROADMAP `v1.3.12 / 上架提交`)。`.mcpb/manifest.json` 走 `manifest_version=0.3` schema(顶层 `server.mcp_config` + `user_config` 块声明 5 个 `LARK_*` 全 `sensitive=true`,Claude Desktop UI 自动提示用户填凭证后通过 `${user_config.KEY}` 替换到 `mcp_config.env`);`PRIVACY.md` 中英双语 6 维度(采集 / 处理 / 存储 / 第三方 / 留存 / 联系),README 加 "## 隐私 / Privacy" 段;CI gate `scripts/check-mcpb-version.js` 接进 `validate.yml` 校验 `.mcpb/manifest.json::version` 与 `package.json::version` 一致。
|
|
116
|
+
- **`.cursor-plugin/plugin.json` + 4 源版本三角**:仓库具备 Cursor Marketplace 收录所需 manifest(`mcpServers` 块镜像 README 的 Claude Code 配置),可去 https://cursor.com/marketplace/publish 提交(剩余待用户填表单,ROADMAP `v1.3.12 / 上架提交`)。校对 `cursor/plugins` 官方 schema 修正 prep 文档错误:`author` 实际是 `{name, email}` with `additionalProperties: false`(非 `{name, url}`),`repository` 是字符串(非 `{type, url}`)。`scripts/check-version.js` 由 3 源扩 4 源版本三角(`package.json` / `.claude-plugin/plugin.json` / `SKILL.md` / `.cursor-plugin/plugin.json`),任一源 mismatch 即 CI fail。
|
|
117
|
+
- **MCP Registry CI 自动 publish**:`v1.3.11` 头号 release 已自动跑通——`registry.modelcontextprotocol.io` 现 isLatest=1.3.11;以后每次 tag 推送自动同步,零人工。`.github/workflows/publish.yml` 增 mcp-publisher 步骤(curl 安装 `mcp-publisher` 二进制 → `login github-oidc`,runner OIDC token 取代 PAT → `publish mcp-registry.json`),publish job 加 `permissions: id-token: write`。`scripts/check-mcp-registry-version.js` 在 `publish.yml`(pre-publish)+ `validate.yml`(PR-time)双闸门校验 `mcp-registry.json::version` + `packages[0].version` 与 `package.json::version` 一致。
|
|
17
118
|
|
|
18
119
|
### Test scenarios
|
|
19
120
|
- 单 profile + 单 hash:`setup` 自动绑定,stderr 一行 `Bound profile "default" to Lark account hash <hex>`,无后续噪声
|
|
20
121
|
- 多 profile + 多 hash:在 Lark Desktop 切到 profile B 绑的账号 → 15 s 内 stderr 出 `Lark Desktop account changed; switching profile to "B"` → `credentials.json::active` 更新 → 下一次工具调用走 B 的凭证
|
|
21
122
|
- 未绑定但活跃 hash:在 Lark Desktop 切到一个新账号 → stderr 出一次性提示带 `setup --profile <name> --bind-hash <hash>` 命令;后续 heartbeat 不再重复
|
|
22
|
-
- 非 darwin:`getSdkStorageDir()` 返回 null
|
|
23
|
-
-
|
|
123
|
+
- 非 darwin:`getSdkStorageDir()` 返回 null,反应器全部 no-op;`setup --no-bind-hash` 显式跳过
|
|
124
|
+
- `node scripts/build-mcpb.js` 产出 `dist/feishu-user-plugin-1.3.11.mcpb`,`unzip -p` 验证 `manifest.json` 在 archive 根
|
|
125
|
+
- `curl 'https://registry.modelcontextprotocol.io/v0/servers?search=feishu-user-plugin'` 返回 v1.3.11 isLatest=true
|
|
24
126
|
|
|
25
127
|
## [1.3.10] - 2026-05-09
|
|
26
128
|
|
|
@@ -202,6 +304,55 @@ D 系列首项 ship:新增 `read_doc_markdown` 工具,用 `feishu-docx` 把
|
|
|
202
304
|
- `getWikiNode(nodeToken, _spaceId)` — `spaceId` parameter position swapped; retained only for backward-compatibility of any external caller. The endpoint itself ignores `space_id`.
|
|
203
305
|
- `create_doc_block` no longer requires `children` — callers who use the new `image_path` or `image_token` shortcut omit it. One of `children` / `image_path` / `image_token` must be provided.
|
|
204
306
|
|
|
307
|
+
## [1.3.2] - 2026-04-17
|
|
308
|
+
|
|
309
|
+
主线:以"真用户身份"补两个 longstanding gap —— 用户消息的 @-mention 现在真能通知到人 + 用户身份创建的 docx / bitable 资源现在真归你(不是 app)。
|
|
310
|
+
|
|
311
|
+
### Fixed
|
|
312
|
+
- **@-mentions 作为用户发送时不通知**:飞书 Web bundle 反向工程发现 `RichText` 需要 `atIds[]` (field 6) 注册 AT element ids,没有这个字段后端会把 `user_id` 清空。`proto/lark.proto::RichText` 扩字段(`atIds` / `anchorIds` / `imageIds` 等),加上真正的 `AtProperty` / `AnchorProperty` message。Live 测试:bot-API 回读现在保留 `user_id` + `user_name`(之前两个都空字符串)。
|
|
313
|
+
- **`create_doc` / `create_bitable` 创建后归属错乱**:所有 docx / bitable / drive 操作改走 UAT-first → app fallback(新 helper `_uatREST` + `_asUserOrApp`)。修复 1770032(docx forbidden)+ 91403(bitable forbidden)—— 之前 UAT 创建的资源用 app 路径打开会 403,因为根本不是 UAT 创的。
|
|
314
|
+
|
|
315
|
+
### Added
|
|
316
|
+
- **`ats: [{userId, name}]` 参数**给 `send_as_user` / `send_to_user` / `send_to_group` / `send_post_as_user`:在 TEXT 消息里 splice @-mention(marker `@<name>`);在 POST/RichText 消息里 `sendPost` 把 AT elem ids 汇到 `richText.atIds`,AT 编码用 `AtProperty`。
|
|
317
|
+
- **`_formatMessage` surface `mentions[]`**:`im.message` payload 里 mentions 数组现在被 `read_messages` / `read_p2p_messages` 透传出来,供下游用 mention 的 name 直接 narrate 而不用再查 contact API。
|
|
318
|
+
|
|
319
|
+
### Changed
|
|
320
|
+
- Docs synced:`CLAUDE.md` / `skills/feishu-user-plugin/references/CLAUDE.md` / `.claude-plugin/plugin.json` 全部更新 @-mention 用法 + UAT-first 行为说明。
|
|
321
|
+
- Removed redundant per-resource as-user wrappers:`createDocAsUser` / `createBitableAsUser` / `createFolderAsUser` 删除,被 `_asUserOrApp` 统一替代。
|
|
322
|
+
|
|
323
|
+
## [1.3.1] - 2026-04-17
|
|
324
|
+
|
|
325
|
+
主线:MCP 稳定性 root fix + 用户身份创建 + Codex 双客户端支持 + 工具表收敛(81 → 66,去 calendar/tasks 这种 app 权限未开通的伪能力)。
|
|
326
|
+
|
|
327
|
+
### Fixed
|
|
328
|
+
- **MCP 中途掉线(root cause #1)**:Lark SDK 的 logger 默认写 stdout 污染了 JSON-RPC channel。`src/index.js` 启动把 SDK logger 改写到 stderr(PR #2 by [@ZYAH111](https://github.com/ZYAH111))。
|
|
329
|
+
- **uncaughtException / unhandledRejection 兜底**:MCP server 不再因为单个 tool handler 抛错而整个 crash —— 进程级 handler 把错误吐到 stderr,server 继续接 next request。
|
|
330
|
+
- **config 写入 race**:`atomicWrite(tmp + rename)` 替代直接 fs.writeFile,防止 Claude Code spawn 多 MCP server 时并发改 `~/.claude.json` 互相覆盖。
|
|
331
|
+
|
|
332
|
+
### Added
|
|
333
|
+
- **UAT-first creation**:`create_doc` / `create_bitable` / `create_folder` 现在用 `LARK_USER_ACCESS_TOKEN` 走 UAT 路径创建,资源归用户而非 app。
|
|
334
|
+
- **Codex TOML 支持**:`npx feishu-user-plugin setup --client codex|both` 写 `~/.codex/config.toml::mcp_servers`。新增 `scripts/mcp_stdio_bridge.js` 适配 Codex 协议差异。
|
|
335
|
+
- **3-layer 版本确认**:CLAUDE.md 规则 + `prepublishOnly` script + CI tag check,三层保护防版本号 drift。
|
|
336
|
+
- **5 个新 bitable 工具**:`get_bitable_meta`、`copy_bitable_app`、`update_bitable_table`、`create_bitable_view`、`delete_bitable_view`。
|
|
337
|
+
|
|
338
|
+
### Changed
|
|
339
|
+
- **工具数 81 → 66**:移除 calendar(5) + tasks(5) 工具 —— 飞书 app 权限管理后台对应 scope 未开通,工具调用 100% 失败。后续在 v1.3.4 重新加回时 app 权限已申请。
|
|
340
|
+
- **合并 pin/unpin → `pin_message(action='pin'|'unpin')`**,`add/remove_members → manage_members(action=...)`。
|
|
341
|
+
- **吸收单 record CRUD 到 batch tools**:`create_bitable_record` → `batch_create_bitable_records(records=[<one>])` 等。
|
|
342
|
+
- **OAuth scopes**:加 `docx:document`、`drive:drive` write 权限。
|
|
343
|
+
|
|
344
|
+
## [1.3.0] - 2026-04-03
|
|
345
|
+
|
|
346
|
+
主线:tool surface 一次性扩张 46 → 76(+30)—— bot messaging 全套、docx block 编辑、calendar / tasks / drive 操作首次纳入。
|
|
347
|
+
|
|
348
|
+
### Added
|
|
349
|
+
- **IM 域(13 工具)**:`send_message_as_bot`、`delete_message`、`update_message`、`add_reaction`、`delete_reaction`、`pin_message`、`unpin_message`、`create_group`、`update_group`、`list_members`、`add_members`、`remove_members`。
|
|
350
|
+
- **Docx block 编辑(3 工具)**:`create_doc_block`、`update_doc_block`、`delete_doc_blocks` —— 飞书 docx 的原子编辑单元。
|
|
351
|
+
- **Bitable(2 工具)**:`get_bitable_record`(按 record_id 取一条)、`delete_bitable_table`。
|
|
352
|
+
- **Drive 操作(3 工具)**:`copy_file`、`move_file`、`delete_file`。
|
|
353
|
+
- **Calendar(5 工具)**:`list_calendars`、`create_calendar_event`、`list_calendar_events`、`delete_calendar_event`、`get_freebusy`。(v1.3.1 因 scope 未开通暂时下线,v1.3.4 加回。)
|
|
354
|
+
- **Tasks(5 工具)**:`create_task`、`get_task`、`list_tasks`、`update_task`、`complete_task`。(v1.3.1 因 scope 未开通暂时下线,v1.3.7 v2 API 重做。)
|
|
355
|
+
|
|
205
356
|
## [1.3.3] - 2026-04-20
|
|
206
357
|
|
|
207
358
|
### Fixed
|