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 +29 -0
- package/README.md +6 -32
- package/README_CN.md +6 -32
- package/dist/cli-launcher.js +127 -12
- package/dist/daemon-install.js +52 -0
- package/dist/mcp-server.js +15 -4
- package/package.json +1 -1
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
|
[](https://open-vsx.org/extension/jianger666/cursor-feedback)
|
|
7
7
|
[](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
|
|
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
|

|
|
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
|
-
- **
|
|
209
|
-
- **
|
|
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
|
-
|
|
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
|
|
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
|
[](https://open-vsx.org/extension/jianger666/cursor-feedback)
|
|
7
7
|
[](https://www.npmjs.com/package/cursor-feedback)
|
|
8
8
|
|
|
9
|
-
**一次对话,无限 AI 交互** -
|
|
9
|
+
**一次对话,无限 AI 交互** - 如果你是按次计费的用户,它能帮你省下月度请求配额;还能把 Cursor 和飞书打通——AI 请求反馈时推到飞书,你在手机上就能回,甚至发 `/new` 直接拉起新会话。为 Cursor 量身打造的交互式反馈工具(基于 MCP)。
|
|
10
10
|
|
|
11
11
|

|
|
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
|
-
-
|
|
213
|
-
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
package/dist/cli-launcher.js
CHANGED
|
@@ -38,13 +38,27 @@ exports.CliLauncher = void 0;
|
|
|
38
38
|
* CLI 会话拉起器:飞书 /new 命令 → 非交互模式 spawn cursor-agent。
|
|
39
39
|
*
|
|
40
40
|
* 关键设计(均来自实测结论,勿随意更改):
|
|
41
|
-
* -
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* - 默认模型用**非 Max 变体**(见 DEFAULT_MODEL 注释)。
|
|
42
|
+
* - spawn 前每次都强制重写 ~/.cursor/cli-config.json:maxMode=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
|
|
281
|
-
*
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
515
|
-
|
|
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 只保留尾部,防止长会话把内存吃爆 */
|
package/dist/daemon-install.js
CHANGED
|
@@ -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
|
package/dist/mcp-server.js
CHANGED
|
@@ -497,7 +497,7 @@ class McpFeedbackServer {
|
|
|
497
497
|
}
|
|
498
498
|
/**
|
|
499
499
|
* 处理飞书斜杠命令。返回是否已消费该消息。
|
|
500
|
-
* - /new [目录或项目名] 任务描述:拉起 headless CLI
|
|
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]}
|
|
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-
|
|
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
|
|
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.
|
|
5
|
+
"version": "2.7.1",
|
|
6
6
|
"icon": "icon.png",
|
|
7
7
|
"author": "jianger666",
|
|
8
8
|
"license": "MIT",
|