cursor-feedback 2.6.0 → 2.7.1

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 CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.7.1](https://github.com/jianger666/cursor-feedback-extension/compare/v2.7.0...v2.7.1) (2026-07-07)
6
+
7
+
8
+ ### Features
9
+
10
+ * **cli:** 会话期配置守护——每秒监测 cli-config.json,Max 残留(maxMode=true 或 effort=max)一出现立即清除,兜住 CLI 断线重连等中途重读配置的路径 ([2fc4133](https://github.com/jianger666/cursor-feedback-extension/commit/2fc413388e1d7d230fc5b225ddbd45e094a95537))
11
+ * **cli:** 配置守护升级为 fs.watch 事件监听(毫秒级响应)+ 1s 轮询兜底,监听目录防原子写替换丢失目标 ([895387c](https://github.com/jianger666/cursor-feedback-extension/commit/895387ce4ad27bb8a344ed6e56fa01b851ee2729))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **cli:** headless 会话 MCP 加载修复——spawn 带 --approve-mcps;mcp.json 非字符串 env 归一化(否则 CLI 静默忽略整个文件) ([1e24435](https://github.com/jianger666/cursor-feedback-extension/commit/1e2443536278a82912ab7557bc1aa52d69762e43))
17
+ * **cli:** 按用户反馈去掉 -max 模型告警文案(清理机制下 -max 也正常计 1 次);补充残留与 --model 优先级实验结论 ([cf9c520](https://github.com/jianger666/cursor-feedback-extension/commit/cf9c5203cce9a30e911399536de236b4edb26d7c))
18
+ * **cli:** 默认模型改为非 Max 变体 xhigh——-max 后缀模型 CLI 会强制按 Max 计费(maxMode=false 拦不住),并清除 selectedModel 残留、/model 设 -max 时告警 ([29516aa](https://github.com/jianger666/cursor-feedback-extension/commit/29516aaf27c6e0666e3beed7375dbf03a481810b))
19
+
20
+
21
+ ### Documentation
22
+
23
+ * **cli:** 修正 Max 计费根因注释——账单证实是 selectedModel(effort=max) 残留导致后续会话按 Max 跑,而非模型属性不可关 ([4d389cb](https://github.com/jianger666/cursor-feedback-extension/commit/4d389cb8882e192a38dd85b01cf4c4fe34592384))
24
+ * **cli:** 补充 Max 计费机制实测结论——CLI 启动约 5s 即持久化 selectedModel,计费档取决于启动前文件状态(文件监视实验验证) ([5a1710d](https://github.com/jianger666/cursor-feedback-extension/commit/5a1710db95d13d5971e6ff7dbfd0eb71699d05ac))
25
+ * 定位收敛为 Cursor 专属——移除「任意 MCP host」章节与相关表述 ([ef069ea](https://github.com/jianger666/cursor-feedback-extension/commit/ef069ea73607a7f5cbbce87a091ded6bdb442829))
26
+
27
+ ## [2.7.0](https://github.com/jianger666/cursor-feedback-extension/compare/v2.6.0...v2.7.0) (2026-07-06)
28
+
29
+
30
+ ### Features
31
+
32
+ * **daemon:** 守护自动升级——IDE 新版 server 启动时发现守护版本落后即静默重装(带并发锁/残锁自清) ([738c270](https://github.com/jianger666/cursor-feedback-extension/commit/738c270f4bb6b4b6115f30b5c811fca26d10b43a))
33
+
5
34
  ## [2.6.0](https://github.com/jianger666/cursor-feedback-extension/compare/v2.5.0...v2.6.0) (2026-07-06)
6
35
 
7
36
 
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Open VSX Downloads](https://img.shields.io/open-vsx/dt/jianger666/cursor-feedback)](https://open-vsx.org/extension/jianger666/cursor-feedback)
7
7
  [![npm](https://img.shields.io/npm/v/cursor-feedback)](https://www.npmjs.com/package/cursor-feedback)
8
8
 
9
- **One conversation, unlimited AI interactions** - If you're on a per-request plan, it saves your monthly quota; plus it bridges your agent tool with Feishu (Lark) — when AI asks for feedback, it's pushed to Feishu and you can reply from your phone. An interactive feedback tool built on MCP (Model Context Protocol).
9
+ **One conversation, unlimited AI interactions** - If you're on a per-request plan, it saves your monthly quota; plus it bridges Cursor with Feishu (Lark) — when AI asks for feedback, it's pushed to Feishu and you can reply from your phone, or even send `/new` to spawn a fresh session. An interactive feedback tool built for Cursor, on top of MCP.
10
10
 
11
11
  ![Demo](./demo.gif)
12
12
 
@@ -205,8 +205,8 @@ Custom timeout (optional, default 5 minutes):
205
205
 
206
206
  Either way works — but you first need to set up the bot in the Feishu console: enable the bot, grant permissions, and turn on **event subscription** (long-connection mode + the `im.message.receive_v1` event). Full walkthrough in the [Feishu setup guide](./docs/feishu-setup.md):
207
207
 
208
- - **On Cursor**: fill in Feishu credentials via the "Notification settings" icon at the top of the panel.
209
- - **On other MCP hosts** (agent tools without this panel): configure through `env` in `mcp.json`:
208
+ - **Panel config (recommended)**: fill in Feishu credentials via the "Notification settings" icon at the top of the panel.
209
+ - **Env config** (handy for pinning the config in `mcp.json` / rolling it out to a team):
210
210
 
211
211
  ```json
212
212
  {
@@ -233,34 +233,7 @@ Either way works — but you first need to set up the bot in the Feishu console:
233
233
 
234
234
  > Priority: **panel config (when credentials are filled) > env here > default**. The panel wins when App ID/Secret are filled; otherwise it falls back to env. You still need to send the bot one message in Feishu to complete binding.
235
235
 
236
- ### Feishu round-trip on any MCP host
237
-
238
- `cursor-feedback` is **not tied to Cursor**. The whole Feishu loop — long connection, card push, and reply routing — lives inside the MCP server process, so it works on **any MCP-capable agent host**: Cursor, Claude Desktop, Cline, command-line agents, or your own harness. No sidebar / VS Code panel required.
239
-
240
- Three steps to enable the round-trip:
241
-
242
- 1. **Register the MCP server** in the host's config with Feishu credentials in `env` (same `FEISHU_APP_ID` / `FEISHU_APP_SECRET` as above; `FEISHU_ENABLED` / `FEISHU_ACK` optional):
243
-
244
- ```json
245
- {
246
- "mcpServers": {
247
- "cursor-feedback": {
248
- "command": "npx",
249
- "args": ["-y", "cursor-feedback@latest"],
250
- "env": {
251
- "FEISHU_APP_ID": "cli_xxxxxxxx",
252
- "FEISHU_APP_SECRET": "your_app_secret"
253
- }
254
- }
255
- }
256
- }
257
- ```
258
-
259
- 2. **Bind the chat once** — send the bot any message in Feishu. The server records that chat as the push target (persisted to disk, shared across processes), so it knows where to deliver cards.
260
-
261
- 3. **Round-trip** — the agent calls `interactive_feedback` → a card is pushed to Feishu → you reply in Feishu → your reply is routed back to the agent as the tool result. Reply from your phone; no IDE needed.
262
-
263
- > **No idle-kill outside Cursor.** Only the Cursor extension keeps the server warm by polling. On every other host the server stays alive purely on the stdio connection: it exits on stdin EOF or parent-process death, and **never** on idle — and it always stays up while a feedback request is still waiting. So a reply that takes several minutes still makes it back.
236
+ Once configured, send the bot any message in Feishu to **bind the chat** — the server records it as the push target (persisted to disk, shared across processes). From then on: the agent calls `interactive_feedback` → a card is pushed to Feishu → you reply in Feishu → your reply is routed back to the agent as the tool result. Reply from your phone; no need to sit at the computer.
264
237
 
265
238
  ### Launch CLI sessions from Feishu (/new)
266
239
 
@@ -278,7 +251,7 @@ Send these directly in the bot chat:
278
251
  | `/model [modelId]` | Show / set the session model (persisted) |
279
252
  | `/help` | Show command usage |
280
253
 
281
- - Sessions run in **non-interactive mode** with `maxMode=false` force-written before spawn on request-based plans each session costs exactly 1 request, never Max billing.
254
+ - Sessions run in **non-interactive mode**. Before every spawn the launcher rewrites `~/.cursor/cli-config.json` to a clean state (`maxMode=false`, target model, stale `selectedModel` removed), so sessions are billed at the normal per-request rate including models with a `-max` suffix.
282
255
  - The launched agent talks to you through this extension's feedback cards: confirmations and progress reports are pushed to Feishu; just reply to the cards.
283
256
  - Put custom rules in `~/.cursor-feedback/cli-rules.md` (e.g. "always reply in Chinese"); they are injected into every `/new` session.
284
257
  - When a session ends (done / error / `/stop` / 3-hour cap), a wrap-up message with the final output is sent to Feishu.
@@ -289,6 +262,7 @@ Normally the server process is spawned by Cursor, so closing Cursor drops the Fe
289
262
 
290
263
  - **Enable**: the "Background service" toggle in the extension's notification settings, or `npx cursor-feedback@latest install-daemon` (`uninstall-daemon` / `daemon-status` likewise).
291
264
  - **How**: the current package is copied to `~/.cursor-feedback/daemon/app` (self-contained, immune to npx cache cleanup); registered as a launchd agent on macOS (auto-restart on crash) or a logon scheduled task on Windows.
265
+ - **Auto-upgrade**: the daemon copy never goes stale — whenever a newer server starts inside the IDE and sees the installed daemon is outdated, it silently reinstalls and restarts it.
292
266
  - **Keep-awake**: while on AC power the daemon prevents system sleep (macOS `caffeinate -s`; Windows power assertion). On battery it does nothing. Lock screen never affects background processes.
293
267
  - **Typical use**: leave the machine plugged in and locked after work; `/new` from your phone anytime.
294
268
 
package/README_CN.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Open VSX Downloads](https://img.shields.io/open-vsx/dt/jianger666/cursor-feedback)](https://open-vsx.org/extension/jianger666/cursor-feedback)
7
7
  [![npm](https://img.shields.io/npm/v/cursor-feedback)](https://www.npmjs.com/package/cursor-feedback)
8
8
 
9
- **一次对话,无限 AI 交互** - 如果你是按次计费的用户,它能帮你省下月度请求配额;还能把你的 Agent 工具和飞书打通——AI 请求反馈时推到飞书,你在手机上就能回。基于 MCP(Model Context Protocol)的交互式反馈工具。
9
+ **一次对话,无限 AI 交互** - 如果你是按次计费的用户,它能帮你省下月度请求配额;还能把 Cursor 和飞书打通——AI 请求反馈时推到飞书,你在手机上就能回,甚至发 `/new` 直接拉起新会话。为 Cursor 量身打造的交互式反馈工具(基于 MCP)。
10
10
 
11
11
  ![Demo](./demo.gif)
12
12
 
@@ -209,8 +209,8 @@ npm install -g cursor-feedback
209
209
 
210
210
  两种方式都行——但都要先在飞书后台把机器人配好:开启机器人能力、申请权限、配置**事件订阅**(长连接方式 + `im.message.receive_v1` 事件)。完整步骤见[飞书配置教程](./docs/feishu-setup.md):
211
211
 
212
- - **用 Cursor**:在插件面板顶部「通知设置」图标里填飞书凭证。
213
- - **用其他 MCP host**(没有这个插件面板的 Agent 工具):在 `mcp.json` `env` 里配置:
212
+ - **面板配置(推荐)**:在插件面板顶部「通知设置」图标里填飞书凭证。
213
+ - **env 配置**(适合把配置固化在 `mcp.json` / 团队统一分发):
214
214
 
215
215
  ```json
216
216
  {
@@ -237,34 +237,7 @@ npm install -g cursor-feedback
237
237
 
238
238
  > 优先级:**插件面板(填了凭证)> 这里的 env > 默认**。面板里填了 App ID/Secret 就以面板为准;没填则回退到 env。首次仍需在飞书里给机器人发一条消息完成绑定。
239
239
 
240
- ### 在任意 MCP host 上跑飞书往复
241
-
242
- `cursor-feedback` **不绑定 Cursor**。整条飞书链路——长连接、卡片推送、回复路由——都跑在 MCP server 进程内,所以它能在**任意支持 MCP 的 Agent host** 上工作:Cursor、Claude Desktop、Cline、命令行 Agent,或你自建的 harness,都不需要侧边栏 / VS Code 面板。
243
-
244
- 三步打通往复:
245
-
246
- 1. **注册 MCP server**:在 host 的配置里加上,并把飞书凭证写进 `env`(与上面相同的 `FEISHU_APP_ID` / `FEISHU_APP_SECRET`;`FEISHU_ENABLED` / `FEISHU_ACK` 可选):
247
-
248
- ```json
249
- {
250
- "mcpServers": {
251
- "cursor-feedback": {
252
- "command": "npx",
253
- "args": ["-y", "cursor-feedback@latest"],
254
- "env": {
255
- "FEISHU_APP_ID": "cli_xxxxxxxx",
256
- "FEISHU_APP_SECRET": "your_app_secret"
257
- }
258
- }
259
- }
260
- }
261
- ```
262
-
263
- 2. **首次绑定会话**:在飞书里给机器人发任意一条消息。server 会把这个会话记为推送目标(落盘、跨进程共享),之后就知道往哪推卡片。
264
-
265
- 3. **往复**:Agent 调 `interactive_feedback` → 卡片推到飞书 → 你在飞书回复 → 回复作为工具结果路由回 Agent。手机上就能回,不用守着 IDE。
266
-
267
- > **非 Cursor 环境不会因空闲被杀。** 只有 Cursor 插件会靠轮询给 server「续命」。在其他所有 host 上,server 完全依赖 stdio 连接存活:只在 stdin EOF 或父进程死亡时退出,**绝不**因空闲退出;而且只要还有反馈在等用户,任何环境都不会退。所以你回复花了好几分钟,结果照样能回到 Agent。
240
+ 配置好后,在飞书里给机器人发任意一条消息完成**首次绑定**——server 会把这个会话记为推送目标(落盘、跨进程共享)。之后:Agent `interactive_feedback` → 卡片推到飞书 → 你在飞书回复 → 回复作为工具结果路由回 Agent。手机上就能回,不用守着电脑。
268
241
 
269
242
  ### 手机飞书拉起 CLI 会话(/new)
270
243
 
@@ -282,7 +255,7 @@ npm install -g cursor-feedback
282
255
  | `/model [模型id]` | 查看 / 设置会话模型(持久化) |
283
256
  | `/help` | 查看命令用法 |
284
257
 
285
- - 会话以**非交互模式**运行,拉起前强制写入 `maxMode=false`——按次计费套餐固定只扣 1 次请求,永远不会走 Max 计费。
258
+ - 会话以**非交互模式**运行。每次拉起前都会把 `~/.cursor/cli-config.json` 重写为干净状态(`maxMode=false` + 目标模型 + 清除 `selectedModel` 残留),因此会话按普通单次请求计费——包括 `-max` 后缀的模型。
286
259
  - 拉起的 Agent 也通过本插件的反馈卡片与你沟通:需要确认、汇报进展都会推到飞书,直接回复卡片即可。
287
260
  - 可在 `~/.cursor-feedback/cli-rules.md` 写自定义规则(如「永远用中文」),每次 `/new` 自动注入。
288
261
  - 会话结束(完成 / 异常 / `/stop` / 超 3 小时兜底终止)都会发飞书收尾消息附最终输出。
@@ -293,6 +266,7 @@ npm install -g cursor-feedback
293
266
 
294
267
  - **开启方式**:插件面板「通知设置」里打开「常驻服务」开关;或命令行 `npx cursor-feedback@latest install-daemon`(卸载对应 `uninstall-daemon`,状态 `daemon-status`)。
295
268
  - **实现**:把当前包完整拷贝到 `~/.cursor-feedback/daemon/app`(自包含,不怕 npx 缓存被清),macOS 注册 launchd(崩溃自动拉起),Windows 注册登录计划任务。
269
+ - **自动升级**:守护拷贝不会「静默落后」——IDE 里的新版 server 启动时发现守护版本旧了,会静默重装并重启守护,无需手动操作。
296
270
  - **防睡眠**:守护进程在接电源时自动阻止系统睡眠(macOS `caffeinate -s`;Windows 电源断言),电池供电不生效、不偷耗电。锁屏本来就不影响后台进程。
297
271
  - **典型用法**:下班插着电源锁屏走人,手机飞书随时 `/new`。
298
272
 
@@ -38,13 +38,27 @@ exports.CliLauncher = void 0;
38
38
  * CLI 会话拉起器:飞书 /new 命令 → 非交互模式 spawn cursor-agent。
39
39
  *
40
40
  * 关键设计(均来自实测结论,勿随意更改):
41
- * - 必须用非交互 print 模式(-p):交互式 TUI 会话对 Fable 5 会强制回到 Max Mode,
42
- * 次数计费的套餐一次对话被扣 10+ 次;非交互模式尊重 cli-config.jsonmaxMode=false
43
- * 固定只扣 1 次请求。
44
- * - spawn 前每次都强制把 ~/.cursor/cli-config.json 写成 maxMode=false + 目标模型:
45
- * 交互式会话会把这个文件改回 max,不能信任上次的残留状态。
41
+ * - 默认模型用**非 Max 变体**(见 DEFAULT_MODEL 注释)。
42
+ * - spawn 前每次都强制重写 ~/.cursor/cli-config.jsonmaxMode=false + 目标模型,
43
+ * 并删除 selectedModel 残留——三件缺一不可,不能信任上次的状态。
44
+ * 实测机制(2026-07-07 文件监视 + 前日账单交叉验证):
45
+ * 1. CLI 启动约 5~10s 内会把 --model 解析结果持久化回 cli-config.json 的
46
+ * selectedModel(含 effort 参数),会话结束时再写一次——任何一次会话
47
+ * (含 IDE 终端手动跑的)都会留下与该会话模型匹配的残留;
48
+ * 2. 本次会话的实际计费档取决于启动前的文件状态:干净状态(无 selectedModel、
49
+ * maxMode=false)时即使 --model 传 -max 模型也按非 Max 计 1 次;
50
+ * 但残留 selectedModel(effort=max) 在场时同样命令扣 40+ 并带 MAX 标签。
51
+ * 3. 残留与 --model 冲突时 --model 赢(实验:残留 effort=xhigh + --model low
52
+ * → 会话按 low 跑)——所以用非 Max 模型时即使清理和别的进程写残留发生竞态
53
+ * 也不会翻车;仅「用户显式选 -max 模型 + 恰好有并发进程写入 max 残留」
54
+ * 这一极小竞态窗口无法从我们这侧根除。
55
+ * - 必须用非交互 print 模式(-p):交互式 TUI 会持久改写 cli-config.json 的模型选择。
46
56
  * - CLI 不读 IDE 的全局 User Rules,用户的个人规则要显式注入到 prompt 里
47
57
  * (从 ~/.cursor-feedback/cli-rules.md 读取,没有则只注入 cursor-feedback 沟通协议)。
58
+ * - 必须带 --approve-mcps:MCP 批准状态按工作目录落盘,headless 会话无法弹批准框,
59
+ * 不带这个 flag 时 cursor-feedback 工具根本不加载,agent 会卡死。
60
+ * - mcp.json 的 env 值必须全是字符串:出现 boolean/number 时 CLI 会静默忽略整个
61
+ * mcp.json(IDE 却能容忍),spawn 前做一次归一化。
48
62
  * - 拉起的 agent 会通过全局注册的 cursor-feedback MCP 发飞书卡片,用户在手机上
49
63
  * 直接和这个会话对话——本模块只负责拉起和收尾,过程中的交互走既有反馈链路。
50
64
  */
@@ -277,11 +291,51 @@ class CliLauncher {
277
291
  return 'cursor-agent'; // 最后交给 PATH 解析,spawn error 时报错提示
278
292
  }
279
293
  /**
280
- * spawn 前强制写 cli-config.json:maxMode=false + 目标模型。
281
- * 每次都写——交互式会话会把它改回 max,残留状态不可信。
294
+ * spawn 前把 ~/.cursor/mcp.json 里的非字符串 env 值归一化为字符串。
295
+ * 实测(2026-07):env 里出现 boolean/number(如 "FEISHU_ENABLED": true,IDE 能容忍)
296
+ * 会让 cursor-agent 校验失败并**静默忽略整个 mcp.json**——所有 MCP server 全部消失,
297
+ * headless 会话找不到 cursor-feedback 工具,卡死在「必须用它沟通」的指令上。
298
+ */
299
+ normalizeMcpJsonEnv() {
300
+ try {
301
+ const p = path.join(os.homedir(), '.cursor', 'mcp.json');
302
+ const j = JSON.parse(fs.readFileSync(p, 'utf-8'));
303
+ let changed = false;
304
+ for (const server of Object.values(j.mcpServers || {})) {
305
+ for (const [k, v] of Object.entries(server.env || {})) {
306
+ if (typeof v !== 'string') {
307
+ server.env[k] = String(v);
308
+ changed = true;
309
+ }
310
+ }
311
+ }
312
+ if (changed) {
313
+ fs.writeFileSync(p, JSON.stringify(j, null, 2));
314
+ clog('mcp.json 中的非字符串 env 已归一化为字符串(CLI 兼容性)');
315
+ }
316
+ }
317
+ catch {
318
+ // mcp.json 不存在或损坏:不是本模块的职责,交给 cursor-agent 自己报错
319
+ }
320
+ }
321
+ static cliConfigPath() {
322
+ return path.join(os.homedir(), '.cursor', 'cli-config.json');
323
+ }
324
+ /** 配置里是否存在会触发 Max 计费的状态(两个独立触发源,任一命中即危险) */
325
+ static hasMaxResidue(cfg) {
326
+ if (cfg.maxMode === true)
327
+ return true;
328
+ const sel = cfg.selectedModel;
329
+ if (sel?.parameters?.some((p) => p?.id === 'effort' && p?.value === 'max'))
330
+ return true;
331
+ return false;
332
+ }
333
+ /**
334
+ * spawn 前强制写 cli-config.json:maxMode=false + 目标模型 + 删除 selectedModel。
335
+ * 每次都写——上次会话(含用户在终端手动跑的)会留下残留,状态不可信。
282
336
  */
283
337
  ensureMaxModeOff() {
284
- const p = path.join(os.homedir(), '.cursor', 'cli-config.json');
338
+ const p = CliLauncher.cliConfigPath();
285
339
  let cfg = {};
286
340
  try {
287
341
  cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
@@ -292,8 +346,56 @@ class CliLauncher {
292
346
  cfg.maxMode = false;
293
347
  cfg.model = { modelId: this.model(), maxMode: false };
294
348
  cfg.hasChangedDefaultModel = true;
349
+ // 清掉 CLI 自己写入的 selectedModel(Max 模型会话会留下 effort=max 的残留,
350
+ // 不删的话下次会话可能沿用 Max 档计费)
351
+ delete cfg.selectedModel;
295
352
  fs.writeFileSync(p, JSON.stringify(cfg, null, 2));
296
- clog(`cli-config.json 已写入 maxMode=false, model=${this.model()}`);
353
+ clog(`cli-config.json 已写入 maxMode=false, model=${this.model()},并清除 selectedModel`);
354
+ }
355
+ /**
356
+ * 会话运行期间的配置守护:监听 cli-config.json,一旦出现 Max 残留立即改回干净状态。
357
+ * 动机:spawn 前的清理只保证「启动瞬间」干净;会话运行中 CLI 自己会把当前模型写回
358
+ * selectedModel(-max 模型就是 effort=max),若 CLI 存在断线重连等重读配置的路径,
359
+ * 残留可能被中途拾取。守护用 fs.watch 事件监听做到毫秒级响应,再加 1s 轮询兜底
360
+ * (fs.watch 少数场景会漏事件)。只在「危险」时才写(maxMode=true 或 effort=max),
361
+ * 非 Max 状态一律不动,避免与 CLI 的正常写回互相打架。
362
+ * @returns 停止守护的清理函数(会话结束时调用)
363
+ */
364
+ startConfigGuard() {
365
+ const p = CliLauncher.cliConfigPath();
366
+ const checkAndClean = () => {
367
+ try {
368
+ const cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
369
+ if (CliLauncher.hasMaxResidue(cfg)) {
370
+ this.ensureMaxModeOff();
371
+ clog('配置守护:检测到 Max 残留被写回,已立即清除');
372
+ }
373
+ }
374
+ catch {
375
+ // 文件正被写入(半包 JSON)或暂不可读:跳过,等下一次事件/轮询
376
+ }
377
+ };
378
+ // 监听目录而不是文件本身:CLI 若用「写临时文件再 rename」的原子写法,
379
+ // 直接 watch 文件会在第一次替换后失去目标(inode 变了)
380
+ let watcher = null;
381
+ try {
382
+ watcher = fs.watch(path.dirname(p), (_event, filename) => {
383
+ // filename 在个别平台可能为 null,此时也检查一次(checkAndClean 幂等且廉价)
384
+ if (!filename || filename === path.basename(p))
385
+ checkAndClean();
386
+ });
387
+ watcher.on('error', () => {
388
+ // 监听挂掉不影响安全性,轮询兜底还在
389
+ });
390
+ }
391
+ catch {
392
+ // 平台不支持 fs.watch 时只靠轮询
393
+ }
394
+ const timer = setInterval(checkAndClean, 1000);
395
+ return () => {
396
+ clearInterval(timer);
397
+ watcher?.close();
398
+ };
297
399
  }
298
400
  /** 用户自定义注入规则:~/.cursor-feedback/cli-rules.md(可选) */
299
401
  readUserRules() {
@@ -344,6 +446,7 @@ class CliLauncher {
344
446
  this.releaseLock();
345
447
  return '写入 cli-config.json 失败:' + e;
346
448
  }
449
+ this.normalizeMcpJsonEnv();
347
450
  const bin = this.findBinary();
348
451
  if (!bin) {
349
452
  this.releaseLock();
@@ -359,7 +462,10 @@ class CliLauncher {
359
462
  ? next.slice(next.length - CliLauncher.OUTPUT_TAIL_LIMIT)
360
463
  : next;
361
464
  };
362
- const args = ['-p', '--trust', '--model', this.model(), prompt];
465
+ // --approve-mcps 必带:headless 无法弹批准框,MCP 不批准就不加载,agent 会找不到
466
+ // cursor-feedback 工具而卡死。批准状态按工作目录存(~/.cursor/projects/<dir>/
467
+ // mcp-approvals.json),逐目录 enable 不现实,只有这个 flag 对任意 cwd 生效。
468
+ const args = ['-p', '--trust', '--approve-mcps', '--model', this.model(), prompt];
363
469
  const isWin = process.platform === 'win32';
364
470
  // Windows 上 .cmd/.ps1 不能直接 spawn(Node 18.20+ 禁止),需经 cmd.exe 转发
365
471
  const viaCmdShell = isWin && /\.(cmd|bat|ps1)$/i.test(bin);
@@ -396,10 +502,13 @@ class CliLauncher {
396
502
  clog('会话超过时长上限,强制终止');
397
503
  CliLauncher.killTree(child, 'SIGKILL');
398
504
  }, CliLauncher.SESSION_MAX_MS);
505
+ // 会话全程守护 cli-config.json,Max 残留一出现就清(防 CLI 中途重读配置拾取残留)
506
+ const stopConfigGuard = this.startConfigGuard();
399
507
  child.stdout?.on('data', (chunk) => { stdout = append(stdout, chunk); });
400
508
  child.stderr?.on('data', (chunk) => { stderr = append(stderr, chunk); });
401
509
  const finish = (code) => {
402
510
  clearTimeout(killTimer);
511
+ stopConfigGuard();
403
512
  const elapsedMs = Date.now() - this.startedAt;
404
513
  // 跨实例 /stop 会在锁上打 stopRequested 标记再杀进程,这里一并算「主动终止」
405
514
  const lockAtFinish = this.readLock();
@@ -511,8 +620,14 @@ class CliLauncher {
511
620
  }
512
621
  }
513
622
  exports.CliLauncher = CliLauncher;
514
- /** 默认模型:实测非交互模式下该模型 + maxMode=false 固定扣 1 次请求 */
515
- CliLauncher.DEFAULT_MODEL = 'claude-fable-5-thinking-max';
623
+ /**
624
+ * 默认模型:用非 Max 变体(2026-07-06 账单实测)。`-max` 后缀模型(显示名
625
+ * "Fable 5 1M Max Thinking")跑过一次后,CLI 会把 selectedModel(effort=max)
626
+ * 持久化进 cli-config.json,这份残留不清则后续会话按 Max 计费(实测扣 40+)。
627
+ * ensureMaxModeOff 已做残留清理,但默认模型仍选 thinking-xhigh(非 Max 最高档,
628
+ * 固定扣 1 次)从根上避开这类状态机风险。
629
+ */
630
+ CliLauncher.DEFAULT_MODEL = 'claude-fable-5-thinking-xhigh';
516
631
  /** 会话时长兜底:防止无人回复的 headless 会话无限续期挂着 */
517
632
  CliLauncher.SESSION_MAX_MS = 3 * 60 * 60 * 1000;
518
633
  /** stdout/stderr 只保留尾部,防止长会话把内存吃爆 */
@@ -37,6 +37,7 @@ exports.daemonSupported = daemonSupported;
37
37
  exports.installDaemon = installDaemon;
38
38
  exports.uninstallDaemon = uninstallDaemon;
39
39
  exports.daemonStatus = daemonStatus;
40
+ exports.upgradeDaemonIfOutdated = upgradeDaemonIfOutdated;
40
41
  /**
41
42
  * 常驻守护进程的安装 / 卸载 / 状态查询。
42
43
  *
@@ -55,10 +56,12 @@ const child_process_1 = require("child_process");
55
56
  const fs = __importStar(require("fs"));
56
57
  const os = __importStar(require("os"));
57
58
  const path = __importStar(require("path"));
59
+ const logger_js_1 = require("./logger.js");
58
60
  const LABEL = 'com.jianger666.cursor-feedback.daemon';
59
61
  const WIN_TASK_NAME = 'CursorFeedbackDaemon';
60
62
  function dlog(message) {
61
63
  console.error(`[${new Date().toISOString()}] [daemon-install] ${message}`);
64
+ (0, logger_js_1.fileLog)('daemon-install', message);
62
65
  }
63
66
  function daemonRoot() {
64
67
  return path.join(os.homedir(), '.cursor-feedback', 'daemon');
@@ -289,4 +292,53 @@ function winTaskExists() {
289
292
  return false;
290
293
  }
291
294
  }
295
+ /**
296
+ * 守护自动升级:已安装的守护是安装时的快照,不会随 npx @latest 走。
297
+ * IDE 里的新版 server 启动时调用这里——发现守护版本与自己不一致就静默重装
298
+ * (重装会 bootout 旧守护、拷贝新包、重新拉起),用户零操作。
299
+ *
300
+ * 并发防护:多窗口 = 多个 server 同时启动,重装涉及删目录 + 整树拷贝,撞上会互相
301
+ * 写坏。用 wx 独占锁串行化,抢不到的直接跳过(反正有人在装);残锁超 10 分钟视为
302
+ * 上次进程死在半路,清掉重来。
303
+ *
304
+ * @returns 是否真的执行了重装
305
+ */
306
+ function upgradeDaemonIfOutdated(currentVersion) {
307
+ try {
308
+ const st = daemonStatus();
309
+ if (!st.installed || !st.installedVersion || st.installedVersion === currentVersion) {
310
+ return false;
311
+ }
312
+ const lock = path.join(daemonRoot(), 'upgrade.lock');
313
+ try {
314
+ const age = Date.now() - fs.statSync(lock).mtimeMs;
315
+ if (age > 10 * 60 * 1000)
316
+ fs.rmSync(lock, { force: true });
317
+ }
318
+ catch {
319
+ // 无残锁
320
+ }
321
+ try {
322
+ fs.mkdirSync(daemonRoot(), { recursive: true });
323
+ fs.writeFileSync(lock, String(process.pid), { flag: 'wx' });
324
+ }
325
+ catch {
326
+ return false; // 另一个窗口的 server 正在升级
327
+ }
328
+ try {
329
+ dlog(`守护版本 ${st.installedVersion} 落后于当前 ${currentVersion},自动重装升级`);
330
+ installDaemon();
331
+ dlog('守护自动升级完成');
332
+ return true;
333
+ }
334
+ finally {
335
+ fs.rmSync(lock, { force: true });
336
+ }
337
+ }
338
+ catch (e) {
339
+ // 升级失败不影响 server 正常启动,旧守护继续工作
340
+ dlog(`守护自动升级失败(不影响本进程):${e}`);
341
+ return false;
342
+ }
343
+ }
292
344
  //# sourceMappingURL=daemon-install.js.map
@@ -497,7 +497,7 @@ class McpFeedbackServer {
497
497
  }
498
498
  /**
499
499
  * 处理飞书斜杠命令。返回是否已消费该消息。
500
- * - /new [目录或项目名] 任务描述:拉起 headless CLI 会话(非交互 + maxMode=false,扣 1 次请求)。
500
+ * - /new [目录或项目名] 任务描述:拉起 headless CLI 会话(非交互,spawn 前清 Max 残留)。
501
501
  * 工作目录刻意保持简单:显式指定 > 主目录,与当前开着哪些 IDE 窗口无关。
502
502
  * - /projects:列出 Cursor 打开过的项目路径(供手机上查路径 / 复制给 /new)
503
503
  * - /stop:终止运行中的 CLI 会话
@@ -553,10 +553,10 @@ class McpFeedbackServer {
553
553
  if (mModel) {
554
554
  if (mModel[1]) {
555
555
  this.cliLauncher.writeSettings({ model: mModel[1] });
556
- this.feishu.replyText(chatId, `✅ CLI 模型已设为 ${mModel[1]}\n(无论什么模型,拉起前都会强制 maxMode=false,不会走 Max 计费)`);
556
+ this.feishu.replyText(chatId, `✅ CLI 模型已设为 ${mModel[1]}`);
557
557
  }
558
558
  else {
559
- this.feishu.replyText(chatId, `当前 CLI 模型:${this.cliLauncher.model()}\n设置:/model 模型id(例如 /model claude-fable-5-thinking-max)`);
559
+ this.feishu.replyText(chatId, `当前 CLI 模型:${this.cliLauncher.model()}\n设置:/model 模型id(例如 /model claude-fable-5-thinking-xhigh)`);
560
560
  }
561
561
  return true;
562
562
  }
@@ -620,7 +620,7 @@ class McpFeedbackServer {
620
620
  this.feishu.replyText(chatId, '❌ 拉起 CLI 会话失败:' + err);
621
621
  }
622
622
  else {
623
- this.feishu.replyText(chatId, '🚀 CLI 会话已拉起(非交互模式,已强制 maxMode=false,按 1 次请求计费)\n' +
623
+ this.feishu.replyText(chatId, '🚀 CLI 会话已拉起(非交互模式)\n' +
624
624
  `模型:${this.cliLauncher.model()}\n` +
625
625
  `工作目录:${cwd}\n` +
626
626
  `任务:${task.length > 100 ? task.slice(0, 100) + '…' : task}\n\n` +
@@ -2151,6 +2151,17 @@ class McpFeedbackServer {
2151
2151
  this.startWatchdog();
2152
2152
  debugLog('MCP Server started successfully');
2153
2153
  debugLog('Waiting for tool calls from AI agent...');
2154
+ // 守护自动升级:本进程经 npx @latest 拉起、必然是最新版;发现已装守护版本落后
2155
+ // 就静默重装(拷贝新包 + 重启守护),用户零操作。延后跑:整树拷贝要几秒,
2156
+ // 不能挡 MCP 握手;失败也只记日志,旧守护继续工作。
2157
+ setTimeout(() => {
2158
+ try {
2159
+ (0, daemon_install_js_1.upgradeDaemonIfOutdated)(PKG_VERSION);
2160
+ }
2161
+ catch {
2162
+ // upgradeDaemonIfOutdated 内部已兜错,这里纯保险
2163
+ }
2164
+ }, 5000);
2154
2165
  }
2155
2166
  catch (error) {
2156
2167
  debugLog(`Failed to start server: ${error}`);
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "cursor-feedback",
3
3
  "displayName": "Cursor Feedback",
4
4
  "description": "One Cursor conversation, unlimited AI interactions - Save your monthly request quota! Interactive feedback loop for AI chat via MCP",
5
- "version": "2.6.0",
5
+ "version": "2.7.1",
6
6
  "icon": "icon.png",
7
7
  "author": "jianger666",
8
8
  "license": "MIT",