cursor-feedback 2.3.1 → 2.4.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.
@@ -0,0 +1,292 @@
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.daemonSupported = daemonSupported;
37
+ exports.installDaemon = installDaemon;
38
+ exports.uninstallDaemon = uninstallDaemon;
39
+ exports.daemonStatus = daemonStatus;
40
+ /**
41
+ * 常驻守护进程的安装 / 卸载 / 状态查询。
42
+ *
43
+ * 目标:Cursor(IDE)不开时飞书链路依然可用——server 以 standalone 守护方式开机自启。
44
+ *
45
+ * 安装策略:把「当前正在运行的这份包」(dist + package.json + 依赖 node_modules)
46
+ * 完整复制到 ~/.cursor-feedback/daemon/app/,自启配置指向这份拷贝。
47
+ * 为什么复制而不是原地引用:npx 缓存路径会被 npm 清理、扩展目录随版本升级变化,
48
+ * 原地引用会让守护进程某天悄悄失联;拷贝是自包含的,升级时重装一次即可覆盖。
49
+ *
50
+ * 平台注册方式:
51
+ * - macOS:~/Library/LaunchAgents/<label>.plist(launchd:开机自启 + 崩溃自动拉起)
52
+ * - Windows:schtasks 登录触发计划任务(无需管理员),经 wscript + vbs 隐藏窗口启动
53
+ */
54
+ const child_process_1 = require("child_process");
55
+ const fs = __importStar(require("fs"));
56
+ const os = __importStar(require("os"));
57
+ const path = __importStar(require("path"));
58
+ const LABEL = 'com.jianger666.cursor-feedback.daemon';
59
+ const WIN_TASK_NAME = 'CursorFeedbackDaemon';
60
+ function dlog(message) {
61
+ console.error(`[${new Date().toISOString()}] [daemon-install] ${message}`);
62
+ }
63
+ function daemonRoot() {
64
+ return path.join(os.homedir(), '.cursor-feedback', 'daemon');
65
+ }
66
+ function appRoot() {
67
+ return path.join(daemonRoot(), 'app');
68
+ }
69
+ function entryPath() {
70
+ return path.join(appRoot(), 'dist', 'mcp-server.js');
71
+ }
72
+ function plistPath() {
73
+ return path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
74
+ }
75
+ function logPath() {
76
+ return path.join(daemonRoot(), 'daemon.log');
77
+ }
78
+ /**
79
+ * 定位真实 node 可执行文件。MCP server 经 npx 启动时 execPath 就是 node;
80
+ * 万一是在 Electron 里跑(异常场景),回退常见安装路径。
81
+ */
82
+ function findNode() {
83
+ const exec = process.execPath;
84
+ const base = path.basename(exec).toLowerCase();
85
+ if (base === 'node' || base === 'node.exe')
86
+ return exec;
87
+ const candidates = process.platform === 'win32'
88
+ ? ['C:\\Program Files\\nodejs\\node.exe']
89
+ : ['/usr/local/bin/node', '/opt/homebrew/bin/node', '/usr/bin/node'];
90
+ for (const c of candidates) {
91
+ if (fs.existsSync(c))
92
+ return c;
93
+ }
94
+ return exec; // 死马当活马:Electron 也能靠 ELECTRON_RUN_AS_NODE 跑 node 脚本
95
+ }
96
+ /** 运行中的这份包的根目录(dist 的上一级) */
97
+ function currentPkgRoot() {
98
+ // __dirname 编译后是 <pkgRoot>/dist
99
+ return path.join(__dirname, '..');
100
+ }
101
+ /**
102
+ * 把当前包(dist + package.json + 依赖)拷贝到守护目录。
103
+ * 依赖布局两种:npm/npx 安装时依赖在 pkgRoot 的父级 node_modules;
104
+ * 源码 / 扩展目录时依赖在 pkgRoot/node_modules。
105
+ */
106
+ function copySelf() {
107
+ const pkgRoot = currentPkgRoot();
108
+ const app = appRoot();
109
+ fs.rmSync(app, { recursive: true, force: true });
110
+ fs.mkdirSync(app, { recursive: true });
111
+ for (const item of ['dist', 'package.json']) {
112
+ const src = path.join(pkgRoot, item);
113
+ if (!fs.existsSync(src))
114
+ throw new Error(`当前包缺少 ${item}(${src}),无法安装守护`);
115
+ fs.cpSync(src, path.join(app, item), { recursive: true });
116
+ }
117
+ const ownDeps = path.join(pkgRoot, 'node_modules');
118
+ const parentDeps = path.resolve(pkgRoot, '..');
119
+ let depsSrc = null;
120
+ if (fs.existsSync(ownDeps)) {
121
+ depsSrc = ownDeps;
122
+ }
123
+ else if (path.basename(parentDeps) === 'node_modules') {
124
+ depsSrc = parentDeps;
125
+ }
126
+ if (!depsSrc)
127
+ throw new Error('找不到依赖 node_modules,无法安装守护');
128
+ fs.cpSync(depsSrc, path.join(app, 'node_modules'), {
129
+ recursive: true,
130
+ dereference: true,
131
+ // npm 布局下父级 node_modules 里含 cursor-feedback 自身,跳过避免套娃拷贝
132
+ filter: (src) => !src.includes(`${path.sep}cursor-feedback${path.sep}`) &&
133
+ !src.endsWith(`${path.sep}cursor-feedback`),
134
+ });
135
+ dlog(`包已拷贝到 ${app}`);
136
+ }
137
+ /* ------------------------------ macOS (launchd) ------------------------------ */
138
+ function buildPlist(node) {
139
+ const isElectron = !/node(\.exe)?$/i.test(path.basename(node));
140
+ const envBlock = isElectron
141
+ ? ' <key>EnvironmentVariables</key>\n <dict>\n <key>ELECTRON_RUN_AS_NODE</key>\n <string>1</string>\n </dict>\n'
142
+ : '';
143
+ return `<?xml version="1.0" encoding="UTF-8"?>
144
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
145
+ <plist version="1.0">
146
+ <dict>
147
+ <key>Label</key>
148
+ <string>${LABEL}</string>
149
+ <key>ProgramArguments</key>
150
+ <array>
151
+ <string>${node}</string>
152
+ <string>${entryPath()}</string>
153
+ <string>--daemon</string>
154
+ </array>
155
+ ${envBlock} <key>RunAtLoad</key>
156
+ <true/>
157
+ <key>KeepAlive</key>
158
+ <dict>
159
+ <key>SuccessfulExit</key>
160
+ <false/>
161
+ </dict>
162
+ <key>StandardOutPath</key>
163
+ <string>${logPath()}</string>
164
+ <key>StandardErrorPath</key>
165
+ <string>${logPath()}</string>
166
+ </dict>
167
+ </plist>
168
+ `;
169
+ }
170
+ function launchctl(args) {
171
+ try {
172
+ (0, child_process_1.execFileSync)('launchctl', args, { stdio: 'ignore' });
173
+ }
174
+ catch {
175
+ // bootout 不存在的服务等场景会报错,可忽略
176
+ }
177
+ }
178
+ function installMac() {
179
+ const node = findNode();
180
+ const plist = plistPath();
181
+ fs.mkdirSync(path.dirname(plist), { recursive: true });
182
+ fs.writeFileSync(plist, buildPlist(node));
183
+ const uid = process.getuid ? process.getuid() : 501;
184
+ // 先卸旧再装新(重装 / 升级场景);bootstrap 失败回退老接口 load
185
+ launchctl(['bootout', `gui/${uid}/${LABEL}`]);
186
+ try {
187
+ (0, child_process_1.execFileSync)('launchctl', ['bootstrap', `gui/${uid}`, plist], { stdio: 'ignore' });
188
+ }
189
+ catch {
190
+ launchctl(['load', '-w', plist]);
191
+ }
192
+ dlog('launchd 守护已注册并启动');
193
+ }
194
+ function uninstallMac() {
195
+ const uid = process.getuid ? process.getuid() : 501;
196
+ launchctl(['bootout', `gui/${uid}/${LABEL}`]);
197
+ fs.rmSync(plistPath(), { force: true });
198
+ dlog('launchd 守护已卸载');
199
+ }
200
+ /* ------------------------------ Windows (schtasks) ------------------------------ */
201
+ function vbsPath() {
202
+ return path.join(daemonRoot(), 'daemon-launch.vbs');
203
+ }
204
+ function installWin() {
205
+ const node = findNode();
206
+ // vbs 包装:schtasks 直接跑控制台程序会闪黑窗,wscript + Run(...,0) 完全隐藏
207
+ const vbs = `CreateObject("Wscript.Shell").Run """${node}"" ""${entryPath()}"" --daemon", 0, False\r\n`;
208
+ fs.mkdirSync(daemonRoot(), { recursive: true });
209
+ fs.writeFileSync(vbsPath(), vbs);
210
+ try {
211
+ (0, child_process_1.execFileSync)('schtasks', ['/Delete', '/TN', WIN_TASK_NAME, '/F'], { stdio: 'ignore' });
212
+ }
213
+ catch {
214
+ // 任务不存在
215
+ }
216
+ (0, child_process_1.execFileSync)('schtasks', ['/Create', '/TN', WIN_TASK_NAME, '/SC', 'ONLOGON', '/TR', `wscript.exe "${vbsPath()}"`, '/RL', 'LIMITED', '/F'], { stdio: 'ignore' });
217
+ // 立即拉起一次(不等下次登录)
218
+ try {
219
+ (0, child_process_1.execFileSync)('schtasks', ['/Run', '/TN', WIN_TASK_NAME], { stdio: 'ignore' });
220
+ }
221
+ catch {
222
+ (0, child_process_1.spawn)('wscript.exe', [vbsPath()], { stdio: 'ignore', detached: true }).unref();
223
+ }
224
+ dlog('Windows 计划任务已注册并启动');
225
+ }
226
+ function uninstallWin() {
227
+ try {
228
+ (0, child_process_1.execFileSync)('schtasks', ['/Delete', '/TN', WIN_TASK_NAME, '/F'], { stdio: 'ignore' });
229
+ }
230
+ catch {
231
+ // 任务不存在
232
+ }
233
+ fs.rmSync(vbsPath(), { force: true });
234
+ dlog('Windows 计划任务已卸载');
235
+ }
236
+ /* ------------------------------ 公共入口 ------------------------------ */
237
+ function daemonSupported() {
238
+ return process.platform === 'darwin' || process.platform === 'win32';
239
+ }
240
+ function installDaemon() {
241
+ if (!daemonSupported())
242
+ throw new Error(`平台 ${process.platform} 暂不支持常驻守护`);
243
+ copySelf();
244
+ if (process.platform === 'darwin')
245
+ installMac();
246
+ else
247
+ installWin();
248
+ return daemonStatus();
249
+ }
250
+ function uninstallDaemon() {
251
+ if (process.platform === 'darwin')
252
+ uninstallMac();
253
+ else if (process.platform === 'win32')
254
+ uninstallWin();
255
+ fs.rmSync(appRoot(), { recursive: true, force: true });
256
+ return daemonStatus();
257
+ }
258
+ function daemonStatus() {
259
+ const registered = process.platform === 'darwin'
260
+ ? fs.existsSync(plistPath())
261
+ : process.platform === 'win32'
262
+ ? winTaskExists()
263
+ : false;
264
+ const entry = entryPath();
265
+ const installed = registered && fs.existsSync(entry);
266
+ let installedVersion = null;
267
+ if (installed) {
268
+ try {
269
+ const pkg = JSON.parse(fs.readFileSync(path.join(appRoot(), 'package.json'), 'utf-8'));
270
+ installedVersion = typeof pkg.version === 'string' ? pkg.version : null;
271
+ }
272
+ catch {
273
+ // 读不到版本不影响状态
274
+ }
275
+ }
276
+ return {
277
+ supported: daemonSupported(),
278
+ installed,
279
+ installedVersion,
280
+ entryPath: installed ? entry : null,
281
+ };
282
+ }
283
+ function winTaskExists() {
284
+ try {
285
+ (0, child_process_1.execFileSync)('schtasks', ['/Query', '/TN', WIN_TASK_NAME], { stdio: 'ignore' });
286
+ return true;
287
+ }
288
+ catch {
289
+ return false;
290
+ }
291
+ }
292
+ //# sourceMappingURL=daemon-install.js.map
package/dist/extension.js CHANGED
@@ -297,6 +297,12 @@ class FeedbackViewProvider {
297
297
  case 'toggleFeishuQueue':
298
298
  await this._handleToggleFeishuQueue(!!data.payload?.enabled);
299
299
  break;
300
+ case 'requestDaemonStatus':
301
+ await this._handleRequestDaemonStatus();
302
+ break;
303
+ case 'toggleDaemon':
304
+ await this._handleToggleDaemon(!!data.payload?.enabled);
305
+ break;
300
306
  case 'feishuRegisterStart':
301
307
  await this._handleFeishuRegisterStart();
302
308
  break;
@@ -1332,6 +1338,60 @@ class FeedbackViewProvider {
1332
1338
  this._broadcastFeishuConfig();
1333
1339
  this._postFeishuState();
1334
1340
  }
1341
+ /** 找一个活跃的 server 端口(常驻服务安装/状态查询走任一进程均可,磁盘配置全局共享) */
1342
+ _anyServerPort() {
1343
+ if (this._activePort)
1344
+ return this._activePort;
1345
+ const ports = this._debugInfo.connectedPorts || [];
1346
+ return ports.length > 0 ? ports[0] : null;
1347
+ }
1348
+ /** 查询常驻服务状态并回显到设置面板 */
1349
+ async _handleRequestDaemonStatus() {
1350
+ const port = this._anyServerPort();
1351
+ if (!port) {
1352
+ this._view?.webview.postMessage({
1353
+ type: 'daemonState',
1354
+ payload: { supported: false, installed: false, error: 'no server' },
1355
+ });
1356
+ return;
1357
+ }
1358
+ try {
1359
+ const raw = await this._httpGet(`http://127.0.0.1:${port}/api/daemon/status`);
1360
+ this._view?.webview.postMessage({ type: 'daemonState', payload: JSON.parse(raw) });
1361
+ }
1362
+ catch (e) {
1363
+ this._view?.webview.postMessage({
1364
+ type: 'daemonState',
1365
+ payload: { supported: true, installed: false, error: String(e) },
1366
+ });
1367
+ }
1368
+ }
1369
+ /** 安装/卸载常驻服务(server 端执行:拷贝自身 + 注册开机自启),完成后回显状态 */
1370
+ async _handleToggleDaemon(enabled) {
1371
+ const port = this._anyServerPort();
1372
+ if (!port) {
1373
+ this._view?.webview.postMessage({
1374
+ type: 'daemonState',
1375
+ payload: { supported: true, installed: !enabled, error: 'no server' },
1376
+ });
1377
+ return;
1378
+ }
1379
+ try {
1380
+ // 安装要拷贝整棵依赖树,超时放宽到 60s
1381
+ const raw = await this._httpPost(`http://127.0.0.1:${port}/api/daemon/${enabled ? 'install' : 'uninstall'}`, '{}', 60000);
1382
+ const parsed = JSON.parse(raw);
1383
+ this._view?.webview.postMessage({
1384
+ type: 'daemonState',
1385
+ payload: parsed.error ? { supported: true, installed: !enabled, error: parsed.error } : parsed,
1386
+ });
1387
+ }
1388
+ catch (e) {
1389
+ this._view?.webview.postMessage({
1390
+ type: 'daemonState',
1391
+ payload: { supported: true, installed: !enabled, error: String(e) },
1392
+ });
1393
+ }
1394
+ }
1335
1395
  /**
1336
1396
  * HTTP GET 请求
1337
1397
  */
package/dist/i18n/en.json CHANGED
@@ -74,6 +74,12 @@
74
74
  "feishuAckDesc": "After you reply in Feishu, the bot adds a Get emoji as acknowledgement.",
75
75
  "queueWhenBusyLabel": "Queue messages while AI is busy",
76
76
  "queueWhenBusyDesc": "While the AI is busy, messages from the panel and Feishu are queued and auto-delivered in order on its next feedback round, marked as appended during the task.",
77
+ "daemonLabel": "Background service (works without IDE)",
78
+ "daemonDesc": "Auto-start a standalone Feishu bridge at login: receive messages and launch CLI sessions with /new even when Cursor is closed. Keeps the machine awake while plugged in.",
79
+ "daemonInstalled": "Installed v{version}",
80
+ "daemonNotSupported": "Not supported on this platform",
81
+ "daemonFailed": "Operation failed: {error}",
82
+ "daemonWorking": "Working…",
77
83
  "queueSend": "Queue Message",
78
84
  "queuedTitle": "Queued messages",
79
85
  "queueModeHint": "AI is busy — messages sent now are queued and delivered when it next asks for feedback",
@@ -165,6 +165,12 @@ function getDefaultMessages() {
165
165
  feishuAckDesc: "After you reply in Feishu, the bot adds a Get emoji as acknowledgement.",
166
166
  queueWhenBusyLabel: "Queue messages while AI is busy",
167
167
  queueWhenBusyDesc: "While the AI is busy, messages from the panel and Feishu are queued and auto-delivered in order on its next feedback round, marked as appended during the task.",
168
+ daemonLabel: "Background service (works without IDE)",
169
+ daemonDesc: "Auto-start a standalone Feishu bridge at login: receive messages and launch CLI sessions with /new even when Cursor is closed. Keeps the machine awake while plugged in.",
170
+ daemonInstalled: "Installed v{version}",
171
+ daemonNotSupported: "Not supported on this platform",
172
+ daemonFailed: "Operation failed: {error}",
173
+ daemonWorking: "Working…",
168
174
  queueSend: "Queue Message",
169
175
  queuedTitle: "Queued messages",
170
176
  queueModeHint: "AI is busy — messages sent now are queued and delivered when it next asks for feedback",
@@ -74,6 +74,12 @@
74
74
  "feishuAckDesc": "你在飞书回复后,机器人加个 Get 表情表示「已收到」。",
75
75
  "queueWhenBusyLabel": "忙时消息排队",
76
76
  "queueWhenBusyDesc": "AI 正忙时,面板与飞书发送的消息先排队,等它下一轮询问时按序自动送达,并附「任务期间追加」提示。",
77
+ "daemonLabel": "常驻服务(IDE 关闭也可用)",
78
+ "daemonDesc": "开机自启一个独立的飞书桥接进程:Cursor 不开也能收发飞书消息、用 /new 从手机拉起 CLI 会话;接电源时自动防睡眠(锁屏不受影响)。",
79
+ "daemonInstalled": "已安装 v{version}",
80
+ "daemonNotSupported": "当前平台暂不支持",
81
+ "daemonFailed": "操作失败:{error}",
82
+ "daemonWorking": "处理中…",
77
83
  "queueSend": "排队发送",
78
84
  "queuedTitle": "排队中的消息",
79
85
  "queueModeHint": "AI 正忙,现在发送的消息会排队,等它下一轮询问时自动送达",
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeepAwake = void 0;
4
+ /**
5
+ * 防睡眠模块:常驻守护进程启用,保证「下班锁屏后手机随时能唤起」。
6
+ *
7
+ * 原则:只在接电源时阻止系统睡眠,绝不偷耗电池;锁屏/关屏不受影响(本来就不断网)。
8
+ * - macOS:拉一个 `caffeinate -s` 子进程。-s 语义本身就是「仅接电时阻止系统睡眠」,
9
+ * 电池供电时自动不生效,无需自己轮询电源状态;进程退出断言自动释放。
10
+ * - Windows:拉一个 PowerShell 子进程调 Win32 SetThreadExecutionState(
11
+ * ES_CONTINUOUS | ES_SYSTEM_REQUIRED)。API 本身不区分电源,故脚本内每 30s 轮询
12
+ * 电池状态,接电才持有断言、拔电立即释放。无需管理员、不改用户电源计划,
13
+ * 进程退出断言自动失效。
14
+ */
15
+ const child_process_1 = require("child_process");
16
+ function klog(message) {
17
+ console.error(`[${new Date().toISOString()}] [keep-awake] ${message}`);
18
+ }
19
+ /**
20
+ * Windows 常驻脚本:持有/释放电源断言 + 电源状态轮询。
21
+ * ES_CONTINUOUS=0x80000000, ES_SYSTEM_REQUIRED=0x00000001。
22
+ * PowerLineStatus: Online=接电(台式机无电池也是 Online)。
23
+ */
24
+ const WIN_KEEP_AWAKE_PS = `
25
+ Add-Type -Name P -Namespace W -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags);'
26
+ Add-Type -AssemblyName System.Windows.Forms
27
+ $held = $false
28
+ while ($true) {
29
+ $ac = [System.Windows.Forms.SystemInformation]::PowerStatus.PowerLineStatus -eq 'Online'
30
+ if ($ac -and -not $held) { [W.P]::SetThreadExecutionState(0x80000001) | Out-Null; $held = $true }
31
+ elseif (-not $ac -and $held) { [W.P]::SetThreadExecutionState(0x80000000) | Out-Null; $held = $false }
32
+ Start-Sleep -Seconds 30
33
+ }
34
+ `;
35
+ class KeepAwake {
36
+ constructor() {
37
+ this.child = null;
38
+ this.stopped = false;
39
+ }
40
+ isActive() {
41
+ return this.child !== null;
42
+ }
43
+ /** 启动防睡眠(幂等)。不支持的平台(Linux 等)静默跳过。 */
44
+ start() {
45
+ if (this.child)
46
+ return;
47
+ this.stopped = false;
48
+ let bin;
49
+ let args;
50
+ if (process.platform === 'darwin') {
51
+ bin = '/usr/bin/caffeinate';
52
+ args = ['-s'];
53
+ }
54
+ else if (process.platform === 'win32') {
55
+ bin = 'powershell.exe';
56
+ args = ['-NoProfile', '-NonInteractive', '-WindowStyle', 'Hidden', '-Command', WIN_KEEP_AWAKE_PS];
57
+ }
58
+ else {
59
+ klog(`平台 ${process.platform} 暂不支持防睡眠,跳过`);
60
+ return;
61
+ }
62
+ let child;
63
+ try {
64
+ child = (0, child_process_1.spawn)(bin, args, { stdio: 'ignore' });
65
+ }
66
+ catch (e) {
67
+ klog('启动防睡眠子进程失败: ' + e);
68
+ return;
69
+ }
70
+ this.child = child;
71
+ klog(`防睡眠已启动: pid=${child.pid}(仅接电源时阻止系统睡眠)`);
72
+ child.on('error', (err) => {
73
+ klog('防睡眠子进程出错: ' + err);
74
+ this.child = null;
75
+ });
76
+ child.on('exit', () => {
77
+ this.child = null;
78
+ // 意外退出(如被任务管理器杀掉)时自动重启,保证常驻语义;主动 stop 不重启
79
+ if (!this.stopped) {
80
+ klog('防睡眠子进程意外退出,10s 后重启');
81
+ setTimeout(() => {
82
+ if (!this.stopped)
83
+ this.start();
84
+ }, 10000);
85
+ }
86
+ });
87
+ }
88
+ stop() {
89
+ this.stopped = true;
90
+ if (!this.child)
91
+ return;
92
+ try {
93
+ this.child.kill();
94
+ }
95
+ catch { /* 进程可能已退出 */ }
96
+ this.child = null;
97
+ klog('防睡眠已停止');
98
+ }
99
+ }
100
+ exports.KeepAwake = KeepAwake;
101
+ //# sourceMappingURL=keep-awake.js.map