sessix-server 0.1.4 → 0.2.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.
Files changed (35) hide show
  1. package/dist/approval/ApprovalProxy.d.ts +0 -10
  2. package/dist/approval/ApprovalProxy.d.ts.map +1 -1
  3. package/dist/approval/ApprovalProxy.js +6 -44
  4. package/dist/approval/ApprovalProxy.js.map +1 -1
  5. package/dist/hooks/HookInstaller.d.ts +7 -11
  6. package/dist/hooks/HookInstaller.d.ts.map +1 -1
  7. package/dist/hooks/HookInstaller.js +49 -96
  8. package/dist/hooks/HookInstaller.js.map +1 -1
  9. package/dist/index.js +326 -20
  10. package/dist/notification/ExpoNotificationChannel.d.ts.map +1 -1
  11. package/dist/notification/ExpoNotificationChannel.js +1 -9
  12. package/dist/notification/ExpoNotificationChannel.js.map +1 -1
  13. package/dist/notification/NotificationService.d.ts +1 -1
  14. package/dist/notification/NotificationService.d.ts.map +1 -1
  15. package/dist/notification/NotificationService.js +5 -13
  16. package/dist/notification/NotificationService.js.map +1 -1
  17. package/dist/providers/ExecutionProvider.d.ts +2 -8
  18. package/dist/providers/ExecutionProvider.d.ts.map +1 -1
  19. package/dist/providers/ProcessProvider.d.ts +2 -2
  20. package/dist/providers/ProcessProvider.d.ts.map +1 -1
  21. package/dist/providers/ProcessProvider.js +15 -49
  22. package/dist/providers/ProcessProvider.js.map +1 -1
  23. package/dist/server.d.ts +6 -0
  24. package/dist/server.d.ts.map +1 -1
  25. package/dist/server.js +306 -18
  26. package/dist/server.js.map +1 -1
  27. package/dist/session/ProjectReader.js +1 -1
  28. package/dist/session/ProjectReader.js.map +1 -1
  29. package/dist/session/SessionManager.d.ts +3 -7
  30. package/dist/session/SessionManager.d.ts.map +1 -1
  31. package/dist/session/SessionManager.js +5 -45
  32. package/dist/session/SessionManager.js.map +1 -1
  33. package/dist/ws/WsBridge.js +4 -4
  34. package/dist/ws/WsBridge.js.map +1 -1
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -43,6 +43,8 @@ var zh = {
43
43
  autoDiscoveryOn: " \u{1F4A1} \u81EA\u52A8\u53D1\u73B0\u5DF2\u542F\u7528\uFF0C\u540C\u7F51\u6BB5\u624B\u673A\u53EF\u81EA\u52A8\u8FDE\u63A5",
44
44
  autoDiscoveryHint: " \u5982\u5728\u516C\u5171\u7F51\u7EDC\uFF0C\u5EFA\u8BAE\u5173\u95ED: SESSIX_AUTO_CONNECT=false npx sessix-server",
45
45
  autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
46
+ pairingOpen: " \u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u5F00\u542F\uFF085 \u5206\u949F\u5185\u6709\u6548\uFF09\u2014 \u6309 p \u91CD\u65B0\u5F00\u542F",
47
+ pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
46
48
  receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
47
49
  goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
48
50
  shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
@@ -69,7 +71,8 @@ var zh = {
69
71
  restarting: "\u91CD\u65B0\u542F\u52A8 {{label}}...",
70
72
  activityPushEnabled: "ActivityKit Push \u5DF2\u542F\u7528",
71
73
  activityPushFailed: "ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:",
72
- activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09"
74
+ activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09",
75
+ noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B"
73
76
  },
74
77
  ws: {
75
78
  started: "WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
@@ -144,6 +147,8 @@ var en = {
144
147
  autoDiscoveryOn: " Auto-discovery enabled, phones on the same network can connect automatically",
145
148
  autoDiscoveryHint: " On public networks, disable with: SESSIX_AUTO_CONNECT=false npx sessix-server",
146
149
  autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
150
+ pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
151
+ pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
147
152
  receivedSignal: "Received {{signal}}, graceful shutdown...",
148
153
  goodbye: "All services closed, goodbye!",
149
154
  shutdownError: "Shutdown error:",
@@ -170,7 +175,8 @@ var en = {
170
175
  restarting: "Restarting {{label}}...",
171
176
  activityPushEnabled: "ActivityKit Push enabled",
172
177
  activityPushFailed: "ActivityKit Push init failed:",
173
- activityPushContinue: "Continuing startup (Live Activity background push unavailable)"
178
+ activityPushContinue: "Continuing startup (Live Activity background push unavailable)",
179
+ noActiveLoginProcess: "No active login process"
174
180
  },
175
181
  ws: {
176
182
  started: "WebSocket server started on port {{port}}",
@@ -283,11 +289,14 @@ var import_node_child_process2 = require("child_process");
283
289
  var import_node_util = require("util");
284
290
 
285
291
  // src/providers/ProcessProvider.ts
286
- var import_child_process = require("child_process");
292
+ var import_child_process2 = require("child_process");
287
293
  var import_readline = require("readline");
288
294
  var import_events = require("events");
289
295
  var import_node_os = require("os");
290
296
  var import_uuid = require("uuid");
297
+
298
+ // src/utils/claudePath.ts
299
+ var import_child_process = require("child_process");
291
300
  function findClaudePath() {
292
301
  try {
293
302
  return (0, import_child_process.execSync)("which claude", { encoding: "utf-8" }).trim();
@@ -307,6 +316,8 @@ function findClaudePath() {
307
316
  return "claude";
308
317
  }
309
318
  }
319
+
320
+ // src/providers/ProcessProvider.ts
310
321
  var CLAUDE_PATH = findClaudePath();
311
322
  var ProcessProvider = class {
312
323
  /** 活跃会话映射表:sessionId -> { session, process } */
@@ -499,7 +510,7 @@ var ProcessProvider = class {
499
510
  }
500
511
  const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
501
512
  delete env.CLAUDECODE;
502
- const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
513
+ const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, args, {
503
514
  cwd: projectPath,
504
515
  env,
505
516
  stdio: ["pipe", "pipe", "pipe"]
@@ -706,7 +717,7 @@ ${context}`;
706
717
  return new Promise((resolve, reject) => {
707
718
  const env = { ...process.env };
708
719
  delete env.CLAUDECODE;
709
- const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
720
+ const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
710
721
  cwd: (0, import_node_os.homedir)(),
711
722
  env,
712
723
  stdio: ["pipe", "pipe", "pipe"]
@@ -1617,6 +1628,7 @@ var ApprovalProxy = class _ApprovalProxy {
1617
1628
  alwaysAllowedTools = /* @__PURE__ */ new Set();
1618
1629
  /** 获取状态信息的回调(由外部注入) */
1619
1630
  statusInfoProvider = null;
1631
+ pairingManager = null;
1620
1632
  constructor(options) {
1621
1633
  this.token = options.token;
1622
1634
  this.port = options.port;
@@ -1651,6 +1663,10 @@ var ApprovalProxy = class _ApprovalProxy {
1651
1663
  setStatusInfoProvider(provider) {
1652
1664
  this.statusInfoProvider = provider;
1653
1665
  }
1666
+ /** 设置配对管理器 */
1667
+ setPairingManager(manager) {
1668
+ this.pairingManager = manager;
1669
+ }
1654
1670
  /** 设置会话的 YOLO 模式(服务端拦截,即使手机断连也生效) */
1655
1671
  setYoloMode(sessionId, enabled) {
1656
1672
  this.yoloSessions.set(sessionId, enabled);
@@ -1811,6 +1827,8 @@ var ApprovalProxy = class _ApprovalProxy {
1811
1827
  const pathname = url.pathname;
1812
1828
  if (req.method === "POST" && pathname === "/hook/approval") {
1813
1829
  this.handleApprovalHook(req, res);
1830
+ } else if (req.method === "POST" && pathname === "/pair") {
1831
+ this.handlePair(req, res);
1814
1832
  } else if (req.method === "GET" && pathname === "/health") {
1815
1833
  this.handleHealth(req, res);
1816
1834
  } else if (req.method === "GET" && pathname === "/token") {
@@ -1881,6 +1899,23 @@ var ApprovalProxy = class _ApprovalProxy {
1881
1899
  activeSessions: info.activeSessions
1882
1900
  });
1883
1901
  }
1902
+ /** 配对端点:配对窗口开放时返回 token */
1903
+ handlePair(_req, res) {
1904
+ if (!this.pairingManager) {
1905
+ this.sendJson(res, 503, { error: "pairing_unavailable" });
1906
+ return;
1907
+ }
1908
+ const result = this.pairingManager.tryPair();
1909
+ if (result) {
1910
+ console.log("[ApprovalProxy] Device paired successfully");
1911
+ this.sendJson(res, 200, result);
1912
+ } else {
1913
+ this.sendJson(res, 403, {
1914
+ error: "pairing_closed",
1915
+ message: "Pairing window is closed. Restart server or press p to reopen."
1916
+ });
1917
+ }
1918
+ }
1884
1919
  /** 返回连接 token(仅本机访问) */
1885
1920
  handleToken(req, res) {
1886
1921
  const remoteAddress = req.socket.remoteAddress;
@@ -1951,12 +1986,12 @@ var MdnsService = class {
1951
1986
  wsPort;
1952
1987
  httpPort;
1953
1988
  version;
1954
- token;
1989
+ pairing;
1955
1990
  constructor(options) {
1956
1991
  this.wsPort = options.wsPort;
1957
1992
  this.httpPort = options.httpPort;
1958
1993
  this.version = options.version ?? "0.1.0";
1959
- this.token = options.token ?? "";
1994
+ this.pairing = options.pairing ?? "closed";
1960
1995
  }
1961
1996
  /**
1962
1997
  * 启动 mDNS 广播
@@ -1974,7 +2009,8 @@ var MdnsService = class {
1974
2009
  txt: {
1975
2010
  version: this.version,
1976
2011
  httpPort: String(this.httpPort),
1977
- token: this.token
2012
+ wsPort: String(this.wsPort),
2013
+ pairing: this.pairing
1978
2014
  }
1979
2015
  });
1980
2016
  console.log(`[MdnsService] ${t("mdns.started", { port: this.wsPort })}`);
@@ -1995,6 +2031,29 @@ var MdnsService = class {
1995
2031
  }
1996
2032
  console.log(`[MdnsService] ${t("mdns.closed")}`);
1997
2033
  }
2034
+ /**
2035
+ * 更新配对状态(重新发布 mDNS 服务)
2036
+ */
2037
+ updatePairingState(state) {
2038
+ this.pairing = state;
2039
+ if (!this.bonjour) return;
2040
+ if (this.service) {
2041
+ this.service.stop?.(() => {
2042
+ });
2043
+ this.service = null;
2044
+ }
2045
+ this.service = this.bonjour.publish({
2046
+ name: "Sessix",
2047
+ type: "sessix",
2048
+ port: this.wsPort,
2049
+ txt: {
2050
+ version: this.version,
2051
+ httpPort: String(this.httpPort),
2052
+ wsPort: String(this.wsPort),
2053
+ pairing: state
2054
+ }
2055
+ });
2056
+ }
1998
2057
  };
1999
2058
 
2000
2059
  // src/hooks/HookInstaller.ts
@@ -2240,8 +2299,8 @@ var NotificationService = class {
2240
2299
  if (entry) entry.enabled = enabled;
2241
2300
  }
2242
2301
  /** 注册手机 push token(连接建立时由 WsBridge 调用) */
2243
- addPushToken(token) {
2244
- this.expoChannel?.addToken(token);
2302
+ addPushToken(token, ws) {
2303
+ this.expoChannel?.addToken(token, ws);
2245
2304
  }
2246
2305
  /** 移除手机 push token(断线时或手机主动注销时调用) */
2247
2306
  removePushToken(token) {
@@ -2481,17 +2540,21 @@ var MacNotificationChannel = class {
2481
2540
  var EXPO_PUSH_API = "https://exp.host/--/api/v2/push/send";
2482
2541
  var ExpoNotificationChannel = class {
2483
2542
  tokens = /* @__PURE__ */ new Set();
2543
+ /** push token → WebSocket 连接映射,用于前台抑制 */
2544
+ tokenWsMap = /* @__PURE__ */ new Map();
2484
2545
  /** per-token 通知音效偏好 */
2485
2546
  soundPreferences = /* @__PURE__ */ new Map();
2486
2547
  isAvailable() {
2487
2548
  return this.tokens.size > 0;
2488
2549
  }
2489
- addToken(token) {
2550
+ addToken(token, ws) {
2490
2551
  this.tokens.add(token);
2552
+ if (ws) this.tokenWsMap.set(token, ws);
2491
2553
  console.log(`[ExpoNotificationChannel] ${t("notification.tokenRegistered", { count: this.tokens.size })}`);
2492
2554
  }
2493
2555
  removeToken(token) {
2494
2556
  this.tokens.delete(token);
2557
+ this.tokenWsMap.delete(token);
2495
2558
  this.soundPreferences.delete(token);
2496
2559
  console.log(`[ExpoNotificationChannel] ${t("notification.tokenRemoved", { count: this.tokens.size })}`);
2497
2560
  }
@@ -2504,7 +2567,12 @@ var ExpoNotificationChannel = class {
2504
2567
  }
2505
2568
  async send(payload) {
2506
2569
  if (this.tokens.size === 0) return;
2507
- const messages = Array.from(this.tokens).map((to) => {
2570
+ const offlineTokens = Array.from(this.tokens).filter((token) => {
2571
+ const ws = this.tokenWsMap.get(token);
2572
+ return !ws || ws.readyState !== ws.OPEN;
2573
+ });
2574
+ if (offlineTokens.length === 0) return;
2575
+ const messages = offlineTokens.map((to) => {
2508
2576
  let sound = payload.sound ?? "default";
2509
2577
  const prefs = this.soundPreferences.get(to);
2510
2578
  if (prefs) {
@@ -2524,7 +2592,7 @@ var ExpoNotificationChannel = class {
2524
2592
  };
2525
2593
  });
2526
2594
  try {
2527
- console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")}`, Array.from(this.tokens));
2595
+ console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")} (${offlineTokens.length}/${this.tokens.size} devices)`, offlineTokens);
2528
2596
  const res = await fetch(EXPO_PUSH_API, {
2529
2597
  method: "POST",
2530
2598
  headers: { "Content-Type": "application/json", Accept: "application/json" },
@@ -3027,6 +3095,180 @@ async function countJsonlFilesWithMtime(dirPath) {
3027
3095
  }
3028
3096
  }
3029
3097
 
3098
+ // src/pairing/PairingManager.ts
3099
+ var PairingManager = class {
3100
+ _state = "closed";
3101
+ timer = null;
3102
+ deadline = 0;
3103
+ token;
3104
+ serverName;
3105
+ version;
3106
+ defaultDuration;
3107
+ onStateChange;
3108
+ constructor(opts) {
3109
+ this.token = opts.token;
3110
+ this.serverName = opts.serverName;
3111
+ this.version = opts.version;
3112
+ this.defaultDuration = opts.defaultDuration ?? 3e5;
3113
+ this.onStateChange = opts.onStateChange;
3114
+ }
3115
+ get state() {
3116
+ return this._state;
3117
+ }
3118
+ open(duration) {
3119
+ const ms = duration ?? this.defaultDuration;
3120
+ if (this.timer) clearTimeout(this.timer);
3121
+ this._state = "open";
3122
+ this.deadline = Date.now() + ms;
3123
+ this.timer = setTimeout(() => this.close(), ms);
3124
+ this.onStateChange("open");
3125
+ }
3126
+ close() {
3127
+ if (this.timer) {
3128
+ clearTimeout(this.timer);
3129
+ this.timer = null;
3130
+ }
3131
+ if (this._state === "closed") return;
3132
+ this._state = "closed";
3133
+ this.deadline = 0;
3134
+ this.onStateChange("closed");
3135
+ }
3136
+ tryPair() {
3137
+ if (this._state !== "open") return null;
3138
+ const result = { token: this.token, serverName: this.serverName, version: this.version };
3139
+ this.close();
3140
+ return result;
3141
+ }
3142
+ getRemainingSeconds() {
3143
+ if (this._state !== "open") return 0;
3144
+ return Math.max(0, Math.ceil((this.deadline - Date.now()) / 1e3));
3145
+ }
3146
+ destroy() {
3147
+ if (this.timer) {
3148
+ clearTimeout(this.timer);
3149
+ this.timer = null;
3150
+ }
3151
+ }
3152
+ };
3153
+
3154
+ // src/auth/AuthManager.ts
3155
+ var import_child_process3 = require("child_process");
3156
+ var import_child_process4 = require("child_process");
3157
+ var import_util = require("util");
3158
+ var import_events2 = require("events");
3159
+ var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3160
+ var CLAUDE_PATH2 = findClaudePath();
3161
+ var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3162
+ var AuthManager = class extends import_events2.EventEmitter {
3163
+ loginProcess = null;
3164
+ loginTimeout = null;
3165
+ urlSent = false;
3166
+ /** 检查当前 Claude CLI 认证状态(异步,不阻塞事件循环) */
3167
+ async checkAuth() {
3168
+ try {
3169
+ const { stdout } = await execFileAsync(CLAUDE_PATH2, ["auth", "status"], {
3170
+ timeout: 1e4
3171
+ });
3172
+ const parsed = JSON.parse(stdout.trim());
3173
+ return {
3174
+ loggedIn: !!parsed.loggedIn,
3175
+ email: parsed.email,
3176
+ authMethod: parsed.authMethod
3177
+ };
3178
+ } catch {
3179
+ return { loggedIn: false };
3180
+ }
3181
+ }
3182
+ /** 启动登录流程,捕获 URL 并通过事件推送 */
3183
+ async startLogin() {
3184
+ if (this.loginProcess) {
3185
+ this.loginProcess.kill();
3186
+ this.loginProcess = null;
3187
+ }
3188
+ this.clearLoginTimeout();
3189
+ this.urlSent = false;
3190
+ const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3191
+ env: { ...process.env, BROWSER: "echo" },
3192
+ stdio: ["pipe", "pipe", "pipe"]
3193
+ });
3194
+ this.loginProcess = proc;
3195
+ const handleOutput = (data) => {
3196
+ const text = data.toString();
3197
+ console.log(`[AuthManager] login output: ${text.trim()}`);
3198
+ if (!this.urlSent) {
3199
+ const url = this.extractUrl(text);
3200
+ if (url) {
3201
+ this.urlSent = true;
3202
+ console.log(`[AuthManager] \u6355\u83B7\u5230\u767B\u5F55 URL: ${url}`);
3203
+ this.emit("login_url", url);
3204
+ }
3205
+ }
3206
+ };
3207
+ proc.stdout?.on("data", handleOutput);
3208
+ proc.stderr?.on("data", handleOutput);
3209
+ proc.on("exit", (code) => {
3210
+ console.log(`[AuthManager] login process exited with code ${code}`);
3211
+ this.loginProcess = null;
3212
+ this.clearLoginTimeout();
3213
+ this.checkAuth().then((status) => {
3214
+ if (status.loggedIn) {
3215
+ this.emit("login_result", { success: true });
3216
+ } else if (code !== 0) {
3217
+ this.emit("login_result", { success: false, error: `Exit code: ${code}` });
3218
+ }
3219
+ });
3220
+ });
3221
+ proc.on("error", (err) => {
3222
+ console.error(`[AuthManager] login process error:`, err.message);
3223
+ this.loginProcess = null;
3224
+ this.clearLoginTimeout();
3225
+ this.emit("login_result", { success: false, error: err.message });
3226
+ });
3227
+ this.loginTimeout = setTimeout(() => {
3228
+ if (this.loginProcess) {
3229
+ console.warn("[AuthManager] login process timed out, killing");
3230
+ this.loginProcess.kill();
3231
+ this.loginProcess = null;
3232
+ this.emit("login_result", { success: false, error: "Login timed out" });
3233
+ }
3234
+ }, LOGIN_TIMEOUT_MS);
3235
+ }
3236
+ /** 提交授权码到登录进程的 stdin */
3237
+ submitCode(code) {
3238
+ if (!this.loginProcess?.stdin?.writable) {
3239
+ console.warn("[AuthManager] No active login process");
3240
+ return false;
3241
+ }
3242
+ console.log(`[AuthManager] \u63D0\u4EA4\u6388\u6743\u7801`);
3243
+ this.loginProcess.stdin.write(code + "\n");
3244
+ return true;
3245
+ }
3246
+ /** 是否有登录进程在运行 */
3247
+ get isLoginInProgress() {
3248
+ return this.loginProcess !== null;
3249
+ }
3250
+ /** 清理资源 */
3251
+ destroy() {
3252
+ this.clearLoginTimeout();
3253
+ if (this.loginProcess) {
3254
+ this.loginProcess.kill();
3255
+ this.loginProcess = null;
3256
+ }
3257
+ this.removeAllListeners();
3258
+ }
3259
+ clearLoginTimeout() {
3260
+ if (this.loginTimeout) {
3261
+ clearTimeout(this.loginTimeout);
3262
+ this.loginTimeout = null;
3263
+ }
3264
+ }
3265
+ /** 从文本中提取 URL */
3266
+ extractUrl(text) {
3267
+ const match = text.match(/https?:\/\/[^\s"'<>]+/);
3268
+ return match ? match[0] : null;
3269
+ }
3270
+ };
3271
+
3030
3272
  // src/server.ts
3031
3273
  var import_promises5 = require("fs/promises");
3032
3274
  var WS_PORT = 3745;
@@ -3105,9 +3347,32 @@ async function start(opts = {}) {
3105
3347
  HTTP_PORT,
3106
3348
  () => ApprovalProxy.create({ port: HTTP_PORT, token })
3107
3349
  );
3350
+ let mdnsService = null;
3351
+ const pairingManager = new PairingManager({
3352
+ token,
3353
+ serverName: (0, import_node_os4.hostname)(),
3354
+ version: "0.2.0",
3355
+ onStateChange: (state) => mdnsService?.updatePairingState(state)
3356
+ });
3357
+ approvalProxy.setPairingManager(pairingManager);
3358
+ if (opts.enablePairing !== false) {
3359
+ pairingManager.open();
3360
+ }
3361
+ const authManager = new AuthManager();
3362
+ authManager.on("login_url", (url) => {
3363
+ wsBridge.broadcast({ type: "auth_login_url", url });
3364
+ });
3365
+ authManager.on("login_result", (result) => {
3366
+ wsBridge.broadcast({ type: "auth_login_result", success: result.success, error: result.error });
3367
+ if (result.success) {
3368
+ authManager.checkAuth().then((status) => {
3369
+ wsBridge.broadcast({ type: "auth_status", loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod });
3370
+ });
3371
+ }
3372
+ });
3108
3373
  const unreadSessionIds = /* @__PURE__ */ new Set();
3109
3374
  notificationService.setGlobalPendingCountProvider(
3110
- () => approvalProxy.getPendingCount() + unreadSessionIds.size
3375
+ () => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
3111
3376
  );
3112
3377
  const broadcastUnreadSessions = () => {
3113
3378
  wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
@@ -3322,7 +3587,7 @@ async function start(opts = {}) {
3322
3587
  break;
3323
3588
  }
3324
3589
  case "register_push_token": {
3325
- notificationService.addPushToken(event.token);
3590
+ notificationService.addPushToken(event.token, ws);
3326
3591
  break;
3327
3592
  }
3328
3593
  case "unregister_push_token": {
@@ -3361,6 +3626,22 @@ async function start(opts = {}) {
3361
3626
  approvalProxy.addToClaudeSettings(event.projectPath, event.toolName);
3362
3627
  break;
3363
3628
  }
3629
+ case "check_auth": {
3630
+ const status = await authManager.checkAuth();
3631
+ wsBridge.send(ws, { type: "auth_status", loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod });
3632
+ break;
3633
+ }
3634
+ case "start_auth_login": {
3635
+ await authManager.startLogin();
3636
+ break;
3637
+ }
3638
+ case "submit_auth_code": {
3639
+ const submitted = authManager.submitCode(event.code);
3640
+ if (!submitted) {
3641
+ wsBridge.send(ws, { type: "auth_login_result", success: false, error: t("server.noActiveLoginProcess") });
3642
+ }
3643
+ break;
3644
+ }
3364
3645
  default: {
3365
3646
  wsBridge.send(ws, {
3366
3647
  type: "error",
@@ -3438,10 +3719,13 @@ async function start(opts = {}) {
3438
3719
  connections: wsBridge.getConnectionCount(),
3439
3720
  activeSessions: sessionManager.getActiveSessions().length
3440
3721
  }));
3441
- let mdnsService = null;
3442
3722
  const startMdns = () => {
3443
3723
  if (mdnsService) return;
3444
- mdnsService = new MdnsService({ wsPort: WS_PORT, httpPort: HTTP_PORT, token });
3724
+ mdnsService = new MdnsService({
3725
+ wsPort: WS_PORT,
3726
+ httpPort: HTTP_PORT,
3727
+ pairing: pairingManager.state
3728
+ });
3445
3729
  mdnsService.start();
3446
3730
  };
3447
3731
  const stopMdns = () => {
@@ -3476,7 +3760,9 @@ async function start(opts = {}) {
3476
3760
  errors.push(err);
3477
3761
  }
3478
3762
  };
3763
+ await attempt(() => authManager.destroy(), "AuthManager");
3479
3764
  await attempt(() => stopMdns(), "mDNS");
3765
+ await attempt(() => pairingManager.destroy(), "PairingManager");
3480
3766
  await attempt(() => wsBridge.close(), "WebSocket");
3481
3767
  await attempt(() => approvalProxy.close(), "ApprovalProxy");
3482
3768
  await attempt(() => sessionManager.destroy(), "SessionManager");
@@ -3504,7 +3790,9 @@ async function start(opts = {}) {
3504
3790
  } else {
3505
3791
  stopMdns();
3506
3792
  }
3507
- }
3793
+ },
3794
+ openPairing: (duration) => pairingManager.open(duration),
3795
+ closePairing: () => pairingManager.close()
3508
3796
  };
3509
3797
  }
3510
3798
 
@@ -3552,6 +3840,8 @@ async function main() {
3552
3840
  console.log();
3553
3841
  console.log(t("startup.waitingConnection"));
3554
3842
  console.log();
3843
+ console.log(t("startup.pairingOpen"));
3844
+ console.log();
3555
3845
  const shutdown = async (signal) => {
3556
3846
  console.log(`
3557
3847
  [Main] ${t("startup.receivedSignal", { signal })}`);
@@ -3564,8 +3854,24 @@ async function main() {
3564
3854
  process.exit(1);
3565
3855
  }
3566
3856
  };
3567
- process.on("SIGINT", () => shutdown("SIGINT"));
3568
- process.on("SIGTERM", () => shutdown("SIGTERM"));
3857
+ if (process.stdin.isTTY) {
3858
+ process.stdin.setRawMode(true);
3859
+ process.stdin.resume();
3860
+ process.stdin.setEncoding("utf8");
3861
+ process.stdin.on("data", (key) => {
3862
+ if (key === "p" || key === "P") {
3863
+ server.openPairing();
3864
+ console.log(`
3865
+ ${t("startup.pairingReopened")}`);
3866
+ }
3867
+ if (key === "") {
3868
+ shutdown("SIGINT");
3869
+ }
3870
+ });
3871
+ } else {
3872
+ process.on("SIGINT", () => shutdown("SIGINT"));
3873
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
3874
+ }
3569
3875
  }
3570
3876
  function getLocalIp() {
3571
3877
  const interfaces = (0, import_node_os5.networkInterfaces)();
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoNotificationChannel.d.ts","sourceRoot":"","sources":["../../src/notification/ExpoNotificationChannel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAI3F;;;;;;;GAOG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,MAAM,CAAyB;IAEvC,WAAW,IAAI,OAAO;IAItB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK1B,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAqCxD"}
1
+ {"version":3,"file":"ExpoNotificationChannel.d.ts","sourceRoot":"","sources":["../../src/notification/ExpoNotificationChannel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAI3F;;;;;;;GAOG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,MAAM,CAAyB;IAEvC,WAAW,IAAI,OAAO;IAItB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK1B,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6BxD"}
@@ -45,15 +45,7 @@ class ExpoNotificationChannel {
45
45
  console.warn('[ExpoNotificationChannel] Expo Push API 返回错误:', res.status, JSON.stringify(body));
46
46
  }
47
47
  else {
48
- // 检查每个 ticket 的状态,记录失败的推送
49
- const data = body.data;
50
- if (Array.isArray(data)) {
51
- for (const ticket of data) {
52
- if (ticket.status === 'error') {
53
- console.error(`[ExpoNotificationChannel] 推送失败: ${ticket.message} (${ticket.details?.error ?? 'unknown'})`);
54
- }
55
- }
56
- }
48
+ console.log('[ExpoNotificationChannel] Expo Push API 响应:', JSON.stringify(body));
57
49
  }
58
50
  }
59
51
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoNotificationChannel.js","sourceRoot":"","sources":["../../src/notification/ExpoNotificationChannel.ts"],"names":[],"mappings":";;;AAEA,MAAM,aAAa,GAAG,sCAAsC,CAAA;AAE5D;;;;;;;GAOG;AACH,MAAa,uBAAuB;IAC1B,MAAM,GAAgB,IAAI,GAAG,EAAE,CAAA;IAEvC,WAAW;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAA;IAC7B,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACtB,OAAO,CAAC,GAAG,CAAC,mDAAmD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;IACpF,CAAC;IAED,WAAW,CAAC,KAAa;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACzB,OAAO,CAAC,GAAG,CAAC,mDAAmD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;IACpF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA4B;QACrC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,OAAM;QAElC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACpD,EAAE;YACF,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS;YACjC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;SACzB,CAAC,CAAC,CAAA;QAEH,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,wCAAwC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;YAC9E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;gBACrC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,EAAE,kBAAkB,EAAE;gBAC3E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;aAC/B,CAAC,CAAA;YAEF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;YAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,+CAA+C,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;YACjG,CAAC;iBAAM,CAAC;gBACN,0BAA0B;gBAC1B,MAAM,IAAI,GAAI,IAA0G,CAAC,IAAI,CAAA;gBAC7H,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxB,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;wBAC1B,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;4BAC9B,OAAO,CAAC,KAAK,CAAC,mCAAmC,MAAM,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,IAAI,SAAS,GAAG,CAAC,CAAA;wBAC5G,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,GAAG,CAAC,CAAA;QACxD,CAAC;IACH,CAAC;CACF;AAtDD,0DAsDC"}
1
+ {"version":3,"file":"ExpoNotificationChannel.js","sourceRoot":"","sources":["../../src/notification/ExpoNotificationChannel.ts"],"names":[],"mappings":";;;AAEA,MAAM,aAAa,GAAG,sCAAsC,CAAA;AAE5D;;;;;;;GAOG;AACH,MAAa,uBAAuB;IAC1B,MAAM,GAAgB,IAAI,GAAG,EAAE,CAAA;IAEvC,WAAW;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAA;IAC7B,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACtB,OAAO,CAAC,GAAG,CAAC,mDAAmD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;IACpF,CAAC;IAED,WAAW,CAAC,KAAa;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACzB,OAAO,CAAC,GAAG,CAAC,mDAAmD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;IACpF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA4B;QACrC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,OAAM;QAElC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACpD,EAAE;YACF,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,SAAS;YACjC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;SACzB,CAAC,CAAC,CAAA;QAEH,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,wCAAwC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;YAC9E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;gBACrC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,EAAE,kBAAkB,EAAE;gBAC3E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;aAC/B,CAAC,CAAA;YAEF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;YAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,CAAC,+CAA+C,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;YACjG,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,6CAA6C,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;YAClF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,GAAG,CAAC,CAAA;QACxD,CAAC;IACH,CAAC;CACF;AA9CD,0DA8CC"}
@@ -36,7 +36,7 @@ export declare class NotificationService {
36
36
  /** 更新会话的 YOLO 模式状态 */
37
37
  setYoloMode(sessionId: string, enabled: boolean): void;
38
38
  /** 直接触发审批通知(由 ApprovalProxy 回调调用) */
39
- notifyApproval(request: ApprovalRequest, pendingCount: number): void;
39
+ notifyApproval(request: ApprovalRequest): void;
40
40
  /** 简单的工具危险等级判断 */
41
41
  private getDangerLevel;
42
42
  /** 清理资源 */
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationService.d.ts","sourceRoot":"","sources":["../../src/notification/NotificationService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,6BAA6B,CAAA;AAC3F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAEnE;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAQ5B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,WAAW;IARrB,OAAO,CAAC,UAAU,CAAwE;IAC1F,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,0CAA0C;IAC1C,OAAO,CAAC,aAAa,CAA6B;gBAGxC,cAAc,EAAE,cAAc,EAC9B,WAAW,GAAE,uBAAuB,GAAG,IAAW;IAQ5D,8BAA8B;IAC9B,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,UAAO,GAAG,IAAI;IAI1E,qBAAqB;IACrB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAKrD,0CAA0C;IAC1C,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjC,qCAAqC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC,8CAA8C;IAC9C,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAI1D,0DAA0D;IAC1D,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5D,gCAAgC;IAChC,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIhD,sBAAsB;IACtB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAItD,qCAAqC;IACrC,cAAc,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAoDpE,kBAAkB;IAClB,OAAO,CAAC,cAAc;IAMtB,WAAW;IACX,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,cAAc;IAKtB,sBAAsB;IACtB,OAAO,CAAC,WAAW;CAGpB"}
1
+ {"version":3,"file":"NotificationService.d.ts","sourceRoot":"","sources":["../../src/notification/NotificationService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAuB,MAAM,6BAA6B,CAAA;AAC3F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAEnE;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAQ5B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,WAAW;IARrB,OAAO,CAAC,UAAU,CAAwE;IAC1F,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,0CAA0C;IAC1C,OAAO,CAAC,aAAa,CAA6B;gBAGxC,cAAc,EAAE,cAAc,EAC9B,WAAW,GAAE,uBAAuB,GAAG,IAAW;IAQ5D,8BAA8B;IAC9B,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,OAAO,UAAO,GAAG,IAAI;IAI1E,qBAAqB;IACrB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAKrD,0CAA0C;IAC1C,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjC,qCAAqC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC,8CAA8C;IAC9C,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAI1D,0DAA0D;IAC1D,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5D,gCAAgC;IAChC,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIhD,sBAAsB;IACtB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAItD,qCAAqC;IACrC,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IA0C9C,kBAAkB;IAClB,OAAO,CAAC,cAAc;IAMtB,WAAW;IACX,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,WAAW;IA2CnB,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,cAAc;IAKtB,sBAAsB;IACtB,OAAO,CAAC,WAAW;CAGpB"}
@@ -60,21 +60,14 @@ class NotificationService {
60
60
  this.yoloModeState.set(sessionId, enabled);
61
61
  }
62
62
  /** 直接触发审批通知(由 ApprovalProxy 回调调用) */
63
- notifyApproval(request, pendingCount) {
63
+ notifyApproval(request) {
64
64
  // YOLO 模式:客户端会自动批准,不发推送
65
65
  if (this.yoloModeState.get(request.sessionId))
66
66
  return;
67
67
  const projectName = this.getProjectName(request.sessionId);
68
- // 多个待审批时汇总文案
69
- const title = pendingCount > 1
70
- ? `${pendingCount} 项待审批 — ${projectName}`
71
- : `需要授权 — ${projectName}`;
72
- const body = pendingCount > 1
73
- ? `最新: ${request.toolName}: ${request.description}`
74
- : `${request.toolName}: ${request.description}`;
75
68
  this.notify({
76
- title,
77
- body,
69
+ title: `需要授权 — ${projectName}`,
70
+ body: `${request.toolName}: ${request.description}`,
78
71
  sound: 'Funk',
79
72
  data: {
80
73
  type: 'approval_request',
@@ -94,13 +87,12 @@ class NotificationService {
94
87
  toolName: request.toolName,
95
88
  description: request.description.slice(0, 80),
96
89
  dangerLevel,
97
- pendingCount,
98
90
  },
99
91
  isYoloMode,
100
92
  updatedAt: Date.now(),
101
93
  }, {
102
- title,
103
- body,
94
+ title: `需要授权 — ${projectName}`,
95
+ body: `${request.toolName}: ${request.description}`,
104
96
  });
105
97
  }
106
98
  }