cursor-feedback 2.7.2 → 2.7.3

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,18 @@
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.3](https://github.com/jianger666/cursor-feedback-extension/compare/v2.7.2...v2.7.3) (2026-07-07)
6
+
7
+
8
+ ### Features
9
+
10
+ * **feishu:** 新增 /models 命令查可用模型列表——手机上不知道模型 id 怎么拼的痛点 ([e63fb46](https://github.com/jianger666/cursor-feedback-extension/commit/e63fb46d96c8798912825f1e39c3246e949857c1))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * 全项目扫描修复 8 个 bug + 飞书体验优化 ([d463835](https://github.com/jianger666/cursor-feedback-extension/commit/d46383570f514341fe2ef1a1dba5acbabbb9614e))
16
+
5
17
  ### [2.7.2](https://github.com/jianger666/cursor-feedback-extension/compare/v2.7.1...v2.7.2) (2026-07-07)
6
18
 
7
19
 
package/README.md CHANGED
@@ -247,8 +247,10 @@ Send these directly in the bot chat:
247
247
  | `/new /abs/path task description` | Launch in an explicit working directory |
248
248
  | `/new project-name task description` | Launch in a project Cursor has opened before (unique folder-name match) |
249
249
  | `/projects` | List project paths Cursor has opened (to look up / copy into `/new`) |
250
+ | `/status` | Show the running CLI session (task, elapsed time, model) |
250
251
  | `/stop` | Terminate the running CLI session |
251
252
  | `/model [modelId]` | Show / set the session model (persisted) |
253
+ | `/models [keyword]` | List available models (curated picks by default; filter with a keyword, e.g. `/models fable`) |
252
254
  | `/help` | Show command usage |
253
255
 
254
256
  - Sessions run in **non-interactive mode** with the model specified via `--model` flag.
package/README_CN.md CHANGED
@@ -251,8 +251,10 @@ npm install -g cursor-feedback
251
251
  | `/new /绝对路径 任务描述` | 指定工作目录拉起 |
252
252
  | `/new 项目名 任务描述` | 在 Cursor 打开过的项目里拉起(目录名唯一匹配) |
253
253
  | `/projects` | 列出 Cursor 打开过的项目路径(供查路径 / 复制给 `/new`) |
254
+ | `/status` | 查看运行中会话状态(任务、已运行时长、模型) |
254
255
  | `/stop` | 终止运行中的 CLI 会话 |
255
256
  | `/model [模型id]` | 查看 / 设置会话模型(持久化) |
257
+ | `/models [关键词]` | 查可用模型列表(默认常用推荐;带关键词搜索,如 `/models fable`) |
256
258
  | `/help` | 查看命令用法 |
257
259
 
258
260
  - 会话以**非交互模式**运行,通过 `--model` 参数指定模型。
@@ -68,6 +68,8 @@ class CliLauncher {
68
68
  this.startedAt = 0;
69
69
  this.taskBrief = '';
70
70
  this.stopRequested = false;
71
+ /** --list-models 结果缓存(拉一次要几秒,10 分钟内复用) */
72
+ this.modelsCache = null;
71
73
  }
72
74
  /* ---------- 磁盘全局会话锁 ----------
73
75
  * 「同时只跑一个 CLI 会话」必须全局生效:多窗口 + 守护进程是多实例并存,
@@ -105,10 +107,12 @@ class CliLauncher {
105
107
  static pidLooksLikeAgent(pid) {
106
108
  try {
107
109
  if (process.platform === 'win32') {
108
- const out = (0, child_process_1.execFileSync)('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
109
- encoding: 'utf-8',
110
- });
111
- return /cursor-agent|node|cmd\.exe/i.test(out);
110
+ // 查完整命令行而不是进程名:会话进程是 cmd.exe 转发拉起的,进程名只会是
111
+ // cmd.exe / node.exe——按名字匹配等于放行所有 node 进程,pid 复用后会误杀
112
+ // 用户无关进程(如 dev server)。命令行含 cursor-agent 才算真命中。
113
+ const out = (0, child_process_1.execFileSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command',
114
+ `(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CommandLine`], { encoding: 'utf-8' });
115
+ return /cursor-agent/i.test(out);
112
116
  }
113
117
  const out = (0, child_process_1.execFileSync)('ps', ['-o', 'command=', '-p', String(pid)], { encoding: 'utf-8' });
114
118
  return /cursor-agent/i.test(out);
@@ -201,6 +205,39 @@ class CliLauncher {
201
205
  this.readSettings().model ||
202
206
  CliLauncher.DEFAULT_MODEL);
203
207
  }
208
+ /**
209
+ * 可用模型列表(调 cursor-agent --list-models 并解析)。
210
+ * 供飞书 /models 命令用:用户在手机上不知道模型 id 怎么拼,给他们一份带显示名的清单。
211
+ * 失败(未安装 / 未登录 / 超时)返回空数组,调用方给引导文案。
212
+ */
213
+ listModels() {
214
+ if (this.modelsCache && Date.now() - this.modelsCache.at < 10 * 60 * 1000) {
215
+ return this.modelsCache.list;
216
+ }
217
+ const bin = this.findBinary();
218
+ if (!bin)
219
+ return [];
220
+ try {
221
+ const isWin = process.platform === 'win32';
222
+ const viaCmdShell = isWin && /\.(cmd|bat|ps1)$/i.test(bin);
223
+ const out = viaCmdShell
224
+ ? (0, child_process_1.execFileSync)('cmd.exe', ['/d', '/s', '/c', bin, '--list-models'], { encoding: 'utf-8', timeout: 30000 })
225
+ : (0, child_process_1.execFileSync)(bin, ['--list-models'], { encoding: 'utf-8', timeout: 30000 });
226
+ const list = [];
227
+ for (const line of out.split('\n')) {
228
+ const m = line.match(/^(\S+) - (.+)$/);
229
+ if (m)
230
+ list.push({ id: m[1], name: m[2].trim() });
231
+ }
232
+ if (list.length > 0)
233
+ this.modelsCache = { at: Date.now(), list };
234
+ return list;
235
+ }
236
+ catch (e) {
237
+ clog('拉取模型列表失败: ' + e);
238
+ return [];
239
+ }
240
+ }
204
241
  /** Cursor IDE 的 storage.json 路径(平台相关) */
205
242
  static storageJsonPath() {
206
243
  if (process.platform === 'darwin') {
@@ -279,7 +316,19 @@ class CliLauncher {
279
316
  return p;
280
317
  }
281
318
  }
282
- return 'cursor-agent'; // 最后交给 PATH 解析,spawn error 时报错提示
319
+ // 常见安装位都没有 PATH(which/where)。真找不到时返回 null,
320
+ // 让 start() 给出「怎么装」的友好提示,而不是晦涩的 spawn ENOENT
321
+ try {
322
+ const probe = process.platform === 'win32' ? 'where' : 'which';
323
+ const out = (0, child_process_1.execFileSync)(probe, ['cursor-agent'], { encoding: 'utf-8' }).trim();
324
+ const first = out.split(/\r?\n/)[0];
325
+ if (first)
326
+ return first;
327
+ }
328
+ catch {
329
+ // PATH 里也没有
330
+ }
331
+ return null;
283
332
  }
284
333
  /**
285
334
  * spawn 前把 ~/.cursor/mcp.json 里的非字符串 env 值归一化为字符串。
@@ -355,7 +404,10 @@ class CliLauncher {
355
404
  const bin = this.findBinary();
356
405
  if (!bin) {
357
406
  this.releaseLock();
358
- return '找不到 cursor-agent,可通过环境变量 CURSOR_AGENT_PATH 指定路径。';
407
+ return ('这台电脑还没安装 Cursor CLI(cursor-agent)。\n' +
408
+ '安装方法:在电脑终端执行 curl https://cursor.com/install -fsS | bash\n' +
409
+ '(Windows 用 PowerShell:irm https://cursor.com/install.ps1 | iex)\n' +
410
+ '装好后重新 /new 即可;装在非默认位置时可用环境变量 CURSOR_AGENT_PATH 指定路径。');
359
411
  }
360
412
  const workDir = fs.existsSync(cwd) ? cwd : os.homedir();
361
413
  const prompt = this.buildPrompt(task);
@@ -409,7 +461,13 @@ class CliLauncher {
409
461
  }, CliLauncher.SESSION_MAX_MS);
410
462
  child.stdout?.on('data', (chunk) => { stdout = append(stdout, chunk); });
411
463
  child.stderr?.on('data', (chunk) => { stderr = append(stderr, chunk); });
464
+ // spawn 失败时 Node 会先后触发 error 和 close 两个事件(实测证实),
465
+ // 不加防重标志 finish 会跑两次 → 用户在飞书收到两条收尾消息
466
+ let finished = false;
412
467
  const finish = (code) => {
468
+ if (finished)
469
+ return;
470
+ finished = true;
413
471
  clearTimeout(killTimer);
414
472
  const elapsedMs = Date.now() - this.startedAt;
415
473
  // 跨实例 /stop 会在锁上打 stopRequested 标记再杀进程,这里一并算「主动终止」
@@ -117,24 +117,30 @@ function copySelf() {
117
117
  throw new Error(`当前包缺少 ${item}(${src}),无法安装守护`);
118
118
  fs.cpSync(src, path.join(app, item), { recursive: true });
119
119
  }
120
+ // 依赖来源可能有两处且可同时存在:父级 node_modules(npm 扁平布局的共享依赖)、
121
+ // pkgRoot/node_modules(源码布局的全部依赖 / npm 版本冲突时的嵌套依赖)。
122
+ // 必须两处都拷(父级先拷、嵌套后拷覆盖同名),只拷一处会丢依赖导致守护起不来。
120
123
  const ownDeps = path.join(pkgRoot, 'node_modules');
121
124
  const parentDeps = path.resolve(pkgRoot, '..');
122
- let depsSrc = null;
123
- if (fs.existsSync(ownDeps)) {
124
- depsSrc = ownDeps;
125
- }
126
- else if (path.basename(parentDeps) === 'node_modules') {
127
- depsSrc = parentDeps;
128
- }
129
- if (!depsSrc)
125
+ const sources = [];
126
+ if (path.basename(parentDeps) === 'node_modules')
127
+ sources.push(parentDeps);
128
+ if (fs.existsSync(ownDeps))
129
+ sources.push(ownDeps);
130
+ if (sources.length === 0)
130
131
  throw new Error('找不到依赖 node_modules,无法安装守护');
131
- fs.cpSync(depsSrc, path.join(app, 'node_modules'), {
132
- recursive: true,
133
- dereference: true,
134
- // npm 布局下父级 node_modules 里含 cursor-feedback 自身,跳过避免套娃拷贝
135
- filter: (src) => !src.includes(`${path.sep}cursor-feedback${path.sep}`) &&
136
- !src.endsWith(`${path.sep}cursor-feedback`),
137
- });
132
+ for (const depsSrc of sources) {
133
+ // 只精确排除 depsSrc 直下的 cursor-feedback 包目录(防套娃拷贝自身)。
134
+ // 不能按「路径含 /cursor-feedback/」模糊排除:安装路径的上级目录恰好叫
135
+ // cursor-feedback 时会把所有依赖误排掉,拷出一个空 node_modules。
136
+ const selfPkg = path.join(depsSrc, 'cursor-feedback');
137
+ fs.cpSync(depsSrc, path.join(app, 'node_modules'), {
138
+ recursive: true,
139
+ dereference: true,
140
+ force: true,
141
+ filter: (src) => src !== selfPkg && !src.startsWith(selfPkg + path.sep),
142
+ });
143
+ }
138
144
  dlog(`包已拷贝到 ${app}`);
139
145
  }
140
146
  /* ------------------------------ macOS (launchd) ------------------------------ */
@@ -292,10 +298,23 @@ function winTaskExists() {
292
298
  return false;
293
299
  }
294
300
  }
301
+ /** semver 大小比较:a < b 返回 -1,相等 0,a > b 返回 1(只比主.次.补丁数字段) */
302
+ function compareSemver(a, b) {
303
+ const pa = a.split('.').map((x) => parseInt(x, 10) || 0);
304
+ const pb = b.split('.').map((x) => parseInt(x, 10) || 0);
305
+ for (let i = 0; i < 3; i++) {
306
+ if ((pa[i] || 0) < (pb[i] || 0))
307
+ return -1;
308
+ if ((pa[i] || 0) > (pb[i] || 0))
309
+ return 1;
310
+ }
311
+ return 0;
312
+ }
295
313
  /**
296
314
  * 守护自动升级:已安装的守护是安装时的快照,不会随 npx @latest 走。
297
- * IDE 里的新版 server 启动时调用这里——发现守护版本与自己不一致就静默重装
315
+ * IDE 里的新版 server 启动时调用这里——发现守护版本【低于】自己就静默重装
298
316
  * (重装会 bootout 旧守护、拷贝新包、重新拉起),用户零操作。
317
+ * 只升不降:某个窗口 pin 了旧版包时绝不能把新守护降级回去(否则新旧窗口互相拉锯)。
299
318
  *
300
319
  * 并发防护:多窗口 = 多个 server 同时启动,重装涉及删目录 + 整树拷贝,撞上会互相
301
320
  * 写坏。用 wx 独占锁串行化,抢不到的直接跳过(反正有人在装);残锁超 10 分钟视为
@@ -306,7 +325,8 @@ function winTaskExists() {
306
325
  function upgradeDaemonIfOutdated(currentVersion) {
307
326
  try {
308
327
  const st = daemonStatus();
309
- if (!st.installed || !st.installedVersion || st.installedVersion === currentVersion) {
328
+ // 只在守护版本严格低于当前版本时才重装(防止 pin 旧版的窗口把新守护降级)
329
+ if (!st.installed || !st.installedVersion || compareSemver(st.installedVersion, currentVersion) >= 0) {
310
330
  return false;
311
331
  }
312
332
  const lock = path.join(daemonRoot(), 'upgrade.lock');
package/dist/extension.js CHANGED
@@ -776,8 +776,13 @@ class FeedbackViewProvider {
776
776
  */
777
777
  async _handleFeedbackSubmit(payload) {
778
778
  // 提交到「当前请求所属」的端口:请求与端口强绑定,绝不能退回 basePort——
779
- // 多项目同时触发时 basePort 可能是别的项目的 server,会得到 "Request not found"
780
- const port = this._currentRequestPort || this._activePort || this._basePort;
779
+ // 多项目同时触发时 basePort 可能是别的项目的 server,会得到 "Request not found"
780
+ // 两个端口都被抖动置空时明确报错让用户重试(下一秒轮询会找回端口),绝不瞎提交。
781
+ const port = this._currentRequestPort || this._activePort;
782
+ if (!port) {
783
+ vscode.window.showErrorMessage(this._i18n.submitFailed + ': ' + this._i18n.cannotConnectMCP);
784
+ return;
785
+ }
781
786
  try {
782
787
  const response = await this._httpPost(`http://127.0.0.1:${port}/api/feedback/submit`, JSON.stringify({
783
788
  requestId: payload.requestId,
@@ -1064,7 +1069,10 @@ class FeedbackViewProvider {
1064
1069
  const requestId = payload?.requestId;
1065
1070
  if (!requestId || typeof payload?.paused !== 'boolean')
1066
1071
  return;
1067
- const port = this._currentRequestPort || this._activePort || this._basePort;
1072
+ // _handleFeedbackSubmit:绝不回退 basePort(可能是别的项目的 server)
1073
+ const port = this._currentRequestPort || this._activePort;
1074
+ if (!port)
1075
+ return; // 端口抖动瞬间:静默跳过,倒计时照常走,用户可再点
1068
1076
  try {
1069
1077
  const response = await this._httpPost(`http://127.0.0.1:${port}/api/feedback/pause`, JSON.stringify({ requestId, paused: payload.paused }));
1070
1078
  const result = JSON.parse(response);
package/dist/feishu.js CHANGED
@@ -426,7 +426,24 @@ class FeishuBridge {
426
426
  */
427
427
  bufferInbound(chatId, parentId, messageId, text, images, files) {
428
428
  const key = chatId || '_';
429
- const existing = this.inboundBuffer.get(key);
429
+ let existing = this.inboundBuffer.get(key);
430
+ // 用户在合并窗口内先后回复了两张不同卡片:两条是给不同请求的独立回复,
431
+ // 绝不能合并(旧实现会丢掉第二条的 parentId、两条都窜进第一张卡片)。
432
+ // 仅当两条都显式带 parentId 且不同才拆开结算;「无 → 有」维持原合并行为
433
+ // (飞书图文拆条时部分分片可能不带 parent_id,不能误拆)。
434
+ if (existing && existing.parentId && parentId && existing.parentId !== parentId) {
435
+ clearTimeout(existing.timer);
436
+ this.inboundBuffer.delete(key);
437
+ this.onReply?.({
438
+ parentId: existing.parentId,
439
+ text: existing.texts.join('\n').trim(),
440
+ chatId,
441
+ messageId: existing.lastMessageId,
442
+ images: existing.images,
443
+ files: existing.files,
444
+ });
445
+ existing = undefined;
446
+ }
430
447
  if (existing)
431
448
  clearTimeout(existing.timer);
432
449
  const buf = existing || {
@@ -399,7 +399,7 @@ class McpFeedbackServer {
399
399
  return;
400
400
  // 斜杠命令优先于反馈路由:用户显式输入命令时不应被当成对某张卡片的回复或排队消息。
401
401
  // 大小写不敏感:手机输入法常自动把句首字母大写(/New),必须容错。
402
- if (/^\s*[//](new|stop|model|cwd|help|projects)\b/i.test(text) && this.handleCliCommand(text, chatId))
402
+ if (/^\s*[//](new|stop|model|models|cwd|help|projects|status)\b/i.test(text) && this.handleCliCommand(text, chatId))
403
403
  return;
404
404
  if (parentId) {
405
405
  // 用户「回复」了某条卡片 → 用 parent_id 精确路由
@@ -500,8 +500,10 @@ class McpFeedbackServer {
500
500
  * - /new [目录或项目名] 任务描述:拉起 headless CLI 会话(非交互模式)。
501
501
  * 工作目录刻意保持简单:显式指定 > 主目录,与当前开着哪些 IDE 窗口无关。
502
502
  * - /projects:列出 Cursor 打开过的项目路径(供手机上查路径 / 复制给 /new)
503
+ * - /status:查看当前 CLI 会话状态
503
504
  * - /stop:终止运行中的 CLI 会话
504
505
  * - /model [模型id]:查看 / 设置 CLI 会话模型(持久化)
506
+ * - /models [关键词]:可用模型列表(无关键词给常用推荐,带关键词过滤)
505
507
  */
506
508
  handleCliCommand(rawText, chatId) {
507
509
  // 输入容错:首尾空格(飞书入站已 trim,这里兜底;trim 同样覆盖全角空格 U+3000);
@@ -518,10 +520,14 @@ class McpFeedbackServer {
518
520
  '· AI 需要沟通时会发反馈卡片,直接回复即可;同时只能跑一个会话\n\n' +
519
521
  '📁 /projects\n' +
520
522
  '列出 Cursor 打开过的项目路径(查路径 / 复制给 /new,也可直接用项目名)\n\n' +
523
+ '🏃 /status\n' +
524
+ '查看当前 CLI 会话状态(任务、已运行时长、模型)\n\n' +
521
525
  '🛑 /stop\n' +
522
526
  '终止运行中的 CLI 会话(任何窗口拉起的都能停)\n\n' +
523
527
  '🧠 /model [模型id]\n' +
524
- '查看 / 设置会话模型(持久化)\n\n' +
528
+ '查看 / 设置会话模型(持久化),Max 变体也可以随意选\n\n' +
529
+ '📋 /models [关键词]\n' +
530
+ '查可用模型列表(不带关键词看常用推荐;带关键词搜索,如 /models fable)\n\n' +
525
531
  '❓ /help\n' +
526
532
  '看这份帮助\n\n' +
527
533
  '💬 不带斜杠的消息照常作为反馈:回复某张卡片就送达那个请求,直接发送则给当前等待中的 AI。');
@@ -549,14 +555,75 @@ class McpFeedbackServer {
549
555
  }
550
556
  return true;
551
557
  }
558
+ if (/^\/status\b/i.test(text)) {
559
+ if (this.cliLauncher.isRunning()) {
560
+ this.feishu.replyText(chatId, `🏃 CLI 会话运行中:${this.cliLauncher.describe()}\n` +
561
+ `模型:${this.cliLauncher.model()}\n` +
562
+ 'AI 需要沟通时会发反馈卡片;发 /stop 可终止。');
563
+ }
564
+ else {
565
+ this.feishu.replyText(chatId, '当前没有运行中的 CLI 会话。发 /new 任务描述 可拉起一个。');
566
+ }
567
+ return true;
568
+ }
569
+ // /models [关键词]:可用模型列表。全量 140+ 个在手机上太长,无关键词时给常用推荐 +
570
+ // 搜索引导;带关键词按 id / 显示名过滤(大小写不敏感)
571
+ const mModels = text.match(/^\/models(?:\s+(.+))?\s*$/i);
572
+ if (mModels) {
573
+ const all = this.cliLauncher.listModels();
574
+ if (all.length === 0) {
575
+ this.feishu.replyText(chatId, '拉取模型列表失败(cursor-agent 未安装或未登录)。可直接 /model 模型id 手动设置。');
576
+ return true;
577
+ }
578
+ const kw = (mModels[1] || '').trim().toLowerCase();
579
+ if (kw) {
580
+ const hits = all.filter((m) => m.id.toLowerCase().includes(kw) || m.name.toLowerCase().includes(kw));
581
+ if (hits.length === 0) {
582
+ this.feishu.replyText(chatId, `没有匹配「${mModels[1]?.trim()}」的模型。试试 /models fable、/models opus、/models gpt`);
583
+ }
584
+ else {
585
+ const MAX_LIST = 40;
586
+ const lines = hits.slice(0, MAX_LIST).map((m) => `· ${m.id}\n ${m.name}`);
587
+ this.feishu.replyText(chatId, `匹配「${mModels[1]?.trim()}」的模型(${hits.length} 个${hits.length > MAX_LIST ? `,只列前 ${MAX_LIST}` : ''}):\n` +
588
+ lines.join('\n') +
589
+ '\n\n设置:/model 模型id');
590
+ }
591
+ }
592
+ else {
593
+ // 常用推荐:从实时列表里挑出存在的(模型下线自动消失,不硬编码失效项)
594
+ const picks = [
595
+ 'claude-fable-5-thinking-xhigh',
596
+ 'claude-fable-5-thinking-max',
597
+ 'claude-opus-4-8-thinking-high',
598
+ 'claude-sonnet-5-thinking-high',
599
+ 'gpt-5.5-medium',
600
+ 'gpt-5.3-codex-high',
601
+ 'composer-2.5-fast',
602
+ 'gemini-3.1-pro',
603
+ ];
604
+ const byId = new Map(all.map((m) => [m.id, m]));
605
+ const lines = picks
606
+ .filter((id) => byId.has(id))
607
+ .map((id) => `· ${id}\n ${byId.get(id).name}`);
608
+ this.feishu.replyText(chatId, `共 ${all.length} 个可用模型。常用推荐:\n${lines.join('\n')}\n\n` +
609
+ '🔍 按关键词搜完整列表:/models fable、/models opus、/models sonnet、/models gpt、/models max\n' +
610
+ `设置:/model 模型id(当前:${this.cliLauncher.model()})`);
611
+ }
612
+ return true;
613
+ }
552
614
  const mModel = text.match(/^\/model(?:\s+(\S+))?\s*$/i);
553
615
  if (mModel) {
554
616
  if (mModel[1]) {
617
+ // 尽力校验:列表拉得到且不含该 id 时提醒(可能是笔误),但不拦截——
618
+ // 列表可能滞后于新模型上线,用户明确要设就尊重
619
+ const all = this.cliLauncher.listModels();
620
+ const known = all.length === 0 || all.some((m) => m.id === mModel[1]);
555
621
  this.cliLauncher.writeSettings({ model: mModel[1] });
556
- this.feishu.replyText(chatId, `✅ CLI 模型已设为 ${mModel[1]}`);
622
+ this.feishu.replyText(chatId, `✅ CLI 模型已设为 ${mModel[1]}` +
623
+ (known ? '' : '\n⚠️ 这个 id 不在当前模型列表里(可能拼错了),发 /models 可查可用模型。'));
557
624
  }
558
625
  else {
559
- this.feishu.replyText(chatId, `当前 CLI 模型:${this.cliLauncher.model()}\n设置:/model 模型id(例如 /model claude-fable-5-thinking-xhigh)`);
626
+ this.feishu.replyText(chatId, `当前 CLI 模型:${this.cliLauncher.model()}\n设置:/model 模型id\n查列表:/models(可加关键词,如 /models fable)`);
560
627
  }
561
628
  return true;
562
629
  }
@@ -620,11 +687,11 @@ class McpFeedbackServer {
620
687
  this.feishu.replyText(chatId, '❌ 拉起 CLI 会话失败:' + err);
621
688
  }
622
689
  else {
623
- this.feishu.replyText(chatId, '🚀 CLI 会话已拉起(非交互模式)\n' +
690
+ this.feishu.replyText(chatId, '🚀 CLI 会话已拉起\n' +
624
691
  `模型:${this.cliLauncher.model()}\n` +
625
692
  `工作目录:${cwd}\n` +
626
693
  `任务:${task.length > 100 ? task.slice(0, 100) + '…' : task}\n\n` +
627
- 'AI 需要和你沟通时会发反馈卡片,直接回复卡片即可;发 /stop 可随时终止。');
694
+ 'AI 需要和你沟通时会发反馈卡片,直接回复卡片即可。\n随时可发 /status 看进度、/stop 终止。');
628
695
  }
629
696
  return true;
630
697
  }
@@ -1129,6 +1196,11 @@ class McpFeedbackServer {
1129
1196
  };
1130
1197
  }
1131
1198
  else {
1199
+ // 覆盖别的会话(chatId 不同)的未过期暂存时,先给旧消息回执「未送达」——
1200
+ // 静默覆盖会让旧消息连过期提示都收不到(定时器随即被重置)
1201
+ if (prev && prev.chatId !== chatId && Date.now() - prev.at <= prev.ttlMs) {
1202
+ this.feishu.replyToMessage(prev.messageId, prev.chatId, '⚠️ 消息未能送达 AI(已被更新的消息顶替)。需要的话请待 AI 回复后重新发送一次。');
1203
+ }
1132
1204
  this.stashedInbound = { text, chatId, images, files, at: Date.now(), messageId, ttlMs, forProjectDir };
1133
1205
  }
1134
1206
  this.armStashExpiryNotice();
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.7.2",
5
+ "version": "2.7.3",
6
6
  "icon": "icon.png",
7
7
  "author": "jianger666",
8
8
  "license": "MIT",