feishu-user-plugin 1.3.12 → 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 +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.mcpb/manifest.json +1 -1
- package/CHANGELOG.md +51 -0
- package/package.json +1 -1
- package/scripts/verify-app-name.js +1 -1
- package/skills/feishu-user-plugin/SKILL.md +2 -2
- package/src/auth/identity-state.js +6 -1
- package/src/clients/official/base.js +27 -2
- package/src/oauth.js +19 -4
- package/src/server.js +16 -0
- package/src/test-all.js +1 -0
- package/src/test-identity-state.js +5 -0
- package/src/test-lark-desktop.js +1 -0
- package/src/test-lru-cache.js +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.13",
|
|
4
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"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
3
|
"displayName": "Feishu MCP for Claude Code & Codex",
|
|
4
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.
|
|
5
|
+
"version": "1.3.13",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "EthanQC"
|
|
8
8
|
},
|
package/.mcpb/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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.
|
|
5
|
+
"version": "1.3.13",
|
|
6
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
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": {
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ 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
|
+
|
|
7
58
|
## [1.3.12] - 2026-05-15
|
|
8
59
|
|
|
9
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。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
3
|
"mcpName": "io.github.EthanQC/feishu-user-plugin",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.13",
|
|
5
5
|
"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.",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -51,7 +51,7 @@ async function main() {
|
|
|
51
51
|
if (info.code === 99991672) {
|
|
52
52
|
console.error('FAIL — code 99991672. The tenant-side scope `application:application:self_manage` is not granted.');
|
|
53
53
|
console.error('Fix:');
|
|
54
|
-
console.error(
|
|
54
|
+
console.error(` 1. Open https://open.feishu.cn/app/${appId}/safe — "应用身份" tab`);
|
|
55
55
|
console.error(' 2. Add scope `application:application:self_manage` (marked 免审权限 — no admin review needed)');
|
|
56
56
|
console.error(' 3. Save; no re-publish required');
|
|
57
57
|
console.error(' 4. Re-run this script to confirm');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feishu-user-plugin
|
|
3
|
-
version: "1.3.
|
|
4
|
-
description: "All-in-one Feishu MCP server + CLI tool — send messages as yourself (incl. batch_send), read group/P2P chats (auto-expands merge_forward), manage docs/tables/wiki (full CRUD)/drive, OKR (with progress writes), calendar (read+write), Tasks v2, multi-profile auto-switch, real-time WS events. v1.3.
|
|
3
|
+
version: "1.3.13"
|
|
4
|
+
description: "All-in-one Feishu MCP server + CLI tool — send messages as yourself (incl. batch_send), read group/P2P chats (auto-expands merge_forward), manage docs/tables/wiki (full CRUD)/drive, OKR (with progress writes), calendar (read+write), Tasks v2, multi-profile auto-switch, real-time WS events. v1.3.13: search_messages tool (Protobuf phase 2, UAT-only), CLI tool mode (tool list / help / dispatch), IdentityState state machine + credentials hot-reload (UAT viaUser flag preserved across the 15+ write tools, no-restart UAT/cookie reload, startup-blind-window closed), displayLabel + sender semantics pack, WS owner PID liveness, gitleaks secret scan, oauth.js token-leak hardening, fixture unit tests pulled into CI gate."
|
|
5
5
|
allowed-tools: send_to_user, send_to_group, send_as_user, send_image_as_user, send_file_as_user, send_post_as_user, batch_send, send_card_as_user, search_contacts, create_p2p_chat, get_chat_info, get_user_info, get_login_status, list_profiles, switch_profile, manage_profile_hints, read_p2p_messages, list_user_chats, list_chats, read_messages, search_messages, send_message_as_bot, reply_message, forward_message, delete_message, update_message, add_reaction, delete_reaction, pin_message, create_group, update_group, list_members, manage_members, search_docs, read_doc, get_doc_blocks, create_doc, manage_doc_block, read_doc_markdown, manage_bitable_app, manage_bitable_table, manage_bitable_field, manage_bitable_view, manage_bitable_record, upload_bitable_attachment, list_wiki_spaces, search_wiki, list_wiki_nodes, get_wiki_node, create_wiki_node, update_wiki_node, move_wiki_node, copy_wiki_node, delete_wiki_node, list_files, create_folder, upload_drive_file, manage_drive_file, upload_image, upload_file, download_message_resource, download_doc_image, list_user_okrs, get_okrs, list_okr_periods, create_okr_progress_record, list_okr_progress_records, delete_okr_progress_record, list_calendars, list_calendar_events, get_calendar_event, create_calendar_event, update_calendar_event, delete_calendar_event, respond_calendar_event, get_freebusy, list_tasks, get_task, create_task, update_task, complete_task, delete_task, manage_task_members, get_new_events, manage_ws_status
|
|
6
6
|
user_invocable: true
|
|
7
7
|
---
|
|
@@ -150,7 +150,12 @@ async function withIdentityFallback({ client, uatFn, botFn, label }) {
|
|
|
150
150
|
uatErr = e;
|
|
151
151
|
}
|
|
152
152
|
if (uatResp && uatResp.code === 0) {
|
|
153
|
-
|
|
153
|
+
// Preserve the legacy _viaUser marker that 15+ _asUserOrApp callers read
|
|
154
|
+
// via `res._viaUser`. Without this flag, calendar/docs/bitable/wiki/okr/
|
|
155
|
+
// tasks/drive write tools labelled UAT-owned resources as viaUser:false,
|
|
156
|
+
// making users believe a bot created them. Caught by Codex review on
|
|
157
|
+
// PR #103 (P1 — set _viaUser on successful UAT results).
|
|
158
|
+
const data = { ...uatResp, _viaUser: true };
|
|
154
159
|
return { data, via: 'uat', identity };
|
|
155
160
|
}
|
|
156
161
|
const cls = _classifyUatFailure(uatResp, uatErr);
|
|
@@ -199,11 +199,36 @@ class LarkOfficialClient {
|
|
|
199
199
|
// dispatching N redundant API calls per read_messages on hot chats.
|
|
200
200
|
// has(id)==true / get(id)==null lets _computeDisplayLabel fall back to
|
|
201
201
|
// "(open_id)" exactly the same way as before.
|
|
202
|
+
//
|
|
203
|
+
// PR #103 Copilot followup: getUserById / getAppName return null on
|
|
204
|
+
// non-zero Feishu codes (e.g. 99991672, scope missing) WITHOUT rejecting,
|
|
205
|
+
// so the per-batch Promise.allSettled rejection log misses these. Log
|
|
206
|
+
// the ids that ended without a name as a separate stderr line so failure
|
|
207
|
+
// shape is observable regardless of whether the underlying lookup
|
|
208
|
+
// returned null or rejected.
|
|
209
|
+
const unresolvedUserIds = [];
|
|
202
210
|
for (const id of unknownUserIds) {
|
|
203
|
-
if (!this._userNameCache.has(id)
|
|
211
|
+
if (!this._userNameCache.has(id) || this._userNameCache.get(id) === null) {
|
|
212
|
+
this._userNameCache.set(id, null);
|
|
213
|
+
unresolvedUserIds.push(id);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (unresolvedUserIds.length) {
|
|
217
|
+
const sample = unresolvedUserIds.slice(0, 5).join(', ');
|
|
218
|
+
const tail = unresolvedUserIds.length > 5 ? ` (+${unresolvedUserIds.length - 5} more)` : '';
|
|
219
|
+
console.error(`[feishu-user-plugin] sender name unresolved (cached null) for ${unresolvedUserIds.length} id(s): ${sample}${tail}`);
|
|
204
220
|
}
|
|
221
|
+
const unresolvedAppIds = [];
|
|
205
222
|
for (const id of unknownAppIds) {
|
|
206
|
-
if (!this._appNameCache.has(id)
|
|
223
|
+
if (!this._appNameCache.has(id) || this._appNameCache.get(id) === null) {
|
|
224
|
+
this._appNameCache.set(id, null);
|
|
225
|
+
unresolvedAppIds.push(id);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (unresolvedAppIds.length) {
|
|
229
|
+
const sample = unresolvedAppIds.slice(0, 5).join(', ');
|
|
230
|
+
const tail = unresolvedAppIds.length > 5 ? ` (+${unresolvedAppIds.length - 5} more)` : '';
|
|
231
|
+
console.error(`[feishu-user-plugin] app name unresolved (cached null) for ${unresolvedAppIds.length} id(s): ${sample}${tail}`);
|
|
207
232
|
}
|
|
208
233
|
|
|
209
234
|
// Step 4: populate senderName, isExternal, displayLabel
|
package/src/oauth.js
CHANGED
|
@@ -163,10 +163,16 @@ async function exchangeCode(code) {
|
|
|
163
163
|
body: JSON.stringify(body),
|
|
164
164
|
});
|
|
165
165
|
const raw = await tokenRes.text();
|
|
166
|
-
|
|
166
|
+
// v1.3.13 security followup: don't log the full raw body — it contains the
|
|
167
|
+
// bare access_token + refresh_token. Log only the http status and a hint of
|
|
168
|
+
// success/failure; the parsed token never leaves this function except via
|
|
169
|
+
// saveToken (which writes the file with 0600 perms).
|
|
170
|
+
console.log(`Token exchange HTTP ${tokenRes.status} (body ${raw.length} bytes)`);
|
|
167
171
|
let tokenData;
|
|
168
172
|
try { tokenData = JSON.parse(raw); } catch (e) {
|
|
169
|
-
|
|
173
|
+
// Parse error path: redact body in the thrown message so an upstream
|
|
174
|
+
// log line doesn't accidentally surface tokens.
|
|
175
|
+
throw new Error(`Response not JSON (HTTP ${tokenRes.status}, ${raw.length} bytes): ${raw.slice(0, 100).replace(/[A-Za-z0-9._-]{40,}/g, '<redacted>')}`);
|
|
170
176
|
}
|
|
171
177
|
if (tokenData.error) {
|
|
172
178
|
throw new Error(`${tokenData.error}: ${tokenData.error_description}`);
|
|
@@ -199,8 +205,17 @@ function saveToken(tokenData) {
|
|
|
199
205
|
if (ok) console.log(`Tokens written to ${profileLabel}`);
|
|
200
206
|
}
|
|
201
207
|
if (!ok) {
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
// v1.3.13 security followup: never dump full token bytes to stderr.
|
|
209
|
+
// Caller can find them by re-running OAuth or reading the credentials
|
|
210
|
+
// file. Show only the field shape so user knows what fields exist.
|
|
211
|
+
console.error('WARNING: Tokens could not be saved automatically. Re-run `npx feishu-user-plugin oauth` after fixing the config path, or check that `~/.feishu-user-plugin/credentials.json` is writable.');
|
|
212
|
+
console.error('Fields that would have been written (values redacted):');
|
|
213
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
214
|
+
const preview = typeof v === 'string' && v.length > 0
|
|
215
|
+
? `${v.slice(0, 6)}…(${v.length} chars)`
|
|
216
|
+
: '<empty>';
|
|
217
|
+
console.error(` ${k}=${preview}`);
|
|
218
|
+
}
|
|
204
219
|
}
|
|
205
220
|
}
|
|
206
221
|
|
package/src/server.js
CHANGED
|
@@ -332,6 +332,17 @@ credMonitor.onUatChange((env) => {
|
|
|
332
332
|
console.error('[feishu-user-plugin] UAT reloaded from credentials.json (no restart needed)');
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
+
credMonitor.onCookieChange(() => {
|
|
336
|
+
// Cookie rotation: null the LarkUserClient singleton so the next
|
|
337
|
+
// getUserClient() call rebuilds it with the fresh cookie from env.
|
|
338
|
+
// Without this, cookie-based tools (send_to_user / search_contacts /
|
|
339
|
+
// get_login_status / send_as_user / batch_send) keep using the stale
|
|
340
|
+
// cookie until restart. PR #103 Codex P2 followup.
|
|
341
|
+
if (!userClient) return;
|
|
342
|
+
userClient = null;
|
|
343
|
+
console.error('[feishu-user-plugin] cookie rotation detected — userClient nulled, rebuilds on next tool call');
|
|
344
|
+
});
|
|
345
|
+
|
|
335
346
|
credMonitor.onCacheInvalidate(() => {
|
|
336
347
|
if (officialClient) identityState.invalidateIdentity(officialClient);
|
|
337
348
|
});
|
|
@@ -568,6 +579,11 @@ async function main() {
|
|
|
568
579
|
}
|
|
569
580
|
}
|
|
570
581
|
|
|
582
|
+
// Baseline credMonitor at startup so any credential changes between server
|
|
583
|
+
// boot and the first tool call fire hooks instead of being silently absorbed
|
|
584
|
+
// by the first sync()'s baselining branch. PR #103 Codex P2 followup.
|
|
585
|
+
credMonitor.sync();
|
|
586
|
+
|
|
571
587
|
// --- Real-time events (v1.3.9 — owner-arbitrated) ---
|
|
572
588
|
if (hasApp) {
|
|
573
589
|
_claimAndStart().catch((e) => {
|
package/src/test-all.js
CHANGED
|
@@ -363,5 +363,6 @@ main().catch(console.error).finally(() => {
|
|
|
363
363
|
process.exitCode = 1;
|
|
364
364
|
});
|
|
365
365
|
require('./test-cli-tool').run();
|
|
366
|
+
require('./test-lark-desktop').run();
|
|
366
367
|
require('./test-display-label'); // standalone — runs on require, exits non-zero on fail
|
|
367
368
|
});
|
|
@@ -86,6 +86,11 @@ async function run() {
|
|
|
86
86
|
assert.equal(r1.data.ok, undefined, 'should pass through fields, not double-wrap');
|
|
87
87
|
assert.equal(r1.data.data.ok, true);
|
|
88
88
|
assert.equal(r1.viaReason, undefined, 'no fallback → no via_reason');
|
|
89
|
+
// PR #103 Codex P1 followup: UAT success must set the legacy _viaUser=true
|
|
90
|
+
// marker so 15+ _asUserOrApp callsites (calendar/docs/bitable/wiki/okr/tasks
|
|
91
|
+
// /drive) report viaUser:true. Without this flag downstream code thinks the
|
|
92
|
+
// resource was created by the bot.
|
|
93
|
+
assert.equal(r1.data._viaUser, true, 'UAT success path must mark _viaUser=true on response');
|
|
89
94
|
|
|
90
95
|
// --- 8. withIdentityFallback: UAT returns 20064 → bot fallback, identity refined ---
|
|
91
96
|
let botRan = false;
|
package/src/test-lark-desktop.js
CHANGED
package/src/test-lru-cache.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/test-lru-cache.js — unit test for src/utils
|
|
1
|
+
// src/test-lru-cache.js — unit test for the LRUCache class exported by src/utils.js.
|
|
2
2
|
//
|
|
3
3
|
// Replaces the v1.3.12 `new Map()` _userNameCache / _appNameCache. Pre-fix the
|
|
4
4
|
// caches grew unboundedly across the server's lifetime (one entry per unique
|