cursor-feedback 2.5.0 → 2.7.0

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,20 @@
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.0](https://github.com/jianger666/cursor-feedback-extension/compare/v2.6.0...v2.7.0) (2026-07-06)
6
+
7
+
8
+ ### Features
9
+
10
+ * **daemon:** 守护自动升级——IDE 新版 server 启动时发现守护版本落后即静默重装(带并发锁/残锁自清) ([738c270](https://github.com/jianger666/cursor-feedback-extension/commit/738c270f4bb6b4b6115f30b5c811fca26d10b43a))
11
+
12
+ ## [2.6.0](https://github.com/jianger666/cursor-feedback-extension/compare/v2.5.0...v2.6.0) (2026-07-06)
13
+
14
+
15
+ ### Features
16
+
17
+ * **diag:** server 日志落盘(按天切割/7天保留)+ 诊断包导出(端点/面板按钮/密钥脱敏/无server降级);/help 去掉计费说明 ([91a0d67](https://github.com/jianger666/cursor-feedback-extension/commit/91a0d67346a207afdab8d2df02278c40674bfbb5))
18
+
5
19
  ## [2.5.0](https://github.com/jianger666/cursor-feedback-extension/compare/v2.4.1...v2.5.0) (2026-07-06)
6
20
 
7
21
 
package/README.md CHANGED
@@ -289,8 +289,13 @@ Normally the server process is spawned by Cursor, so closing Cursor drops the Fe
289
289
 
290
290
  - **Enable**: the "Background service" toggle in the extension's notification settings, or `npx cursor-feedback@latest install-daemon` (`uninstall-daemon` / `daemon-status` likewise).
291
291
  - **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.
292
+ - **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
293
  - **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
294
  - **Typical use**: leave the machine plugged in and locked after work; `/new` from your phone anytime.
295
+
296
+ ### Diagnostics export
297
+
298
+ Server logs are also written to `~/.cursor-feedback/logs/` (daily files, 7-day retention). Hit **Export diagnostics** in the extension's notification settings to save a single report — recent logs, environment and sanitized config (secrets are masked) — to attach to bug reports. Works even when no server is running (falls back to reading log files directly).
294
299
  - After upgrading the extension, toggle the switch off/on once to refresh the daemon's copy.
295
300
 
296
301
  ## 🏗️ Architecture
package/README_CN.md CHANGED
@@ -293,8 +293,13 @@ npm install -g cursor-feedback
293
293
 
294
294
  - **开启方式**:插件面板「通知设置」里打开「常驻服务」开关;或命令行 `npx cursor-feedback@latest install-daemon`(卸载对应 `uninstall-daemon`,状态 `daemon-status`)。
295
295
  - **实现**:把当前包完整拷贝到 `~/.cursor-feedback/daemon/app`(自包含,不怕 npx 缓存被清),macOS 注册 launchd(崩溃自动拉起),Windows 注册登录计划任务。
296
+ - **自动升级**:守护拷贝不会「静默落后」——IDE 里的新版 server 启动时发现守护版本旧了,会静默重装并重启守护,无需手动操作。
296
297
  - **防睡眠**:守护进程在接电源时自动阻止系统睡眠(macOS `caffeinate -s`;Windows 电源断言),电池供电不生效、不偷耗电。锁屏本来就不影响后台进程。
297
298
  - **典型用法**:下班插着电源锁屏走人,手机飞书随时 `/new`。
299
+
300
+ ### 诊断包导出
301
+
302
+ server 日志会同时落盘到 `~/.cursor-feedback/logs/`(按天切割,保留 7 天)。在插件「通知设置」里点**导出诊断包**,一键保存包含最近日志、环境信息和脱敏配置(密钥打码)的报告文件,报 bug 时直接附上。即使 server 全挂了也能导(降级为直接读日志文件)。
298
303
  - 升级插件版本后重新开关一次即可更新守护进程里的拷贝。
299
304
 
300
305
  ## 🏗️ 架构
@@ -52,8 +52,10 @@ const child_process_1 = require("child_process");
52
52
  const fs = __importStar(require("fs"));
53
53
  const os = __importStar(require("os"));
54
54
  const path = __importStar(require("path"));
55
+ const logger_js_1 = require("./logger.js");
55
56
  function clog(message) {
56
57
  console.error(`[${new Date().toISOString()}] [cli-launcher] ${message}`);
58
+ (0, logger_js_1.fileLog)('cli-launcher', message);
57
59
  }
58
60
  class CliLauncher {
59
61
  constructor() {
@@ -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/extension.js CHANGED
@@ -38,6 +38,7 @@ exports.deactivate = deactivate;
38
38
  const vscode = __importStar(require("vscode"));
39
39
  const http = __importStar(require("http"));
40
40
  const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
41
42
  const path = __importStar(require("path"));
42
43
  const child_process_1 = require("child_process");
43
44
  const i18n_1 = require("./i18n");
@@ -303,6 +304,9 @@ class FeedbackViewProvider {
303
304
  case 'toggleDaemon':
304
305
  await this._handleToggleDaemon(!!data.payload?.enabled);
305
306
  break;
307
+ case 'exportDiagnostics':
308
+ await this._handleExportDiagnostics();
309
+ break;
306
310
  case 'feishuRegisterStart':
307
311
  await this._handleFeishuRegisterStart();
308
312
  break;
@@ -1392,6 +1396,63 @@ class FeedbackViewProvider {
1392
1396
  });
1393
1397
  }
1394
1398
  }
1399
+ /**
1400
+ * 导出诊断包:优先从活跃 server 拿完整报告(含运行时状态);server 全挂时降级为
1401
+ * 插件本地拼装(正好覆盖「server 起不来」这种最需要日志的场景)。存为 txt 并打开。
1402
+ */
1403
+ async _handleExportDiagnostics() {
1404
+ let report = null;
1405
+ const port = this._anyServerPort();
1406
+ if (port) {
1407
+ try {
1408
+ report = await this._httpGet(`http://127.0.0.1:${port}/api/diagnostics`);
1409
+ }
1410
+ catch {
1411
+ report = null;
1412
+ }
1413
+ }
1414
+ if (!report) {
1415
+ report = this._buildLocalDiagnostics();
1416
+ }
1417
+ const def = path.join(os.homedir(), 'Downloads', `cursor-feedback-diag-${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.txt`);
1418
+ const uri = await vscode.window.showSaveDialog({
1419
+ defaultUri: vscode.Uri.file(def),
1420
+ filters: { Text: ['txt'] },
1421
+ });
1422
+ if (!uri)
1423
+ return;
1424
+ await vscode.workspace.fs.writeFile(uri, Buffer.from(report, 'utf-8'));
1425
+ const open = this._t('diagOpenAction');
1426
+ const picked = await vscode.window.showInformationMessage(this._t('diagSaved'), open);
1427
+ if (picked === open) {
1428
+ await vscode.window.showTextDocument(uri);
1429
+ }
1430
+ }
1431
+ /** server 全挂时的降级诊断:插件自己读日志文件 + 基本环境信息(不含 server 运行时状态) */
1432
+ _buildLocalDiagnostics() {
1433
+ const parts = [
1434
+ `cursor-feedback 诊断报告(降级模式:没有活跃的 server,运行时状态缺失) 生成于 ${new Date().toISOString()}`,
1435
+ `===== 环境 =====\nextension version: ${vscode.extensions.getExtension('jianger666.cursor-feedback')?.packageJSON?.version || '?'}\nplatform: ${process.platform} (${process.arch})\nvscode: ${vscode.version}`,
1436
+ ];
1437
+ try {
1438
+ const dir = path.join(os.homedir(), '.cursor-feedback', 'logs');
1439
+ const files = fs
1440
+ .readdirSync(dir)
1441
+ .filter((f) => /^\d{4}-\d{2}-\d{2}\.log$/.test(f))
1442
+ .sort()
1443
+ .slice(-2);
1444
+ let text = '';
1445
+ for (const f of files) {
1446
+ text += `===== ${f} =====\n` + fs.readFileSync(path.join(dir, f), 'utf-8');
1447
+ }
1448
+ const MAX = 256 * 1024;
1449
+ parts.push(text.length > MAX ? '…(前文截断)\n' + text.slice(text.length - MAX) : text || '(没有日志文件)');
1450
+ }
1451
+ catch {
1452
+ parts.push('(没有日志文件)');
1453
+ }
1454
+ return parts.join('\n\n') + '\n';
1455
+ }
1395
1456
  /**
1396
1457
  * HTTP GET 请求
1397
1458
  */
package/dist/feishu.js CHANGED
@@ -50,8 +50,10 @@ exports.FeishuBridge = void 0;
50
50
  const fs = __importStar(require("fs"));
51
51
  const os = __importStar(require("os"));
52
52
  const path = __importStar(require("path"));
53
+ const logger_js_1 = require("./logger.js");
53
54
  function flog(message) {
54
55
  console.error(`[${new Date().toISOString()}] [feishu] ${message}`);
56
+ (0, logger_js_1.fileLog)('feishu', message);
55
57
  }
56
58
  class FeishuBridge {
57
59
  constructor() {
package/dist/i18n/en.json CHANGED
@@ -80,6 +80,11 @@
80
80
  "daemonNotSupported": "Not supported on this platform",
81
81
  "daemonFailed": "Operation failed: {error}",
82
82
  "daemonWorking": "Working…",
83
+ "diagLabel": "Diagnostics",
84
+ "diagDesc": "Export a diagnostic report (recent logs, environment and sanitized config — secrets are masked) to attach to bug reports.",
85
+ "diagExportBtn": "Export diagnostics",
86
+ "diagSaved": "Diagnostic report saved.",
87
+ "diagOpenAction": "Open",
83
88
  "queueSend": "Queue Message",
84
89
  "queuedTitle": "Queued messages",
85
90
  "queueModeHint": "AI is busy — messages sent now are queued and delivered when it next asks for feedback",
@@ -171,6 +171,11 @@ function getDefaultMessages() {
171
171
  daemonNotSupported: "Not supported on this platform",
172
172
  daemonFailed: "Operation failed: {error}",
173
173
  daemonWorking: "Working…",
174
+ diagLabel: "Diagnostics",
175
+ diagDesc: "Export a diagnostic report (recent logs, environment and sanitized config — secrets are masked) to attach to bug reports.",
176
+ diagExportBtn: "Export diagnostics",
177
+ diagSaved: "Diagnostic report saved.",
178
+ diagOpenAction: "Open",
174
179
  queueSend: "Queue Message",
175
180
  queuedTitle: "Queued messages",
176
181
  queueModeHint: "AI is busy — messages sent now are queued and delivered when it next asks for feedback",
@@ -80,6 +80,11 @@
80
80
  "daemonNotSupported": "当前平台暂不支持",
81
81
  "daemonFailed": "操作失败:{error}",
82
82
  "daemonWorking": "处理中…",
83
+ "diagLabel": "诊断信息",
84
+ "diagDesc": "导出诊断报告(最近日志、环境信息、脱敏后的配置——密钥不会包含在内),报 bug 时附上它。",
85
+ "diagExportBtn": "导出诊断包",
86
+ "diagSaved": "诊断报告已保存。",
87
+ "diagOpenAction": "打开",
83
88
  "queueSend": "排队发送",
84
89
  "queuedTitle": "排队中的消息",
85
90
  "queueModeHint": "AI 正忙,现在发送的消息会排队,等它下一轮询问时自动送达",
package/dist/logger.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.fileLog = fileLog;
37
+ exports.readRecentLogs = readRecentLogs;
38
+ /**
39
+ * 落盘日志:server 侧所有日志除了打到 stderr(Cursor 的 MCP 日志),同时写入
40
+ * ~/.cursor-feedback/logs/YYYY-MM-DD.log,供「导出诊断包」排查用户现场问题。
41
+ * 设计约束:绝不能因为日志失败影响主流程(全部吞错);日志量小,直接同步追加(崩溃不丢行)。
42
+ */
43
+ const fs = __importStar(require("fs"));
44
+ const os = __importStar(require("os"));
45
+ const path = __importStar(require("path"));
46
+ /** 日志保留天数:超过的按文件名日期清理 */
47
+ const RETENTION_DAYS = 7;
48
+ function logsDir() {
49
+ return path.join(os.homedir(), '.cursor-feedback', 'logs');
50
+ }
51
+ /** 本地日期 YYYY-MM-DD(日志按用户本地时区的自然日切割) */
52
+ function localDay(d = new Date()) {
53
+ const pad = (n) => String(n).padStart(2, '0');
54
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
55
+ }
56
+ let cleanedForDay = '';
57
+ /** 清理过期日志:每个进程每天最多执行一次 */
58
+ function cleanupOldLogs(today) {
59
+ if (cleanedForDay === today)
60
+ return;
61
+ cleanedForDay = today;
62
+ try {
63
+ const cutoff = new Date();
64
+ cutoff.setDate(cutoff.getDate() - RETENTION_DAYS);
65
+ const cutoffDay = localDay(cutoff);
66
+ for (const f of fs.readdirSync(logsDir())) {
67
+ const m = f.match(/^(\d{4}-\d{2}-\d{2})\.log$/);
68
+ // 文件名日期字典序即时间序;不匹配命名规则的文件不动
69
+ if (m && m[1] < cutoffDay) {
70
+ fs.rmSync(path.join(logsDir(), f), { force: true });
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ // 目录不存在或不可读,忽略
76
+ }
77
+ }
78
+ /** 追加一行日志到当天的文件。tag 用于区分模块(mcp / feishu / cli-launcher)。 */
79
+ function fileLog(tag, message) {
80
+ try {
81
+ const today = localDay();
82
+ fs.mkdirSync(logsDir(), { recursive: true });
83
+ cleanupOldLogs(today);
84
+ const line = `[${new Date().toISOString()}] [${tag}] ${message}\n`;
85
+ fs.appendFileSync(path.join(logsDir(), `${today}.log`), line);
86
+ }
87
+ catch {
88
+ // 磁盘满/权限问题都不能影响主流程
89
+ }
90
+ }
91
+ /**
92
+ * 读取最近的日志尾部(今天优先,不够再补前一天),用于诊断包。
93
+ * 返回不超过 maxBytes 的文本。
94
+ */
95
+ function readRecentLogs(maxBytes = 256 * 1024) {
96
+ try {
97
+ const files = fs
98
+ .readdirSync(logsDir())
99
+ .filter((f) => /^\d{4}-\d{2}-\d{2}\.log$/.test(f))
100
+ .sort()
101
+ .slice(-2);
102
+ let text = '';
103
+ for (const f of files) {
104
+ text += `===== ${f} =====\n`;
105
+ text += fs.readFileSync(path.join(logsDir(), f), 'utf-8');
106
+ }
107
+ return text.length > maxBytes ? '…(前文截断)\n' + text.slice(text.length - maxBytes) : text;
108
+ }
109
+ catch {
110
+ return '(没有日志文件)';
111
+ }
112
+ }
113
+ //# sourceMappingURL=logger.js.map
@@ -62,6 +62,7 @@ const feishu_js_1 = require("./feishu.js");
62
62
  const cli_launcher_js_1 = require("./cli-launcher.js");
63
63
  const keep_awake_js_1 = require("./keep-awake.js");
64
64
  const daemon_install_js_1 = require("./daemon-install.js");
65
+ const logger_js_1 = require("./logger.js");
65
66
  // ⚠️ MCP stdio 协议要求 stdout 只承载 JSON-RPC 消息。第三方库(尤其飞书 SDK 的内置 logger,
66
67
  // 输出形如 "[info]: [...]")会用 console.log/info/debug 往 stdout 打日志,一旦混入就会让
67
68
  // Cursor 端 JSON 解析失败、连接进入 failed(表现为 "Not connected")。这里在进程最早期把这三个
@@ -74,6 +75,7 @@ console.debug = (...args) => console.error(...args);
74
75
  function debugLog(message) {
75
76
  const timestamp = new Date().toISOString();
76
77
  console.error(`[${timestamp}] ${message}`);
78
+ (0, logger_js_1.fileLog)('mcp', message);
77
79
  }
78
80
  // 包版本真相源:package.json(dist/mcp-server.js 的上一级目录)。读不到时兜底 0.0.0。
79
81
  const PKG_VERSION = (() => {
@@ -513,14 +515,13 @@ class McpFeedbackServer {
513
515
  '· 默认工作目录是主目录;可在任务前指定项目:\n' +
514
516
  ' /new /Users/me/proj 帮我修下测试\n' +
515
517
  ' /new crm-web 帮我修下测试(项目名唯一时自动匹配到完整路径)\n' +
516
- '· 非交互模式 + 强制 maxMode=false,每次会话固定只扣 1 次请求\n' +
517
518
  '· AI 需要沟通时会发反馈卡片,直接回复即可;同时只能跑一个会话\n\n' +
518
519
  '📁 /projects\n' +
519
520
  '列出 Cursor 打开过的项目路径(查路径 / 复制给 /new,也可直接用项目名)\n\n' +
520
521
  '🛑 /stop\n' +
521
522
  '终止运行中的 CLI 会话(任何窗口拉起的都能停)\n\n' +
522
523
  '🧠 /model [模型id]\n' +
523
- '查看 / 设置会话模型(持久化;无论选什么模型都不会走 Max 计费)\n\n' +
524
+ '查看 / 设置会话模型(持久化)\n\n' +
524
525
  '❓ /help\n' +
525
526
  '看这份帮助\n\n' +
526
527
  '💬 不带斜杠的消息照常作为反馈:回复某张卡片就送达那个请求,直接发送则给当前等待中的 AI。');
@@ -627,6 +628,64 @@ class McpFeedbackServer {
627
628
  }
628
629
  return true;
629
630
  }
631
+ /**
632
+ * 组装诊断报告(纯文本)。密钥严格脱敏:appSecret 绝不输出,appId 只留前 8 位。
633
+ * 每个区块独立 try/catch,单块失败不影响整包导出。
634
+ */
635
+ buildDiagnostics() {
636
+ const sections = [];
637
+ const add = (title, fn) => {
638
+ let body;
639
+ try {
640
+ body = fn();
641
+ }
642
+ catch (e) {
643
+ body = `(读取失败:${e})`;
644
+ }
645
+ sections.push(`===== ${title} =====\n${body}`);
646
+ };
647
+ const maskId = (id) => (id.length > 8 ? id.slice(0, 8) + '***' : '***');
648
+ const readJson = (file) => {
649
+ try {
650
+ return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.cursor-feedback', file), 'utf-8'));
651
+ }
652
+ catch {
653
+ return null;
654
+ }
655
+ };
656
+ add('环境', () => [
657
+ `version: ${PKG_VERSION}`,
658
+ `pid: ${process.pid} uptimeSec: ${Math.round(process.uptime())}`,
659
+ `platform: ${process.platform} ${os.release()} (${process.arch})`,
660
+ `node: ${process.version}`,
661
+ `daemonMode: ${this.daemonMode}`,
662
+ `ownerWorkspace: ${this.ownerWorkspace || '(none)'}`,
663
+ `hasCurrentRequest: ${this.currentRequest !== null}`,
664
+ ].join('\n'));
665
+ add('飞书配置(脱敏)', () => {
666
+ const cfg = readJson('feishu-config.json');
667
+ if (!cfg)
668
+ return '(未配置)';
669
+ const safe = { ...cfg };
670
+ if (typeof safe.appId === 'string')
671
+ safe.appId = maskId(safe.appId);
672
+ if ('appSecret' in safe)
673
+ safe.appSecret = '***';
674
+ return JSON.stringify(safe, null, 2);
675
+ });
676
+ add('飞书绑定(appId 脱敏)', () => {
677
+ const bind = readJson('feishu-bind.json');
678
+ if (!bind)
679
+ return '(无)';
680
+ return JSON.stringify(Object.fromEntries(Object.entries(bind).map(([k, v]) => [maskId(k), v])), null, 2);
681
+ });
682
+ add('通用设置 settings.json', () => JSON.stringify(readJson('settings.json') ?? '(无)'));
683
+ add('CLI 设置 cli.json', () => JSON.stringify(readJson('cli.json') ?? '(无)'));
684
+ add('CLI 会话锁', () => JSON.stringify(readJson('cli-session.lock') ?? '(无活跃会话)'));
685
+ add('常驻服务状态', () => JSON.stringify((0, daemon_install_js_1.daemonStatus)(), null, 2));
686
+ add('最近日志(今天 + 昨天,尾部截断)', () => (0, logger_js_1.readRecentLogs)());
687
+ return `cursor-feedback 诊断报告 生成于 ${new Date().toISOString()}\n\n` + sections.join('\n\n') + '\n';
688
+ }
630
689
  /** CLI 会话结束的收尾回执:把 agent 的最终输出(尾部)发回飞书 */
631
690
  onCliSessionDone(result, chatId) {
632
691
  const mins = Math.max(1, Math.round(result.elapsedMs / 60000));
@@ -1936,6 +1995,13 @@ class McpFeedbackServer {
1936
1995
  });
1937
1996
  return;
1938
1997
  }
1998
+ // 诊断包:版本/环境/配置(脱敏)/守护状态/会话锁 + 最近日志尾部,纯文本一把梭。
1999
+ // 插件面板「导出诊断包」按钮请求这里;轻量起见不打 zip,一个 txt 就够排查。
2000
+ if (req.method === 'GET' && req.url === '/api/diagnostics') {
2001
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
2002
+ res.end(this.buildDiagnostics());
2003
+ return;
2004
+ }
1939
2005
  // 健康检查
1940
2006
  if (req.method === 'GET' && req.url === '/api/health') {
1941
2007
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -2085,6 +2151,17 @@ class McpFeedbackServer {
2085
2151
  this.startWatchdog();
2086
2152
  debugLog('MCP Server started successfully');
2087
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);
2088
2165
  }
2089
2166
  catch (error) {
2090
2167
  debugLog(`Failed to start server: ${error}`);
@@ -221,6 +221,15 @@
221
221
  <p id="daemonStatusText" class="feishu-modal__desc feishu-modal__desc--sub"></p>
222
222
  </div>
223
223
 
224
+ <!-- 区块③b:诊断信息(导出日志 + 环境 + 脱敏配置,报 bug 用) -->
225
+ <div class="feishu-section">
226
+ <span class="feishu-switch__label feishu-switch__label--strong">{{i18n.diagLabel}}</span>
227
+ <p class="feishu-modal__desc">{{i18n.diagDesc}}</p>
228
+ <div class="feishu-modal__actions">
229
+ <button id="diagExportBtn" class="feishu-link" type="button">{{i18n.diagExportBtn}}</button>
230
+ </div>
231
+ </div>
232
+
224
233
  <!-- 区块④:飞书通知(推送开关 + 凭证 + 绑定状态) -->
225
234
  <div class="feishu-section">
226
235
  <label class="feishu-switch">
@@ -243,6 +243,11 @@
243
243
  daemonStatusText.textContent = i18n.daemonWorking || 'Working…';
244
244
  vscode.postMessage({ type: 'toggleDaemon', payload: { enabled: daemonToggle.checked } });
245
245
  });
246
+ // ---------- 诊断包导出 ----------
247
+ document.getElementById('diagExportBtn').addEventListener('click', () => {
248
+ vscode.postMessage({ type: 'exportDiagnostics' });
249
+ });
250
+
246
251
  function updateDaemonUI(s) {
247
252
  if (!s) return;
248
253
  daemonToggle.disabled = !s.supported;
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.0",
5
+ "version": "2.7.0",
6
6
  "icon": "icon.png",
7
7
  "author": "jianger666",
8
8
  "license": "MIT",
@@ -147,7 +147,7 @@
147
147
  "watch": "tsc -watch -p ./",
148
148
  "lint": "eslint src --ext ts",
149
149
  "start:mcp": "node dist/mcp-server.js",
150
- "test:smoke": "npm run compile && node scripts/smoke-queue.js && node scripts/smoke-dup.js && node scripts/smoke-cli-launcher.js && node scripts/smoke-daemon.js",
150
+ "test:smoke": "npm run compile && node scripts/smoke-queue.js && node scripts/smoke-dup.js && node scripts/smoke-cli-launcher.js && node scripts/smoke-daemon.js && node scripts/smoke-logger.js",
151
151
  "prepublishOnly": "npm run check-changelog && npm run compile",
152
152
  "check-changelog": "node scripts/check-changelog.js",
153
153
  "release": "standard-version",