sessix-server 0.2.2 → 0.2.4

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/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_node_os5 = require("os");
27
+ var import_node_os6 = require("os");
28
28
  var import_node_fs2 = require("fs");
29
29
  var import_node_path5 = require("path");
30
30
 
@@ -85,7 +85,8 @@ var zh = {
85
85
  alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
86
86
  started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
87
87
  stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
88
- closed: "mDNS \u670D\u52A1\u5DF2\u5173\u95ED"
88
+ closed: "mDNS \u670D\u52A1\u5DF2\u5173\u95ED",
89
+ boundInterface: "mDNS \u7ED1\u5B9A\u5230\u5C40\u57DF\u7F51\u63A5\u53E3 {{ip}}"
89
90
  },
90
91
  approval: {
91
92
  httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
@@ -190,7 +191,8 @@ var en = {
190
191
  alreadyRunning: "Service is already running",
191
192
  started: "mDNS broadcast started: _sessix._tcp port {{port}}",
192
193
  stopped: "Service broadcast stopped",
193
- closed: "mDNS service closed"
194
+ closed: "mDNS service closed",
195
+ boundInterface: "mDNS bound to LAN interface {{ip}}"
194
196
  },
195
197
  approval: {
196
198
  httpStarted: "HTTP approval server started on port {{port}}",
@@ -287,7 +289,7 @@ function t(key, params) {
287
289
  // src/server.ts
288
290
  var import_uuid4 = require("uuid");
289
291
  var import_promises4 = require("fs/promises");
290
- var import_node_os4 = require("os");
292
+ var import_node_os5 = require("os");
291
293
  var import_node_path4 = require("path");
292
294
  var import_node_child_process2 = require("child_process");
293
295
  var import_node_util = require("util");
@@ -328,8 +330,8 @@ var ProcessProvider = class {
328
330
  activeSessions = /* @__PURE__ */ new Map();
329
331
  /** 事件发射器,用于分发 Claude 事件流 */
330
332
  emitter = new import_events.EventEmitter();
331
- /** 已发射的 AskUserQuestion toolUseId 集合(避免 partial message 重复触发) */
332
- emittedQuestionToolUseIds = /* @__PURE__ */ new Set();
333
+ /** 已发射的 AskUserQuestion toolUseId 集合,按会话隔离(避免 partial message 重复触发) */
334
+ emittedQuestionToolUseIds = /* @__PURE__ */ new Map();
333
335
  /**
334
336
  * 启动新会话或恢复已有会话
335
337
  *
@@ -405,6 +407,7 @@ var ProcessProvider = class {
405
407
  });
406
408
  });
407
409
  }
410
+ this.emittedQuestionToolUseIds.delete(sessionId);
408
411
  this.activeSessions.delete(sessionId);
409
412
  }
410
413
  /**
@@ -601,8 +604,13 @@ var ProcessProvider = class {
601
604
  const question = input.question ?? "";
602
605
  if (!question) continue;
603
606
  const prevKey = `${block.id}:${question}:${JSON.stringify(input.options ?? [])}`;
604
- if (this.emittedQuestionToolUseIds.has(prevKey)) continue;
605
- this.emittedQuestionToolUseIds.add(prevKey);
607
+ let sessionSet = this.emittedQuestionToolUseIds.get(sessionId);
608
+ if (!sessionSet) {
609
+ sessionSet = /* @__PURE__ */ new Set();
610
+ this.emittedQuestionToolUseIds.set(sessionId, sessionSet);
611
+ }
612
+ if (sessionSet.has(prevKey)) continue;
613
+ sessionSet.add(prevKey);
606
614
  this.emitter.emit(this.getQuestionEventName(sessionId), {
607
615
  toolUseId: block.id,
608
616
  question,
@@ -1105,6 +1113,7 @@ var SessionManager = class {
1105
1113
  }
1106
1114
  /**
1107
1115
  * 刷新缓冲的 assistant 事件,批量发送
1116
+ * Public:subscribe 时需要在读取缓冲区前先刷出,避免事件同时出现在 session_history 和广播中
1108
1117
  */
1109
1118
  flushPendingAssistant(sessionId) {
1110
1119
  const pending = this.pendingAssistantEvents.get(sessionId);
@@ -1706,6 +1715,17 @@ var ApprovalProxy = class _ApprovalProxy {
1706
1715
  isPending(requestId) {
1707
1716
  return this.pendingApprovals.has(requestId);
1708
1717
  }
1718
+ /** 清理指定会话的所有待处理审批(会话被 kill 时调用,默认放行避免阻塞 hook 脚本) */
1719
+ clearPendingForSession(sessionId) {
1720
+ for (const [requestId, pending] of this.pendingApprovals) {
1721
+ if (pending.request.sessionId === sessionId) {
1722
+ clearTimeout(pending.timer);
1723
+ pending.resolve({ decision: "allow" });
1724
+ this.pendingApprovals.delete(requestId);
1725
+ console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
1726
+ }
1727
+ }
1728
+ }
1709
1729
  /** 检查工具是否已被"始终允许"(内存缓存 + settings.json 双重检查) */
1710
1730
  isToolAlwaysAllowed(toolName, projectPath) {
1711
1731
  if (this.alwaysAllowedTools.has(toolName)) return true;
@@ -1984,6 +2004,19 @@ var ApprovalProxy = class _ApprovalProxy {
1984
2004
 
1985
2005
  // src/mdns/MdnsService.ts
1986
2006
  var import_bonjour_service = __toESM(require("bonjour-service"));
2007
+ var import_node_os3 = require("os");
2008
+ function getLanAddresses() {
2009
+ const results = [];
2010
+ for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
2011
+ if (name.startsWith("utun") || name.startsWith("lo")) continue;
2012
+ for (const addr of addrs ?? []) {
2013
+ if (addr.family === "IPv4" && !addr.internal) {
2014
+ results.push(addr.address);
2015
+ }
2016
+ }
2017
+ }
2018
+ return results;
2019
+ }
1987
2020
  var MdnsService = class {
1988
2021
  bonjour = null;
1989
2022
  service = null;
@@ -2005,7 +2038,11 @@ var MdnsService = class {
2005
2038
  console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
2006
2039
  return;
2007
2040
  }
2008
- this.bonjour = new import_bonjour_service.default();
2041
+ const lanAddrs = getLanAddresses();
2042
+ this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
2043
+ if (lanAddrs.length > 0) {
2044
+ console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
2045
+ }
2009
2046
  this.service = this.bonjour.publish({
2010
2047
  name: "Sessix",
2011
2048
  type: "sessix",
@@ -2068,11 +2105,11 @@ var MdnsService = class {
2068
2105
  // src/hooks/HookInstaller.ts
2069
2106
  var import_promises2 = require("fs/promises");
2070
2107
  var import_node_path2 = require("path");
2071
- var import_node_os3 = require("os");
2072
- var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".sessix", "hooks");
2108
+ var import_node_os4 = require("os");
2109
+ var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
2073
2110
  var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
2074
2111
  var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
2075
- var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude", "settings.json");
2112
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
2076
2113
  var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
2077
2114
  var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
2078
2115
  var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
@@ -2093,7 +2130,7 @@ PROJECT_PATH="$PWD"
2093
2130
  RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
2094
2131
  -H "Content-Type: application/json" \\
2095
2132
  -d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
2096
- --max-time 330 \\
2133
+ --max-time 320 \\
2097
2134
  2>/dev/null)
2098
2135
 
2099
2136
  if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
@@ -2252,7 +2289,7 @@ var HookInstaller = class {
2252
2289
  * 写入 Claude Code settings.json
2253
2290
  */
2254
2291
  async writeClaudeSettings(settings) {
2255
- await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude"), { recursive: true });
2292
+ await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
2256
2293
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2257
2294
  }
2258
2295
  /**
@@ -2350,7 +2387,7 @@ var NotificationService = class {
2350
2387
  const title = pendingCount > 1 ? t("notification.pendingApprovals", { title: sessionTitle, count: pendingCount }) : sessionTitle;
2351
2388
  const body = pendingCount > 1 ? `\u{1F527} \u6700\u65B0: ${request.toolName}: ${request.description}` : `\u{1F527} ${request.toolName}: ${request.description}`;
2352
2389
  if (this.activityPushChannel?.hasToken(request.sessionId)) {
2353
- const dangerLevel = this.getDangerLevel(request.toolName);
2390
+ const dangerLevel2 = this.getDangerLevel(request.toolName);
2354
2391
  const isYoloMode = this.getYoloMode(request.sessionId);
2355
2392
  this.activityPushChannel.updateActivityWithAlert(
2356
2393
  request.sessionId,
@@ -2362,7 +2399,7 @@ var NotificationService = class {
2362
2399
  requestId: request.id,
2363
2400
  toolName: request.toolName,
2364
2401
  description: request.description.slice(0, 80),
2365
- dangerLevel,
2402
+ dangerLevel: dangerLevel2,
2366
2403
  pendingCount
2367
2404
  },
2368
2405
  isYoloMode,
@@ -2372,15 +2409,28 @@ var NotificationService = class {
2372
2409
  );
2373
2410
  return;
2374
2411
  }
2412
+ const dangerLevel = this.getDangerLevel(request.toolName);
2413
+ const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2414
+ const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2415
+ const projectName = (0, import_node_path3.basename)(
2416
+ this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2417
+ );
2418
+ const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
2419
+ const subtitle = `\u{1F527} ${request.toolName}: ${this.extractTarget(request)}`;
2375
2420
  this.notify({
2376
- title,
2421
+ title: pushTitle,
2422
+ subtitle,
2377
2423
  body,
2378
2424
  sound: "default",
2379
2425
  badge: this.getGlobalPendingCount(),
2426
+ categoryId,
2380
2427
  data: {
2381
2428
  type: "approval_request",
2382
2429
  sessionId: request.sessionId,
2383
- requestId: request.id
2430
+ requestId: request.id,
2431
+ toolName: request.toolName,
2432
+ projectName,
2433
+ dangerLevel
2384
2434
  }
2385
2435
  });
2386
2436
  }
@@ -2415,6 +2465,13 @@ var NotificationService = class {
2415
2465
  }
2416
2466
  });
2417
2467
  }
2468
+ /** 从审批请求中提取操作目标的简短描述 */
2469
+ extractTarget(request) {
2470
+ const input = request.toolInput;
2471
+ if (input.file_path) return (0, import_node_path3.basename)(String(input.file_path));
2472
+ if (input.command) return String(input.command).slice(0, 40);
2473
+ return request.description.slice(0, 40);
2474
+ }
2418
2475
  /** 简单的工具危险等级判断 */
2419
2476
  getDangerLevel(toolName) {
2420
2477
  if (toolName === "Bash") return "danger";
@@ -2594,9 +2651,11 @@ var ExpoNotificationChannel = class {
2594
2651
  return {
2595
2652
  to,
2596
2653
  title: payload.title,
2654
+ subtitle: payload.subtitle,
2597
2655
  body: payload.body,
2598
2656
  badge: payload.badge,
2599
2657
  sound: pushSound,
2658
+ categoryId: payload.categoryId,
2600
2659
  data: payload.data ?? {}
2601
2660
  };
2602
2661
  });
@@ -3308,7 +3367,7 @@ async function createWithRetry(label, port, factory) {
3308
3367
  }
3309
3368
  }
3310
3369
  async function start(opts = {}) {
3311
- const configDir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".sessix");
3370
+ const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
3312
3371
  const tokenFile = (0, import_node_path4.join)(configDir, "token");
3313
3372
  let token;
3314
3373
  if (opts.token !== void 0) {
@@ -3359,7 +3418,7 @@ async function start(opts = {}) {
3359
3418
  let mdnsService = null;
3360
3419
  const pairingManager = new PairingManager({
3361
3420
  token,
3362
- serverName: (0, import_node_os4.hostname)(),
3421
+ serverName: (0, import_node_os5.hostname)(),
3363
3422
  version: "0.2.0",
3364
3423
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3365
3424
  });
@@ -3433,6 +3492,7 @@ async function start(opts = {}) {
3433
3492
  }
3434
3493
  case "kill_session": {
3435
3494
  wsBridge.broadcast({ type: "status_change", sessionId: event.sessionId, status: "idle" });
3495
+ approvalProxy.clearPendingForSession(event.sessionId);
3436
3496
  await sessionManager.killSession(event.sessionId);
3437
3497
  wsBridge.broadcast({
3438
3498
  type: "session_list",
@@ -3458,7 +3518,8 @@ async function start(opts = {}) {
3458
3518
  type: "session_list",
3459
3519
  sessions: sessionManager.getActiveSessions()
3460
3520
  });
3461
- const bufferedEvents = sessionManager.getSessionEvents(event.sessionId);
3521
+ sessionManager.flushPendingAssistant(event.sessionId);
3522
+ const bufferedEvents = [...sessionManager.getSessionEvents(event.sessionId)];
3462
3523
  if (sessionManager.isBufferTruncated(event.sessionId)) {
3463
3524
  const projectPath = sessionManager.getSessionProjectPath(event.sessionId);
3464
3525
  if (projectPath) {
@@ -3914,7 +3975,7 @@ ${t("startup.pairingReopened")}`);
3914
3975
  }
3915
3976
  }
3916
3977
  function getLocalIp() {
3917
- const interfaces = (0, import_node_os5.networkInterfaces)();
3978
+ const interfaces = (0, import_node_os6.networkInterfaces)();
3918
3979
  for (const iface of Object.values(interfaces)) {
3919
3980
  for (const addr of iface ?? []) {
3920
3981
  if (addr.family === "IPv4" && !addr.internal) {
package/dist/server.js CHANGED
@@ -91,7 +91,8 @@ var zh = {
91
91
  alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
92
92
  started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
93
93
  stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
94
- closed: "mDNS \u670D\u52A1\u5DF2\u5173\u95ED"
94
+ closed: "mDNS \u670D\u52A1\u5DF2\u5173\u95ED",
95
+ boundInterface: "mDNS \u7ED1\u5B9A\u5230\u5C40\u57DF\u7F51\u63A5\u53E3 {{ip}}"
95
96
  },
96
97
  approval: {
97
98
  httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
@@ -196,7 +197,8 @@ var en = {
196
197
  alreadyRunning: "Service is already running",
197
198
  started: "mDNS broadcast started: _sessix._tcp port {{port}}",
198
199
  stopped: "Service broadcast stopped",
199
- closed: "mDNS service closed"
200
+ closed: "mDNS service closed",
201
+ boundInterface: "mDNS bound to LAN interface {{ip}}"
200
202
  },
201
203
  approval: {
202
204
  httpStarted: "HTTP approval server started on port {{port}}",
@@ -293,7 +295,7 @@ function t(key, params) {
293
295
  // src/server.ts
294
296
  var import_uuid4 = require("uuid");
295
297
  var import_promises4 = require("fs/promises");
296
- var import_node_os4 = require("os");
298
+ var import_node_os5 = require("os");
297
299
  var import_node_path4 = require("path");
298
300
  var import_node_child_process2 = require("child_process");
299
301
  var import_node_util = require("util");
@@ -334,8 +336,8 @@ var ProcessProvider = class {
334
336
  activeSessions = /* @__PURE__ */ new Map();
335
337
  /** 事件发射器,用于分发 Claude 事件流 */
336
338
  emitter = new import_events.EventEmitter();
337
- /** 已发射的 AskUserQuestion toolUseId 集合(避免 partial message 重复触发) */
338
- emittedQuestionToolUseIds = /* @__PURE__ */ new Set();
339
+ /** 已发射的 AskUserQuestion toolUseId 集合,按会话隔离(避免 partial message 重复触发) */
340
+ emittedQuestionToolUseIds = /* @__PURE__ */ new Map();
339
341
  /**
340
342
  * 启动新会话或恢复已有会话
341
343
  *
@@ -411,6 +413,7 @@ var ProcessProvider = class {
411
413
  });
412
414
  });
413
415
  }
416
+ this.emittedQuestionToolUseIds.delete(sessionId);
414
417
  this.activeSessions.delete(sessionId);
415
418
  }
416
419
  /**
@@ -607,8 +610,13 @@ var ProcessProvider = class {
607
610
  const question = input.question ?? "";
608
611
  if (!question) continue;
609
612
  const prevKey = `${block.id}:${question}:${JSON.stringify(input.options ?? [])}`;
610
- if (this.emittedQuestionToolUseIds.has(prevKey)) continue;
611
- this.emittedQuestionToolUseIds.add(prevKey);
613
+ let sessionSet = this.emittedQuestionToolUseIds.get(sessionId);
614
+ if (!sessionSet) {
615
+ sessionSet = /* @__PURE__ */ new Set();
616
+ this.emittedQuestionToolUseIds.set(sessionId, sessionSet);
617
+ }
618
+ if (sessionSet.has(prevKey)) continue;
619
+ sessionSet.add(prevKey);
612
620
  this.emitter.emit(this.getQuestionEventName(sessionId), {
613
621
  toolUseId: block.id,
614
622
  question,
@@ -1111,6 +1119,7 @@ var SessionManager = class {
1111
1119
  }
1112
1120
  /**
1113
1121
  * 刷新缓冲的 assistant 事件,批量发送
1122
+ * Public:subscribe 时需要在读取缓冲区前先刷出,避免事件同时出现在 session_history 和广播中
1114
1123
  */
1115
1124
  flushPendingAssistant(sessionId) {
1116
1125
  const pending = this.pendingAssistantEvents.get(sessionId);
@@ -1712,6 +1721,17 @@ var ApprovalProxy = class _ApprovalProxy {
1712
1721
  isPending(requestId) {
1713
1722
  return this.pendingApprovals.has(requestId);
1714
1723
  }
1724
+ /** 清理指定会话的所有待处理审批(会话被 kill 时调用,默认放行避免阻塞 hook 脚本) */
1725
+ clearPendingForSession(sessionId) {
1726
+ for (const [requestId, pending] of this.pendingApprovals) {
1727
+ if (pending.request.sessionId === sessionId) {
1728
+ clearTimeout(pending.timer);
1729
+ pending.resolve({ decision: "allow" });
1730
+ this.pendingApprovals.delete(requestId);
1731
+ console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
1732
+ }
1733
+ }
1734
+ }
1715
1735
  /** 检查工具是否已被"始终允许"(内存缓存 + settings.json 双重检查) */
1716
1736
  isToolAlwaysAllowed(toolName, projectPath) {
1717
1737
  if (this.alwaysAllowedTools.has(toolName)) return true;
@@ -1990,6 +2010,19 @@ var ApprovalProxy = class _ApprovalProxy {
1990
2010
 
1991
2011
  // src/mdns/MdnsService.ts
1992
2012
  var import_bonjour_service = __toESM(require("bonjour-service"));
2013
+ var import_node_os3 = require("os");
2014
+ function getLanAddresses() {
2015
+ const results = [];
2016
+ for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
2017
+ if (name.startsWith("utun") || name.startsWith("lo")) continue;
2018
+ for (const addr of addrs ?? []) {
2019
+ if (addr.family === "IPv4" && !addr.internal) {
2020
+ results.push(addr.address);
2021
+ }
2022
+ }
2023
+ }
2024
+ return results;
2025
+ }
1993
2026
  var MdnsService = class {
1994
2027
  bonjour = null;
1995
2028
  service = null;
@@ -2011,7 +2044,11 @@ var MdnsService = class {
2011
2044
  console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
2012
2045
  return;
2013
2046
  }
2014
- this.bonjour = new import_bonjour_service.default();
2047
+ const lanAddrs = getLanAddresses();
2048
+ this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
2049
+ if (lanAddrs.length > 0) {
2050
+ console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
2051
+ }
2015
2052
  this.service = this.bonjour.publish({
2016
2053
  name: "Sessix",
2017
2054
  type: "sessix",
@@ -2074,11 +2111,11 @@ var MdnsService = class {
2074
2111
  // src/hooks/HookInstaller.ts
2075
2112
  var import_promises2 = require("fs/promises");
2076
2113
  var import_node_path2 = require("path");
2077
- var import_node_os3 = require("os");
2078
- var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".sessix", "hooks");
2114
+ var import_node_os4 = require("os");
2115
+ var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
2079
2116
  var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
2080
2117
  var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
2081
- var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude", "settings.json");
2118
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
2082
2119
  var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
2083
2120
  var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
2084
2121
  var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
@@ -2099,7 +2136,7 @@ PROJECT_PATH="$PWD"
2099
2136
  RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
2100
2137
  -H "Content-Type: application/json" \\
2101
2138
  -d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
2102
- --max-time 330 \\
2139
+ --max-time 320 \\
2103
2140
  2>/dev/null)
2104
2141
 
2105
2142
  if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
@@ -2258,7 +2295,7 @@ var HookInstaller = class {
2258
2295
  * 写入 Claude Code settings.json
2259
2296
  */
2260
2297
  async writeClaudeSettings(settings) {
2261
- await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude"), { recursive: true });
2298
+ await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
2262
2299
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2263
2300
  }
2264
2301
  /**
@@ -2356,7 +2393,7 @@ var NotificationService = class {
2356
2393
  const title = pendingCount > 1 ? t("notification.pendingApprovals", { title: sessionTitle, count: pendingCount }) : sessionTitle;
2357
2394
  const body = pendingCount > 1 ? `\u{1F527} \u6700\u65B0: ${request.toolName}: ${request.description}` : `\u{1F527} ${request.toolName}: ${request.description}`;
2358
2395
  if (this.activityPushChannel?.hasToken(request.sessionId)) {
2359
- const dangerLevel = this.getDangerLevel(request.toolName);
2396
+ const dangerLevel2 = this.getDangerLevel(request.toolName);
2360
2397
  const isYoloMode = this.getYoloMode(request.sessionId);
2361
2398
  this.activityPushChannel.updateActivityWithAlert(
2362
2399
  request.sessionId,
@@ -2368,7 +2405,7 @@ var NotificationService = class {
2368
2405
  requestId: request.id,
2369
2406
  toolName: request.toolName,
2370
2407
  description: request.description.slice(0, 80),
2371
- dangerLevel,
2408
+ dangerLevel: dangerLevel2,
2372
2409
  pendingCount
2373
2410
  },
2374
2411
  isYoloMode,
@@ -2378,15 +2415,28 @@ var NotificationService = class {
2378
2415
  );
2379
2416
  return;
2380
2417
  }
2418
+ const dangerLevel = this.getDangerLevel(request.toolName);
2419
+ const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2420
+ const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2421
+ const projectName = (0, import_node_path3.basename)(
2422
+ this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2423
+ );
2424
+ const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
2425
+ const subtitle = `\u{1F527} ${request.toolName}: ${this.extractTarget(request)}`;
2381
2426
  this.notify({
2382
- title,
2427
+ title: pushTitle,
2428
+ subtitle,
2383
2429
  body,
2384
2430
  sound: "default",
2385
2431
  badge: this.getGlobalPendingCount(),
2432
+ categoryId,
2386
2433
  data: {
2387
2434
  type: "approval_request",
2388
2435
  sessionId: request.sessionId,
2389
- requestId: request.id
2436
+ requestId: request.id,
2437
+ toolName: request.toolName,
2438
+ projectName,
2439
+ dangerLevel
2390
2440
  }
2391
2441
  });
2392
2442
  }
@@ -2421,6 +2471,13 @@ var NotificationService = class {
2421
2471
  }
2422
2472
  });
2423
2473
  }
2474
+ /** 从审批请求中提取操作目标的简短描述 */
2475
+ extractTarget(request) {
2476
+ const input = request.toolInput;
2477
+ if (input.file_path) return (0, import_node_path3.basename)(String(input.file_path));
2478
+ if (input.command) return String(input.command).slice(0, 40);
2479
+ return request.description.slice(0, 40);
2480
+ }
2424
2481
  /** 简单的工具危险等级判断 */
2425
2482
  getDangerLevel(toolName) {
2426
2483
  if (toolName === "Bash") return "danger";
@@ -2600,9 +2657,11 @@ var ExpoNotificationChannel = class {
2600
2657
  return {
2601
2658
  to,
2602
2659
  title: payload.title,
2660
+ subtitle: payload.subtitle,
2603
2661
  body: payload.body,
2604
2662
  badge: payload.badge,
2605
2663
  sound: pushSound,
2664
+ categoryId: payload.categoryId,
2606
2665
  data: payload.data ?? {}
2607
2666
  };
2608
2667
  });
@@ -3314,7 +3373,7 @@ async function createWithRetry(label, port, factory) {
3314
3373
  }
3315
3374
  }
3316
3375
  async function start(opts = {}) {
3317
- const configDir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".sessix");
3376
+ const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
3318
3377
  const tokenFile = (0, import_node_path4.join)(configDir, "token");
3319
3378
  let token;
3320
3379
  if (opts.token !== void 0) {
@@ -3365,7 +3424,7 @@ async function start(opts = {}) {
3365
3424
  let mdnsService = null;
3366
3425
  const pairingManager = new PairingManager({
3367
3426
  token,
3368
- serverName: (0, import_node_os4.hostname)(),
3427
+ serverName: (0, import_node_os5.hostname)(),
3369
3428
  version: "0.2.0",
3370
3429
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3371
3430
  });
@@ -3439,6 +3498,7 @@ async function start(opts = {}) {
3439
3498
  }
3440
3499
  case "kill_session": {
3441
3500
  wsBridge.broadcast({ type: "status_change", sessionId: event.sessionId, status: "idle" });
3501
+ approvalProxy.clearPendingForSession(event.sessionId);
3442
3502
  await sessionManager.killSession(event.sessionId);
3443
3503
  wsBridge.broadcast({
3444
3504
  type: "session_list",
@@ -3464,7 +3524,8 @@ async function start(opts = {}) {
3464
3524
  type: "session_list",
3465
3525
  sessions: sessionManager.getActiveSessions()
3466
3526
  });
3467
- const bufferedEvents = sessionManager.getSessionEvents(event.sessionId);
3527
+ sessionManager.flushPendingAssistant(event.sessionId);
3528
+ const bufferedEvents = [...sessionManager.getSessionEvents(event.sessionId)];
3468
3529
  if (sessionManager.isBufferTruncated(event.sessionId)) {
3469
3530
  const projectPath = sessionManager.getSessionProjectPath(event.sessionId);
3470
3531
  if (projectPath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessix-server",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "bin": {
5
5
  "sessix-server": "./dist/index.js"
6
6
  },