sessix-server 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -24,7 +24,9 @@ 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
+ var import_node_fs2 = require("fs");
29
+ var import_node_path5 = require("path");
28
30
 
29
31
  // src/i18n/locales/zh.ts
30
32
  var zh = {
@@ -45,6 +47,7 @@ var zh = {
45
47
  autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
46
48
  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
49
  pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
50
+ updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
48
51
  receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
49
52
  goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
50
53
  shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
@@ -82,7 +85,8 @@ var zh = {
82
85
  alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
83
86
  started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
84
87
  stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
85
- 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}}"
86
90
  },
87
91
  approval: {
88
92
  httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
@@ -149,6 +153,7 @@ var en = {
149
153
  autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
150
154
  pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
151
155
  pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
156
+ updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
152
157
  receivedSignal: "Received {{signal}}, graceful shutdown...",
153
158
  goodbye: "All services closed, goodbye!",
154
159
  shutdownError: "Shutdown error:",
@@ -186,7 +191,8 @@ var en = {
186
191
  alreadyRunning: "Service is already running",
187
192
  started: "mDNS broadcast started: _sessix._tcp port {{port}}",
188
193
  stopped: "Service broadcast stopped",
189
- closed: "mDNS service closed"
194
+ closed: "mDNS service closed",
195
+ boundInterface: "mDNS bound to LAN interface {{ip}}"
190
196
  },
191
197
  approval: {
192
198
  httpStarted: "HTTP approval server started on port {{port}}",
@@ -283,7 +289,7 @@ function t(key, params) {
283
289
  // src/server.ts
284
290
  var import_uuid4 = require("uuid");
285
291
  var import_promises4 = require("fs/promises");
286
- var import_node_os4 = require("os");
292
+ var import_node_os5 = require("os");
287
293
  var import_node_path4 = require("path");
288
294
  var import_node_child_process2 = require("child_process");
289
295
  var import_node_util = require("util");
@@ -324,8 +330,8 @@ var ProcessProvider = class {
324
330
  activeSessions = /* @__PURE__ */ new Map();
325
331
  /** 事件发射器,用于分发 Claude 事件流 */
326
332
  emitter = new import_events.EventEmitter();
327
- /** 已发射的 AskUserQuestion toolUseId 集合(避免 partial message 重复触发) */
328
- emittedQuestionToolUseIds = /* @__PURE__ */ new Set();
333
+ /** 已发射的 AskUserQuestion toolUseId 集合,按会话隔离(避免 partial message 重复触发) */
334
+ emittedQuestionToolUseIds = /* @__PURE__ */ new Map();
329
335
  /**
330
336
  * 启动新会话或恢复已有会话
331
337
  *
@@ -401,6 +407,7 @@ var ProcessProvider = class {
401
407
  });
402
408
  });
403
409
  }
410
+ this.emittedQuestionToolUseIds.delete(sessionId);
404
411
  this.activeSessions.delete(sessionId);
405
412
  }
406
413
  /**
@@ -597,8 +604,13 @@ var ProcessProvider = class {
597
604
  const question = input.question ?? "";
598
605
  if (!question) continue;
599
606
  const prevKey = `${block.id}:${question}:${JSON.stringify(input.options ?? [])}`;
600
- if (this.emittedQuestionToolUseIds.has(prevKey)) continue;
601
- 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);
602
614
  this.emitter.emit(this.getQuestionEventName(sessionId), {
603
615
  toolUseId: block.id,
604
616
  question,
@@ -1702,6 +1714,17 @@ var ApprovalProxy = class _ApprovalProxy {
1702
1714
  isPending(requestId) {
1703
1715
  return this.pendingApprovals.has(requestId);
1704
1716
  }
1717
+ /** 清理指定会话的所有待处理审批(会话被 kill 时调用,默认放行避免阻塞 hook 脚本) */
1718
+ clearPendingForSession(sessionId) {
1719
+ for (const [requestId, pending] of this.pendingApprovals) {
1720
+ if (pending.request.sessionId === sessionId) {
1721
+ clearTimeout(pending.timer);
1722
+ pending.resolve({ decision: "allow" });
1723
+ this.pendingApprovals.delete(requestId);
1724
+ console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
1725
+ }
1726
+ }
1727
+ }
1705
1728
  /** 检查工具是否已被"始终允许"(内存缓存 + settings.json 双重检查) */
1706
1729
  isToolAlwaysAllowed(toolName, projectPath) {
1707
1730
  if (this.alwaysAllowedTools.has(toolName)) return true;
@@ -1980,6 +2003,19 @@ var ApprovalProxy = class _ApprovalProxy {
1980
2003
 
1981
2004
  // src/mdns/MdnsService.ts
1982
2005
  var import_bonjour_service = __toESM(require("bonjour-service"));
2006
+ var import_node_os3 = require("os");
2007
+ function getLanAddresses() {
2008
+ const results = [];
2009
+ for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
2010
+ if (name.startsWith("utun") || name.startsWith("lo")) continue;
2011
+ for (const addr of addrs ?? []) {
2012
+ if (addr.family === "IPv4" && !addr.internal) {
2013
+ results.push(addr.address);
2014
+ }
2015
+ }
2016
+ }
2017
+ return results;
2018
+ }
1983
2019
  var MdnsService = class {
1984
2020
  bonjour = null;
1985
2021
  service = null;
@@ -2001,7 +2037,11 @@ var MdnsService = class {
2001
2037
  console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
2002
2038
  return;
2003
2039
  }
2004
- this.bonjour = new import_bonjour_service.default();
2040
+ const lanAddrs = getLanAddresses();
2041
+ this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
2042
+ if (lanAddrs.length > 0) {
2043
+ console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
2044
+ }
2005
2045
  this.service = this.bonjour.publish({
2006
2046
  name: "Sessix",
2007
2047
  type: "sessix",
@@ -2037,33 +2077,38 @@ var MdnsService = class {
2037
2077
  updatePairingState(state) {
2038
2078
  this.pairing = state;
2039
2079
  if (!this.bonjour) return;
2040
- if (this.service) {
2041
- this.service.stop?.(() => {
2080
+ const republish = () => {
2081
+ if (!this.bonjour) return;
2082
+ this.service = this.bonjour.publish({
2083
+ name: "Sessix",
2084
+ type: "sessix",
2085
+ port: this.wsPort,
2086
+ txt: {
2087
+ version: this.version,
2088
+ httpPort: String(this.httpPort),
2089
+ wsPort: String(this.wsPort),
2090
+ pairing: state
2091
+ }
2042
2092
  });
2093
+ };
2094
+ if (this.service) {
2095
+ const old = this.service;
2043
2096
  this.service = null;
2097
+ old.stop?.(() => republish());
2098
+ } else {
2099
+ republish();
2044
2100
  }
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
2101
  }
2057
2102
  };
2058
2103
 
2059
2104
  // src/hooks/HookInstaller.ts
2060
2105
  var import_promises2 = require("fs/promises");
2061
2106
  var import_node_path2 = require("path");
2062
- var import_node_os3 = require("os");
2063
- var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".sessix", "hooks");
2107
+ var import_node_os4 = require("os");
2108
+ var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
2064
2109
  var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
2065
2110
  var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
2066
- var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude", "settings.json");
2111
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
2067
2112
  var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
2068
2113
  var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
2069
2114
  var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
@@ -2084,7 +2129,7 @@ PROJECT_PATH="$PWD"
2084
2129
  RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
2085
2130
  -H "Content-Type: application/json" \\
2086
2131
  -d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
2087
- --max-time 330 \\
2132
+ --max-time 320 \\
2088
2133
  2>/dev/null)
2089
2134
 
2090
2135
  if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
@@ -2243,7 +2288,7 @@ var HookInstaller = class {
2243
2288
  * 写入 Claude Code settings.json
2244
2289
  */
2245
2290
  async writeClaudeSettings(settings) {
2246
- await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude"), { recursive: true });
2291
+ await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
2247
2292
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2248
2293
  }
2249
2294
  /**
@@ -3299,7 +3344,7 @@ async function createWithRetry(label, port, factory) {
3299
3344
  }
3300
3345
  }
3301
3346
  async function start(opts = {}) {
3302
- const configDir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".sessix");
3347
+ const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
3303
3348
  const tokenFile = (0, import_node_path4.join)(configDir, "token");
3304
3349
  let token;
3305
3350
  if (opts.token !== void 0) {
@@ -3350,14 +3395,11 @@ async function start(opts = {}) {
3350
3395
  let mdnsService = null;
3351
3396
  const pairingManager = new PairingManager({
3352
3397
  token,
3353
- serverName: (0, import_node_os4.hostname)(),
3398
+ serverName: (0, import_node_os5.hostname)(),
3354
3399
  version: "0.2.0",
3355
3400
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3356
3401
  });
3357
3402
  approvalProxy.setPairingManager(pairingManager);
3358
- if (opts.enablePairing !== false) {
3359
- pairingManager.open();
3360
- }
3361
3403
  const authManager = new AuthManager();
3362
3404
  authManager.on("login_url", (url) => {
3363
3405
  wsBridge.broadcast({ type: "auth_login_url", url });
@@ -3427,6 +3469,7 @@ async function start(opts = {}) {
3427
3469
  }
3428
3470
  case "kill_session": {
3429
3471
  wsBridge.broadcast({ type: "status_change", sessionId: event.sessionId, status: "idle" });
3472
+ approvalProxy.clearPendingForSession(event.sessionId);
3430
3473
  await sessionManager.killSession(event.sessionId);
3431
3474
  wsBridge.broadcast({
3432
3475
  type: "session_list",
@@ -3733,6 +3776,9 @@ async function start(opts = {}) {
3733
3776
  mdnsService.stop();
3734
3777
  mdnsService = null;
3735
3778
  };
3779
+ if (opts.enablePairing !== false) {
3780
+ pairingManager.open();
3781
+ }
3736
3782
  if (opts.enableAutoConnect !== false) {
3737
3783
  startMdns();
3738
3784
  }
@@ -3798,9 +3844,33 @@ async function start(opts = {}) {
3798
3844
 
3799
3845
  // src/index.ts
3800
3846
  var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
3847
+ function getPackageVersion() {
3848
+ try {
3849
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path5.join)(__dirname, "..", "package.json"), "utf8"));
3850
+ return pkg.version ?? "0.0.0";
3851
+ } catch {
3852
+ return "0.0.0";
3853
+ }
3854
+ }
3855
+ var PKG_VERSION = getPackageVersion();
3856
+ async function fetchLatestVersion() {
3857
+ try {
3858
+ const controller = new AbortController();
3859
+ const timer = setTimeout(() => controller.abort(), 5e3);
3860
+ const res = await fetch("https://registry.npmjs.org/sessix-server/latest", {
3861
+ signal: controller.signal
3862
+ });
3863
+ clearTimeout(timer);
3864
+ if (!res.ok) return null;
3865
+ const data = await res.json();
3866
+ return data.version ?? null;
3867
+ } catch {
3868
+ return null;
3869
+ }
3870
+ }
3801
3871
  async function main() {
3802
3872
  console.log("=".repeat(50));
3803
- console.log(t("startup.banner"));
3873
+ console.log(`${t("startup.banner")} v${PKG_VERSION}`);
3804
3874
  console.log("=".repeat(50));
3805
3875
  console.log();
3806
3876
  const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
@@ -3842,6 +3912,13 @@ async function main() {
3842
3912
  console.log();
3843
3913
  console.log(t("startup.pairingOpen"));
3844
3914
  console.log();
3915
+ fetchLatestVersion().then((latest) => {
3916
+ if (!latest || latest === PKG_VERSION) return;
3917
+ console.log();
3918
+ console.log(` \u{1F4E6} ${t("startup.updateAvailable", { current: PKG_VERSION, latest })}`);
3919
+ console.log(` npx sessix-server@latest`);
3920
+ console.log();
3921
+ });
3845
3922
  const shutdown = async (signal) => {
3846
3923
  console.log(`
3847
3924
  [Main] ${t("startup.receivedSignal", { signal })}`);
@@ -3874,7 +3951,7 @@ ${t("startup.pairingReopened")}`);
3874
3951
  }
3875
3952
  }
3876
3953
  function getLocalIp() {
3877
- const interfaces = (0, import_node_os5.networkInterfaces)();
3954
+ const interfaces = (0, import_node_os6.networkInterfaces)();
3878
3955
  for (const iface of Object.values(interfaces)) {
3879
3956
  for (const addr of iface ?? []) {
3880
3957
  if (addr.family === "IPv4" && !addr.internal) {
package/dist/server.js CHANGED
@@ -53,6 +53,7 @@ var zh = {
53
53
  autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
54
54
  pairingOpen: " \u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u5F00\u542F\uFF085 \u5206\u949F\u5185\u6709\u6548\uFF09\u2014 \u6309 p \u91CD\u65B0\u5F00\u542F",
55
55
  pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
56
+ updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
56
57
  receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
57
58
  goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
58
59
  shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
@@ -90,7 +91,8 @@ var zh = {
90
91
  alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
91
92
  started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
92
93
  stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
93
- 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}}"
94
96
  },
95
97
  approval: {
96
98
  httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
@@ -157,6 +159,7 @@ var en = {
157
159
  autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
158
160
  pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
159
161
  pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
162
+ updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
160
163
  receivedSignal: "Received {{signal}}, graceful shutdown...",
161
164
  goodbye: "All services closed, goodbye!",
162
165
  shutdownError: "Shutdown error:",
@@ -194,7 +197,8 @@ var en = {
194
197
  alreadyRunning: "Service is already running",
195
198
  started: "mDNS broadcast started: _sessix._tcp port {{port}}",
196
199
  stopped: "Service broadcast stopped",
197
- closed: "mDNS service closed"
200
+ closed: "mDNS service closed",
201
+ boundInterface: "mDNS bound to LAN interface {{ip}}"
198
202
  },
199
203
  approval: {
200
204
  httpStarted: "HTTP approval server started on port {{port}}",
@@ -291,7 +295,7 @@ function t(key, params) {
291
295
  // src/server.ts
292
296
  var import_uuid4 = require("uuid");
293
297
  var import_promises4 = require("fs/promises");
294
- var import_node_os4 = require("os");
298
+ var import_node_os5 = require("os");
295
299
  var import_node_path4 = require("path");
296
300
  var import_node_child_process2 = require("child_process");
297
301
  var import_node_util = require("util");
@@ -332,8 +336,8 @@ var ProcessProvider = class {
332
336
  activeSessions = /* @__PURE__ */ new Map();
333
337
  /** 事件发射器,用于分发 Claude 事件流 */
334
338
  emitter = new import_events.EventEmitter();
335
- /** 已发射的 AskUserQuestion toolUseId 集合(避免 partial message 重复触发) */
336
- emittedQuestionToolUseIds = /* @__PURE__ */ new Set();
339
+ /** 已发射的 AskUserQuestion toolUseId 集合,按会话隔离(避免 partial message 重复触发) */
340
+ emittedQuestionToolUseIds = /* @__PURE__ */ new Map();
337
341
  /**
338
342
  * 启动新会话或恢复已有会话
339
343
  *
@@ -409,6 +413,7 @@ var ProcessProvider = class {
409
413
  });
410
414
  });
411
415
  }
416
+ this.emittedQuestionToolUseIds.delete(sessionId);
412
417
  this.activeSessions.delete(sessionId);
413
418
  }
414
419
  /**
@@ -605,8 +610,13 @@ var ProcessProvider = class {
605
610
  const question = input.question ?? "";
606
611
  if (!question) continue;
607
612
  const prevKey = `${block.id}:${question}:${JSON.stringify(input.options ?? [])}`;
608
- if (this.emittedQuestionToolUseIds.has(prevKey)) continue;
609
- 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);
610
620
  this.emitter.emit(this.getQuestionEventName(sessionId), {
611
621
  toolUseId: block.id,
612
622
  question,
@@ -1710,6 +1720,17 @@ var ApprovalProxy = class _ApprovalProxy {
1710
1720
  isPending(requestId) {
1711
1721
  return this.pendingApprovals.has(requestId);
1712
1722
  }
1723
+ /** 清理指定会话的所有待处理审批(会话被 kill 时调用,默认放行避免阻塞 hook 脚本) */
1724
+ clearPendingForSession(sessionId) {
1725
+ for (const [requestId, pending] of this.pendingApprovals) {
1726
+ if (pending.request.sessionId === sessionId) {
1727
+ clearTimeout(pending.timer);
1728
+ pending.resolve({ decision: "allow" });
1729
+ this.pendingApprovals.delete(requestId);
1730
+ console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
1731
+ }
1732
+ }
1733
+ }
1713
1734
  /** 检查工具是否已被"始终允许"(内存缓存 + settings.json 双重检查) */
1714
1735
  isToolAlwaysAllowed(toolName, projectPath) {
1715
1736
  if (this.alwaysAllowedTools.has(toolName)) return true;
@@ -1988,6 +2009,19 @@ var ApprovalProxy = class _ApprovalProxy {
1988
2009
 
1989
2010
  // src/mdns/MdnsService.ts
1990
2011
  var import_bonjour_service = __toESM(require("bonjour-service"));
2012
+ var import_node_os3 = require("os");
2013
+ function getLanAddresses() {
2014
+ const results = [];
2015
+ for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
2016
+ if (name.startsWith("utun") || name.startsWith("lo")) continue;
2017
+ for (const addr of addrs ?? []) {
2018
+ if (addr.family === "IPv4" && !addr.internal) {
2019
+ results.push(addr.address);
2020
+ }
2021
+ }
2022
+ }
2023
+ return results;
2024
+ }
1991
2025
  var MdnsService = class {
1992
2026
  bonjour = null;
1993
2027
  service = null;
@@ -2009,7 +2043,11 @@ var MdnsService = class {
2009
2043
  console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
2010
2044
  return;
2011
2045
  }
2012
- this.bonjour = new import_bonjour_service.default();
2046
+ const lanAddrs = getLanAddresses();
2047
+ this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
2048
+ if (lanAddrs.length > 0) {
2049
+ console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
2050
+ }
2013
2051
  this.service = this.bonjour.publish({
2014
2052
  name: "Sessix",
2015
2053
  type: "sessix",
@@ -2045,33 +2083,38 @@ var MdnsService = class {
2045
2083
  updatePairingState(state) {
2046
2084
  this.pairing = state;
2047
2085
  if (!this.bonjour) return;
2048
- if (this.service) {
2049
- this.service.stop?.(() => {
2086
+ const republish = () => {
2087
+ if (!this.bonjour) return;
2088
+ this.service = this.bonjour.publish({
2089
+ name: "Sessix",
2090
+ type: "sessix",
2091
+ port: this.wsPort,
2092
+ txt: {
2093
+ version: this.version,
2094
+ httpPort: String(this.httpPort),
2095
+ wsPort: String(this.wsPort),
2096
+ pairing: state
2097
+ }
2050
2098
  });
2099
+ };
2100
+ if (this.service) {
2101
+ const old = this.service;
2051
2102
  this.service = null;
2103
+ old.stop?.(() => republish());
2104
+ } else {
2105
+ republish();
2052
2106
  }
2053
- this.service = this.bonjour.publish({
2054
- name: "Sessix",
2055
- type: "sessix",
2056
- port: this.wsPort,
2057
- txt: {
2058
- version: this.version,
2059
- httpPort: String(this.httpPort),
2060
- wsPort: String(this.wsPort),
2061
- pairing: state
2062
- }
2063
- });
2064
2107
  }
2065
2108
  };
2066
2109
 
2067
2110
  // src/hooks/HookInstaller.ts
2068
2111
  var import_promises2 = require("fs/promises");
2069
2112
  var import_node_path2 = require("path");
2070
- var import_node_os3 = require("os");
2071
- var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".sessix", "hooks");
2113
+ var import_node_os4 = require("os");
2114
+ var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
2072
2115
  var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
2073
2116
  var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
2074
- var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude", "settings.json");
2117
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
2075
2118
  var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
2076
2119
  var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
2077
2120
  var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
@@ -2092,7 +2135,7 @@ PROJECT_PATH="$PWD"
2092
2135
  RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
2093
2136
  -H "Content-Type: application/json" \\
2094
2137
  -d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
2095
- --max-time 330 \\
2138
+ --max-time 320 \\
2096
2139
  2>/dev/null)
2097
2140
 
2098
2141
  if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
@@ -2251,7 +2294,7 @@ var HookInstaller = class {
2251
2294
  * 写入 Claude Code settings.json
2252
2295
  */
2253
2296
  async writeClaudeSettings(settings) {
2254
- await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os3.homedir)(), ".claude"), { recursive: true });
2297
+ await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
2255
2298
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2256
2299
  }
2257
2300
  /**
@@ -3307,7 +3350,7 @@ async function createWithRetry(label, port, factory) {
3307
3350
  }
3308
3351
  }
3309
3352
  async function start(opts = {}) {
3310
- const configDir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".sessix");
3353
+ const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
3311
3354
  const tokenFile = (0, import_node_path4.join)(configDir, "token");
3312
3355
  let token;
3313
3356
  if (opts.token !== void 0) {
@@ -3358,14 +3401,11 @@ async function start(opts = {}) {
3358
3401
  let mdnsService = null;
3359
3402
  const pairingManager = new PairingManager({
3360
3403
  token,
3361
- serverName: (0, import_node_os4.hostname)(),
3404
+ serverName: (0, import_node_os5.hostname)(),
3362
3405
  version: "0.2.0",
3363
3406
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3364
3407
  });
3365
3408
  approvalProxy.setPairingManager(pairingManager);
3366
- if (opts.enablePairing !== false) {
3367
- pairingManager.open();
3368
- }
3369
3409
  const authManager = new AuthManager();
3370
3410
  authManager.on("login_url", (url) => {
3371
3411
  wsBridge.broadcast({ type: "auth_login_url", url });
@@ -3435,6 +3475,7 @@ async function start(opts = {}) {
3435
3475
  }
3436
3476
  case "kill_session": {
3437
3477
  wsBridge.broadcast({ type: "status_change", sessionId: event.sessionId, status: "idle" });
3478
+ approvalProxy.clearPendingForSession(event.sessionId);
3438
3479
  await sessionManager.killSession(event.sessionId);
3439
3480
  wsBridge.broadcast({
3440
3481
  type: "session_list",
@@ -3741,6 +3782,9 @@ async function start(opts = {}) {
3741
3782
  mdnsService.stop();
3742
3783
  mdnsService = null;
3743
3784
  };
3785
+ if (opts.enablePairing !== false) {
3786
+ pairingManager.open();
3787
+ }
3744
3788
  if (opts.enableAutoConnect !== false) {
3745
3789
  startMdns();
3746
3790
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sessix-server",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "bin": {
5
5
  "sessix-server": "./dist/index.js"
6
6
  },
@@ -8,9 +8,9 @@
8
8
  "types": "./dist/server.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
+ "types": "./dist/server.d.ts",
11
12
  "import": "./dist/server.js",
12
- "require": "./dist/server.js",
13
- "types": "./dist/server.d.ts"
13
+ "require": "./dist/server.js"
14
14
  },
15
15
  "./cli": "./dist/index.js"
16
16
  },