sessix-server 0.2.4 → 0.2.6

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 (34) 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 +205 -145
  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.map +1 -1
  24. package/dist/server.js +198 -139
  25. package/dist/server.js.map +1 -1
  26. package/dist/session/ProjectReader.js +1 -1
  27. package/dist/session/ProjectReader.js.map +1 -1
  28. package/dist/session/SessionManager.d.ts +3 -7
  29. package/dist/session/SessionManager.d.ts.map +1 -1
  30. package/dist/session/SessionManager.js +5 -45
  31. package/dist/session/SessionManager.js.map +1 -1
  32. package/dist/ws/WsBridge.js +4 -4
  33. package/dist/ws/WsBridge.js.map +1 -1
  34. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24,9 +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_os6 = require("os");
28
- var import_node_fs2 = require("fs");
29
- var import_node_path5 = require("path");
27
+ var import_node_os7 = require("os");
28
+ var import_node_fs3 = require("fs");
29
+ var import_node_path6 = require("path");
30
30
 
31
31
  // src/i18n/locales/zh.ts
32
32
  var zh = {
@@ -289,38 +289,87 @@ function t(key, params) {
289
289
  // src/server.ts
290
290
  var import_uuid4 = require("uuid");
291
291
  var import_promises4 = require("fs/promises");
292
- var import_node_os5 = require("os");
293
- var import_node_path4 = require("path");
294
- var import_node_child_process2 = require("child_process");
292
+ var import_node_os6 = require("os");
293
+ var import_node_path5 = require("path");
294
+ var import_node_child_process4 = require("child_process");
295
295
  var import_node_util = require("util");
296
296
 
297
297
  // src/providers/ProcessProvider.ts
298
- var import_child_process2 = require("child_process");
298
+ var import_child_process = require("child_process");
299
299
  var import_readline = require("readline");
300
300
  var import_events = require("events");
301
- var import_node_os = require("os");
301
+ var import_node_os2 = require("os");
302
302
  var import_uuid = require("uuid");
303
303
 
304
304
  // src/utils/claudePath.ts
305
- var import_child_process = require("child_process");
305
+ var import_node_child_process2 = require("child_process");
306
+ var import_node_fs = require("fs");
307
+ var import_node_path = require("path");
308
+ var import_node_os = require("os");
309
+
310
+ // src/utils/platform.ts
311
+ var import_node_child_process = require("child_process");
312
+ var isWindows = process.platform === "win32";
313
+ function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
314
+ return new Promise((resolve) => {
315
+ if (proc.exitCode !== null || proc.signalCode !== null) {
316
+ resolve();
317
+ return;
318
+ }
319
+ const onExit = () => {
320
+ clearTimeout(timer);
321
+ resolve();
322
+ };
323
+ proc.once("exit", onExit);
324
+ if (isWindows) {
325
+ if (proc.pid) {
326
+ (0, import_node_child_process.spawn)("taskkill", ["/PID", String(proc.pid), "/T", "/F"], { stdio: "ignore" });
327
+ }
328
+ } else {
329
+ proc.kill("SIGTERM");
330
+ }
331
+ const timer = setTimeout(() => {
332
+ if (proc.exitCode === null && proc.signalCode === null) {
333
+ if (!isWindows) {
334
+ proc.kill("SIGKILL");
335
+ }
336
+ }
337
+ resolve();
338
+ }, timeoutMs);
339
+ });
340
+ }
341
+ function isNormalExit(code, signal) {
342
+ if (code === 0) return true;
343
+ if (isWindows) {
344
+ return code === 1;
345
+ }
346
+ return code === 143 || signal === "SIGTERM";
347
+ }
348
+
349
+ // src/utils/claudePath.ts
306
350
  function findClaudePath() {
307
351
  try {
308
- return (0, import_child_process.execSync)("which claude", { encoding: "utf-8" }).trim();
352
+ const cmd = isWindows ? "where claude" : "which claude";
353
+ return (0, import_node_child_process2.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
309
354
  } catch {
310
- const candidates = [
311
- `${process.env.HOME}/.local/bin/claude`,
312
- "/usr/local/bin/claude",
313
- "/opt/homebrew/bin/claude"
314
- ];
315
- for (const candidate of candidates) {
316
- try {
317
- (0, import_child_process.execSync)(`test -x "${candidate}"`);
318
- return candidate;
319
- } catch {
320
- }
355
+ }
356
+ const candidates = isWindows ? [
357
+ (0, import_node_path.join)(process.env.LOCALAPPDATA ?? "", "Programs", "claude", "claude.exe"),
358
+ (0, import_node_path.join)((0, import_node_os.homedir)(), "AppData", "Local", "Programs", "claude", "claude.exe"),
359
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "local", "claude.exe")
360
+ ] : [
361
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "bin", "claude"),
362
+ "/usr/local/bin/claude",
363
+ "/opt/homebrew/bin/claude"
364
+ ];
365
+ for (const candidate of candidates) {
366
+ try {
367
+ (0, import_node_fs.accessSync)(candidate, import_node_fs.constants.X_OK);
368
+ return candidate;
369
+ } catch {
321
370
  }
322
- return "claude";
323
371
  }
372
+ return "claude";
324
373
  }
325
374
 
326
375
  // src/providers/ProcessProvider.ts
@@ -393,19 +442,7 @@ var ProcessProvider = class {
393
442
  entry.process.stdin?.end();
394
443
  } catch {
395
444
  }
396
- entry.process.kill("SIGTERM");
397
- await new Promise((resolve) => {
398
- const timeout = setTimeout(() => {
399
- if (entry.process.exitCode === null && entry.process.signalCode === null) {
400
- entry.process.kill("SIGKILL");
401
- }
402
- resolve();
403
- }, 3e3);
404
- entry.process.once("exit", () => {
405
- clearTimeout(timeout);
406
- resolve();
407
- });
408
- });
445
+ await killProcessCrossPlatform(entry.process);
409
446
  }
410
447
  this.emittedQuestionToolUseIds.delete(sessionId);
411
448
  this.activeSessions.delete(sessionId);
@@ -435,7 +472,7 @@ var ProcessProvider = class {
435
472
  entry.process.stdin?.end();
436
473
  } catch {
437
474
  }
438
- entry.process.kill("SIGTERM");
475
+ killProcessCrossPlatform(entry.process);
439
476
  }
440
477
  } else {
441
478
  console.log(`[ProcessProvider] Session ${sessionId}: process exited, respawning`);
@@ -517,7 +554,7 @@ var ProcessProvider = class {
517
554
  }
518
555
  const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
519
556
  delete env.CLAUDECODE;
520
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, args, {
557
+ const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
521
558
  cwd: projectPath,
522
559
  env,
523
560
  stdio: ["pipe", "pipe", "pipe"]
@@ -663,7 +700,7 @@ var ProcessProvider = class {
663
700
  entry.session.lastActiveAt = Date.now();
664
701
  const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
665
702
  if (alreadyHasResult) return;
666
- const isNormal = code === 0 || code === 143 || signal === "SIGTERM";
703
+ const isNormal = isNormalExit(code, signal);
667
704
  entry.session.status = isNormal ? "idle" : "error";
668
705
  if (!isNormal) {
669
706
  console.error(
@@ -729,8 +766,8 @@ ${context}`;
729
766
  return new Promise((resolve, reject) => {
730
767
  const env = { ...process.env };
731
768
  delete env.CLAUDECODE;
732
- const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
733
- cwd: (0, import_node_os.homedir)(),
769
+ const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
770
+ cwd: (0, import_node_os2.homedir)(),
734
771
  env,
735
772
  stdio: ["pipe", "pipe", "pipe"]
736
773
  });
@@ -1622,15 +1659,15 @@ var WsBridge = class _WsBridge {
1622
1659
 
1623
1660
  // src/approval/ApprovalProxy.ts
1624
1661
  var import_node_http = __toESM(require("http"));
1625
- var import_node_fs = __toESM(require("fs"));
1626
- var import_node_path = __toESM(require("path"));
1627
- var import_node_os2 = __toESM(require("os"));
1662
+ var import_node_fs2 = __toESM(require("fs"));
1663
+ var import_node_path2 = __toESM(require("path"));
1664
+ var import_node_os3 = __toESM(require("os"));
1628
1665
  var import_uuid3 = require("uuid");
1629
1666
  var ApprovalProxy = class _ApprovalProxy {
1630
1667
  server;
1631
1668
  token;
1632
1669
  port;
1633
- settingsPath = import_node_path.default.join(import_node_os2.default.homedir(), ".claude", "settings.json");
1670
+ settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
1634
1671
  /** 待处理的审批请求:requestId -> { resolve, timer, request } */
1635
1672
  pendingApprovals = /* @__PURE__ */ new Map();
1636
1673
  /** 审批请求回调(通知外部推送到手机) */
@@ -1735,7 +1772,7 @@ var ApprovalProxy = class _ApprovalProxy {
1735
1772
  isToolInClaudeSettings(toolName, projectPath) {
1736
1773
  const checkPath = (filepath) => {
1737
1774
  try {
1738
- const raw = import_node_fs.default.readFileSync(filepath, "utf-8");
1775
+ const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
1739
1776
  const settings = JSON.parse(raw);
1740
1777
  const allow = settings?.permissions?.allow ?? [];
1741
1778
  return allow.some((entry) => {
@@ -1749,24 +1786,24 @@ var ApprovalProxy = class _ApprovalProxy {
1749
1786
  }
1750
1787
  };
1751
1788
  if (projectPath) {
1752
- const projectSettingsPath = import_node_path.default.join(projectPath, ".claude", "settings.json");
1789
+ const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
1753
1790
  if (checkPath(projectSettingsPath)) return true;
1754
1791
  }
1755
1792
  return checkPath(this.settingsPath);
1756
1793
  }
1757
1794
  /** 将工具写入 settings.json permissions.allow(项目级或全局) */
1758
1795
  addToClaudeSettings(projectPath, toolName) {
1759
- const targetPath = projectPath ? import_node_path.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1796
+ const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
1760
1797
  try {
1761
1798
  if (projectPath) {
1762
- const dir = import_node_path.default.dirname(targetPath);
1763
- if (!import_node_fs.default.existsSync(dir)) {
1764
- import_node_fs.default.mkdirSync(dir, { recursive: true });
1799
+ const dir = import_node_path2.default.dirname(targetPath);
1800
+ if (!import_node_fs2.default.existsSync(dir)) {
1801
+ import_node_fs2.default.mkdirSync(dir, { recursive: true });
1765
1802
  }
1766
1803
  }
1767
1804
  let settings = {};
1768
1805
  try {
1769
- settings = JSON.parse(import_node_fs.default.readFileSync(targetPath, "utf-8"));
1806
+ settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
1770
1807
  } catch {
1771
1808
  }
1772
1809
  if (!settings.permissions) {
@@ -1780,7 +1817,7 @@ var ApprovalProxy = class _ApprovalProxy {
1780
1817
  const entry = `${toolName}(*)`;
1781
1818
  if (!allow.includes(entry)) {
1782
1819
  allow.push(entry);
1783
- import_node_fs.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1820
+ import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
1784
1821
  const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
1785
1822
  console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
1786
1823
  }
@@ -2004,11 +2041,12 @@ var ApprovalProxy = class _ApprovalProxy {
2004
2041
 
2005
2042
  // src/mdns/MdnsService.ts
2006
2043
  var import_bonjour_service = __toESM(require("bonjour-service"));
2007
- var import_node_os3 = require("os");
2044
+ var import_node_os4 = require("os");
2008
2045
  function getLanAddresses() {
2009
2046
  const results = [];
2010
- for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
2011
- if (name.startsWith("utun") || name.startsWith("lo")) continue;
2047
+ for (const [name, addrs] of Object.entries((0, import_node_os4.networkInterfaces)())) {
2048
+ if (name.startsWith("utun") || name === "lo") continue;
2049
+ if (isWindows && (name.startsWith("vEthernet") || name.includes("Loopback"))) continue;
2012
2050
  for (const addr of addrs ?? []) {
2013
2051
  if (addr.family === "IPv4" && !addr.internal) {
2014
2052
  results.push(addr.address);
@@ -2039,7 +2077,13 @@ var MdnsService = class {
2039
2077
  return;
2040
2078
  }
2041
2079
  const lanAddrs = getLanAddresses();
2042
- this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
2080
+ const onMdnsError = (err) => {
2081
+ console.warn(`[MdnsService] mDNS error (non-fatal): ${err.message}`);
2082
+ this.stop();
2083
+ };
2084
+ const opts = lanAddrs.length > 0 ? { interface: lanAddrs[0] } : {};
2085
+ this.bonjour = new import_bonjour_service.default(opts, onMdnsError);
2086
+ this.bonjour.server?.mdns?.on("error", onMdnsError);
2043
2087
  if (lanAddrs.length > 0) {
2044
2088
  console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
2045
2089
  }
@@ -2104,72 +2148,62 @@ var MdnsService = class {
2104
2148
 
2105
2149
  // src/hooks/HookInstaller.ts
2106
2150
  var import_promises2 = require("fs/promises");
2107
- var import_node_path2 = require("path");
2108
- var import_node_os4 = require("os");
2109
- var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
2110
- var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
2111
- var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
2112
- var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
2113
- var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
2114
- var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
2115
- var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
2116
- # Sessix Approval Hook
2117
- # \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
2118
-
2119
- if [ -z "$SESSIX_SESSION_ID" ]; then
2120
- exit 0
2121
- fi
2122
-
2123
- # \u4ECE stdin \u8BFB\u53D6 hook payload
2124
- PAYLOAD=$(cat)
2125
-
2126
- # \u83B7\u53D6\u9879\u76EE\u8DEF\u5F84\uFF08\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09
2127
- PROJECT_PATH="$PWD"
2128
-
2129
- # \u53D1\u9001\u5BA1\u6279\u8BF7\u6C42\u5230 Sessix \u670D\u52A1\u5668\uFF08\u957F\u8F6E\u8BE2\uFF0C\u8D85\u65F6\u65F6\u95F4 > 300s\uFF09
2130
- RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
2131
- -H "Content-Type: application/json" \\
2132
- -d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
2133
- --max-time 320 \\
2134
- 2>/dev/null)
2135
-
2136
- if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
2137
- # \u5982\u679C Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C\uFF08exit 0 = \u6279\u51C6\uFF09
2138
- exit 0
2139
- fi
2151
+ var import_node_path3 = require("path");
2152
+ var import_node_os5 = require("os");
2153
+ var SESSIX_HOOKS_DIR = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".sessix", "hooks");
2154
+ var HOOK_SCRIPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
2155
+ var PERMISSION_ACCEPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
2156
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude", "settings.json");
2157
+ var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
2158
+ var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
2159
+ var LEGACY_HOOK_COMMANDS = [
2160
+ "~/.sessix/hooks/approval-hook.sh",
2161
+ "~/.sessix/hooks/permission-accept.sh"
2162
+ ];
2163
+ var HOOK_SCRIPT_TEMPLATE = `#!/usr/bin/env node
2164
+ // Sessix Approval Hook
2165
+ // \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
2140
2166
 
2141
- # \u89E3\u6790\u670D\u52A1\u5668\u54CD\u5E94
2142
- DECISION=$(echo "$RESPONSE" | grep -o '\\"decision\\":\\"[^"]*\\"' | cut -d'"' -f4)
2167
+ const sessionId = process.env.SESSIX_SESSION_ID
2168
+ if (!sessionId) process.exit(0)
2143
2169
 
2144
- if [ "$DECISION" = "allow" ]; then
2145
- # \u7528\u6237\u6279\u51C6\u6216\u670D\u52A1\u5668\u8D85\u65F6\u81EA\u52A8\u6279\u51C6
2146
- exit 0
2147
- elif [ "$DECISION" = "deny" ]; then
2148
- # \u7528\u6237\u660E\u786E\u62D2\u7EDD
2149
- exit 1
2150
- else
2151
- # \u672A\u77E5\u54CD\u5E94\uFF0C\u9ED8\u8BA4\u653E\u884C
2152
- exit 0
2153
- fi
2170
+ let payload = ''
2171
+ process.stdin.on('data', (chunk) => { payload += chunk })
2172
+ process.stdin.on('end', async () => {
2173
+ try {
2174
+ const res = await fetch('http://localhost:3746/hook/approval', {
2175
+ method: 'POST',
2176
+ headers: { 'Content-Type': 'application/json' },
2177
+ body: JSON.stringify({
2178
+ sessionId,
2179
+ projectPath: process.cwd(),
2180
+ payload: JSON.parse(payload),
2181
+ }),
2182
+ signal: AbortSignal.timeout(320000),
2183
+ })
2184
+ const data = await res.json()
2185
+ process.exit(data.decision === 'deny' ? 1 : 0)
2186
+ } catch {
2187
+ // Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C
2188
+ process.exit(0)
2189
+ }
2190
+ })
2154
2191
  `;
2155
- var PERMISSION_ACCEPT_TEMPLATE = `#!/bin/bash
2156
- # Sessix PermissionRequest \u515C\u5E95
2157
- # \u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42\uFF0C\u907F\u514D Sessix \u4F1A\u8BDD\u963B\u585E
2192
+ var PERMISSION_ACCEPT_TEMPLATE = `#!/usr/bin/env node
2193
+ // Sessix PermissionRequest \u515C\u5E95
2194
+ // \u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42\uFF0C\u907F\u514D Sessix \u4F1A\u8BDD\u963B\u585E
2158
2195
 
2159
- if [ -z "$SESSIX_SESSION_ID" ]; then
2160
- exit 0
2161
- fi
2196
+ if (!process.env.SESSIX_SESSION_ID) process.exit(0)
2162
2197
 
2163
- # \u8F93\u51FA JSON \u51B3\u7B56\uFF0C\u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42
2164
- echo '{"decision":"allow"}'
2165
- exit 0
2198
+ process.stdout.write('{"decision":"allow"}\\n')
2199
+ process.exit(0)
2166
2200
  `;
2167
2201
  var HookInstaller = class {
2168
2202
  /**
2169
2203
  * 安装 hook
2170
2204
  *
2171
2205
  * 1. 创建 ~/.sessix/hooks/ 目录
2172
- * 2. 写入 approval-hook.sh 脚本
2206
+ * 2. 写入 approval-hook.js 脚本
2173
2207
  * 3. 赋予执行权限
2174
2208
  * 4. 更新 Claude Code settings.json 添加 hook 配置
2175
2209
  */
@@ -2222,6 +2256,10 @@ var HookInstaller = class {
2222
2256
  async addHookToSettings() {
2223
2257
  let settings = await this.readClaudeSettings();
2224
2258
  let changed = false;
2259
+ for (const cmd of LEGACY_HOOK_COMMANDS) {
2260
+ this.removeHookCommand(settings, "PreToolUse", cmd);
2261
+ this.removeHookCommand(settings, "PermissionRequest", cmd);
2262
+ }
2225
2263
  if (!settings.hooks) {
2226
2264
  settings.hooks = {};
2227
2265
  }
@@ -2289,7 +2327,7 @@ var HookInstaller = class {
2289
2327
  * 写入 Claude Code settings.json
2290
2328
  */
2291
2329
  async writeClaudeSettings(settings) {
2292
- await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
2330
+ await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
2293
2331
  await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2294
2332
  }
2295
2333
  /**
@@ -2298,11 +2336,11 @@ var HookInstaller = class {
2298
2336
  hasHookConfig(settings) {
2299
2337
  return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
2300
2338
  }
2301
- /** 检查 PreToolUse 中是否有 approval-hook.sh */
2339
+ /** 检查 PreToolUse 中是否有 approval-hook.js */
2302
2340
  hasPreToolUseConfig(settings) {
2303
2341
  return this.hasHookEntry(settings?.hooks?.PreToolUse, HOOK_COMMAND);
2304
2342
  }
2305
- /** 检查 PermissionRequest 中是否有 permission-accept.sh */
2343
+ /** 检查 PermissionRequest 中是否有 permission-accept.js */
2306
2344
  hasPermissionRequestConfig(settings) {
2307
2345
  return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
2308
2346
  }
@@ -2316,7 +2354,7 @@ var HookInstaller = class {
2316
2354
  };
2317
2355
 
2318
2356
  // src/notification/NotificationService.ts
2319
- var import_node_path3 = require("path");
2357
+ var import_node_path4 = require("path");
2320
2358
  var NotificationService = class {
2321
2359
  constructor(sessionManager, expoChannel = null) {
2322
2360
  this.sessionManager = sessionManager;
@@ -2412,7 +2450,7 @@ var NotificationService = class {
2412
2450
  const dangerLevel = this.getDangerLevel(request.toolName);
2413
2451
  const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
2414
2452
  const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
2415
- const projectName = (0, import_node_path3.basename)(
2453
+ const projectName = (0, import_node_path4.basename)(
2416
2454
  this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
2417
2455
  );
2418
2456
  const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
@@ -2468,7 +2506,7 @@ var NotificationService = class {
2468
2506
  /** 从审批请求中提取操作目标的简短描述 */
2469
2507
  extractTarget(request) {
2470
2508
  const input = request.toolInput;
2471
- if (input.file_path) return (0, import_node_path3.basename)(String(input.file_path));
2509
+ if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
2472
2510
  if (input.command) return String(input.command).slice(0, 40);
2473
2511
  return request.description.slice(0, 40);
2474
2512
  }
@@ -2571,7 +2609,7 @@ var NotificationService = class {
2571
2609
  getSessionTitle(sessionId) {
2572
2610
  const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
2573
2611
  if (!session) return "Unknown";
2574
- return session.summary ?? (0, import_node_path3.basename)(session.projectPath);
2612
+ return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
2575
2613
  }
2576
2614
  /** 获取会话的 YOLO 模式状态 */
2577
2615
  getYoloMode(sessionId) {
@@ -2579,9 +2617,9 @@ var NotificationService = class {
2579
2617
  }
2580
2618
  };
2581
2619
 
2582
- // src/notification/MacNotificationChannel.ts
2583
- var import_node_child_process = require("child_process");
2584
- var MacNotificationChannel = class {
2620
+ // src/notification/DesktopNotificationChannel.ts
2621
+ var import_node_child_process3 = require("child_process");
2622
+ var DesktopNotificationChannel = class {
2585
2623
  isAvailable() {
2586
2624
  return process.platform === "darwin";
2587
2625
  }
@@ -2592,9 +2630,9 @@ var MacNotificationChannel = class {
2592
2630
  const sound = payload.sound ?? "Ping";
2593
2631
  const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
2594
2632
  return new Promise((resolve) => {
2595
- (0, import_node_child_process.execFile)("osascript", ["-e", script], (err) => {
2633
+ (0, import_node_child_process3.execFile)("osascript", ["-e", script], (err) => {
2596
2634
  if (err) {
2597
- console.warn("[MacNotificationChannel] Send notification failed:", err.message);
2635
+ console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
2598
2636
  }
2599
2637
  resolve();
2600
2638
  });
@@ -3220,11 +3258,11 @@ var PairingManager = class {
3220
3258
  };
3221
3259
 
3222
3260
  // src/auth/AuthManager.ts
3261
+ var import_child_process2 = require("child_process");
3223
3262
  var import_child_process3 = require("child_process");
3224
- var import_child_process4 = require("child_process");
3225
3263
  var import_util = require("util");
3226
3264
  var import_events2 = require("events");
3227
- var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
3265
+ var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
3228
3266
  var CLAUDE_PATH2 = findClaudePath();
3229
3267
  var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
3230
3268
  var AuthManager = class extends import_events2.EventEmitter {
@@ -3255,7 +3293,7 @@ var AuthManager = class extends import_events2.EventEmitter {
3255
3293
  }
3256
3294
  this.clearLoginTimeout();
3257
3295
  this.urlSent = false;
3258
- const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3296
+ const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
3259
3297
  env: { ...process.env, BROWSER: "echo" },
3260
3298
  stdio: ["pipe", "pipe", "pipe"]
3261
3299
  });
@@ -3341,15 +3379,31 @@ var AuthManager = class extends import_events2.EventEmitter {
3341
3379
  var import_promises5 = require("fs/promises");
3342
3380
  var WS_PORT = 3745;
3343
3381
  var HTTP_PORT = 3746;
3344
- var execAsync = (0, import_node_util.promisify)(import_node_child_process2.exec);
3382
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process4.exec);
3345
3383
  async function killPortProcess(port) {
3346
3384
  try {
3347
- const { stdout } = await execAsync(`lsof -ti :${port}`);
3348
- const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
3349
- if (pids.length > 0) {
3350
- await execAsync(`kill -9 ${pids.join(" ")}`);
3351
- await new Promise((resolve) => setTimeout(resolve, 600));
3385
+ if (isWindows) {
3386
+ const { stdout } = await execAsync(
3387
+ `netstat -ano | findstr :${port} | findstr LISTENING`
3388
+ );
3389
+ const pids = /* @__PURE__ */ new Set();
3390
+ for (const line of stdout.trim().split("\n")) {
3391
+ const parts = line.trim().split(/\s+/);
3392
+ const pid = parts[parts.length - 1];
3393
+ if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
3394
+ }
3395
+ for (const pid of pids) {
3396
+ await execAsync(`taskkill /PID ${pid} /F`).catch(() => {
3397
+ });
3398
+ }
3399
+ } else {
3400
+ const { stdout } = await execAsync(`lsof -ti :${port}`);
3401
+ const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
3402
+ if (pids.length > 0) {
3403
+ await execAsync(`kill -9 ${pids.join(" ")}`);
3404
+ }
3352
3405
  }
3406
+ await new Promise((resolve) => setTimeout(resolve, 600));
3353
3407
  } catch {
3354
3408
  }
3355
3409
  }
@@ -3367,8 +3421,8 @@ async function createWithRetry(label, port, factory) {
3367
3421
  }
3368
3422
  }
3369
3423
  async function start(opts = {}) {
3370
- const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
3371
- const tokenFile = (0, import_node_path4.join)(configDir, "token");
3424
+ const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
3425
+ const tokenFile = (0, import_node_path5.join)(configDir, "token");
3372
3426
  let token;
3373
3427
  if (opts.token !== void 0) {
3374
3428
  token = opts.token;
@@ -3391,7 +3445,7 @@ async function start(opts = {}) {
3391
3445
  const expoChannel = new ExpoNotificationChannel();
3392
3446
  const notificationService = new NotificationService(sessionManager, expoChannel);
3393
3447
  notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
3394
- notificationService.addChannel("mac", new MacNotificationChannel(), opts.enableMacNotification !== false);
3448
+ notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
3395
3449
  if (opts.activityPush) {
3396
3450
  try {
3397
3451
  const activityChannel = new ActivityPushChannel(opts.activityPush);
@@ -3418,7 +3472,7 @@ async function start(opts = {}) {
3418
3472
  let mdnsService = null;
3419
3473
  const pairingManager = new PairingManager({
3420
3474
  token,
3421
- serverName: (0, import_node_os5.hostname)(),
3475
+ serverName: (0, import_node_os6.hostname)(),
3422
3476
  version: "0.2.0",
3423
3477
  onStateChange: (state) => mdnsService?.updatePairingState(state)
3424
3478
  });
@@ -3788,12 +3842,17 @@ async function start(opts = {}) {
3788
3842
  }));
3789
3843
  const startMdns = () => {
3790
3844
  if (mdnsService) return;
3791
- mdnsService = new MdnsService({
3792
- wsPort: WS_PORT,
3793
- httpPort: HTTP_PORT,
3794
- pairing: pairingManager.state
3795
- });
3796
- mdnsService.start();
3845
+ try {
3846
+ mdnsService = new MdnsService({
3847
+ wsPort: WS_PORT,
3848
+ httpPort: HTTP_PORT,
3849
+ pairing: pairingManager.state
3850
+ });
3851
+ mdnsService.start();
3852
+ } catch (err) {
3853
+ console.warn(`[Server] mDNS failed to start (non-fatal): ${err.message}`);
3854
+ mdnsService = null;
3855
+ }
3797
3856
  };
3798
3857
  const stopMdns = () => {
3799
3858
  if (!mdnsService) return;
@@ -3870,7 +3929,7 @@ async function start(opts = {}) {
3870
3929
  var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
3871
3930
  function getPackageVersion() {
3872
3931
  try {
3873
- const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path5.join)(__dirname, "..", "package.json"), "utf8"));
3932
+ const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf8"));
3874
3933
  return pkg.version ?? "0.0.0";
3875
3934
  } catch {
3876
3935
  return "0.0.0";
@@ -3972,10 +4031,11 @@ ${t("startup.pairingReopened")}`);
3972
4031
  } else {
3973
4032
  process.on("SIGINT", () => shutdown("SIGINT"));
3974
4033
  process.on("SIGTERM", () => shutdown("SIGTERM"));
4034
+ process.on("SIGHUP", () => shutdown("SIGHUP"));
3975
4035
  }
3976
4036
  }
3977
4037
  function getLocalIp() {
3978
- const interfaces = (0, import_node_os6.networkInterfaces)();
4038
+ const interfaces = (0, import_node_os7.networkInterfaces)();
3979
4039
  for (const iface of Object.values(interfaces)) {
3980
4040
  for (const addr of iface ?? []) {
3981
4041
  if (addr.family === "IPv4" && !addr.internal) {
@@ -3983,7 +4043,7 @@ function getLocalIp() {
3983
4043
  }
3984
4044
  }
3985
4045
  }
3986
- return "<your-mac-ip>";
4046
+ return "<your-ip>";
3987
4047
  }
3988
4048
  function buildQrUrl(ip, wsPort, token) {
3989
4049
  const base = `sessix://${ip}:${wsPort}`;
@@ -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"}