sessix-server 0.2.5 → 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.
- package/dist/index.js +198 -144
- package/dist/server.js +191 -138
- 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
|
|
28
|
-
var
|
|
29
|
-
var
|
|
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
|
|
293
|
-
var
|
|
294
|
-
var
|
|
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
|
|
298
|
+
var import_child_process = require("child_process");
|
|
299
299
|
var import_readline = require("readline");
|
|
300
300
|
var import_events = require("events");
|
|
301
|
-
var
|
|
301
|
+
var import_node_os2 = require("os");
|
|
302
302
|
var import_uuid = require("uuid");
|
|
303
303
|
|
|
304
304
|
// src/utils/claudePath.ts
|
|
305
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
733
|
-
cwd: (0,
|
|
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
|
|
1626
|
-
var
|
|
1627
|
-
var
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
1763
|
-
if (!
|
|
1764
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
2044
|
+
var import_node_os4 = require("os");
|
|
2008
2045
|
function getLanAddresses() {
|
|
2009
2046
|
const results = [];
|
|
2010
|
-
for (const [name, addrs] of Object.entries((0,
|
|
2011
|
-
if (name.startsWith("utun") || name
|
|
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);
|
|
@@ -2110,72 +2148,62 @@ var MdnsService = class {
|
|
|
2110
2148
|
|
|
2111
2149
|
// src/hooks/HookInstaller.ts
|
|
2112
2150
|
var import_promises2 = require("fs/promises");
|
|
2113
|
-
var
|
|
2114
|
-
var
|
|
2115
|
-
var SESSIX_HOOKS_DIR = (0,
|
|
2116
|
-
var HOOK_SCRIPT_PATH = (0,
|
|
2117
|
-
var PERMISSION_ACCEPT_PATH = (0,
|
|
2118
|
-
var CLAUDE_SETTINGS_PATH = (0,
|
|
2119
|
-
var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.
|
|
2120
|
-
var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.
|
|
2121
|
-
var
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
# \u4ECE stdin \u8BFB\u53D6 hook payload
|
|
2130
|
-
PAYLOAD=$(cat)
|
|
2131
|
-
|
|
2132
|
-
# \u83B7\u53D6\u9879\u76EE\u8DEF\u5F84\uFF08\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09
|
|
2133
|
-
PROJECT_PATH="$PWD"
|
|
2134
|
-
|
|
2135
|
-
# \u53D1\u9001\u5BA1\u6279\u8BF7\u6C42\u5230 Sessix \u670D\u52A1\u5668\uFF08\u957F\u8F6E\u8BE2\uFF0C\u8D85\u65F6\u65F6\u95F4 > 300s\uFF09
|
|
2136
|
-
RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
|
|
2137
|
-
-H "Content-Type: application/json" \\
|
|
2138
|
-
-d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
|
|
2139
|
-
--max-time 320 \\
|
|
2140
|
-
2>/dev/null)
|
|
2141
|
-
|
|
2142
|
-
if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
|
|
2143
|
-
# \u5982\u679C Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C\uFF08exit 0 = \u6279\u51C6\uFF09
|
|
2144
|
-
exit 0
|
|
2145
|
-
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
|
|
2146
2166
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2167
|
+
const sessionId = process.env.SESSIX_SESSION_ID
|
|
2168
|
+
if (!sessionId) process.exit(0)
|
|
2149
2169
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
+
})
|
|
2160
2191
|
`;
|
|
2161
|
-
var PERMISSION_ACCEPT_TEMPLATE = `#!/bin/
|
|
2162
|
-
|
|
2163
|
-
|
|
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
|
|
2164
2195
|
|
|
2165
|
-
if
|
|
2166
|
-
exit 0
|
|
2167
|
-
fi
|
|
2196
|
+
if (!process.env.SESSIX_SESSION_ID) process.exit(0)
|
|
2168
2197
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
exit 0
|
|
2198
|
+
process.stdout.write('{"decision":"allow"}\\n')
|
|
2199
|
+
process.exit(0)
|
|
2172
2200
|
`;
|
|
2173
2201
|
var HookInstaller = class {
|
|
2174
2202
|
/**
|
|
2175
2203
|
* 安装 hook
|
|
2176
2204
|
*
|
|
2177
2205
|
* 1. 创建 ~/.sessix/hooks/ 目录
|
|
2178
|
-
* 2. 写入 approval-hook.
|
|
2206
|
+
* 2. 写入 approval-hook.js 脚本
|
|
2179
2207
|
* 3. 赋予执行权限
|
|
2180
2208
|
* 4. 更新 Claude Code settings.json 添加 hook 配置
|
|
2181
2209
|
*/
|
|
@@ -2228,6 +2256,10 @@ var HookInstaller = class {
|
|
|
2228
2256
|
async addHookToSettings() {
|
|
2229
2257
|
let settings = await this.readClaudeSettings();
|
|
2230
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
|
+
}
|
|
2231
2263
|
if (!settings.hooks) {
|
|
2232
2264
|
settings.hooks = {};
|
|
2233
2265
|
}
|
|
@@ -2295,7 +2327,7 @@ var HookInstaller = class {
|
|
|
2295
2327
|
* 写入 Claude Code settings.json
|
|
2296
2328
|
*/
|
|
2297
2329
|
async writeClaudeSettings(settings) {
|
|
2298
|
-
await (0, import_promises2.mkdir)((0,
|
|
2330
|
+
await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
|
|
2299
2331
|
await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2300
2332
|
}
|
|
2301
2333
|
/**
|
|
@@ -2304,11 +2336,11 @@ var HookInstaller = class {
|
|
|
2304
2336
|
hasHookConfig(settings) {
|
|
2305
2337
|
return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
|
|
2306
2338
|
}
|
|
2307
|
-
/** 检查 PreToolUse 中是否有 approval-hook.
|
|
2339
|
+
/** 检查 PreToolUse 中是否有 approval-hook.js */
|
|
2308
2340
|
hasPreToolUseConfig(settings) {
|
|
2309
2341
|
return this.hasHookEntry(settings?.hooks?.PreToolUse, HOOK_COMMAND);
|
|
2310
2342
|
}
|
|
2311
|
-
/** 检查 PermissionRequest 中是否有 permission-accept.
|
|
2343
|
+
/** 检查 PermissionRequest 中是否有 permission-accept.js */
|
|
2312
2344
|
hasPermissionRequestConfig(settings) {
|
|
2313
2345
|
return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
|
|
2314
2346
|
}
|
|
@@ -2322,7 +2354,7 @@ var HookInstaller = class {
|
|
|
2322
2354
|
};
|
|
2323
2355
|
|
|
2324
2356
|
// src/notification/NotificationService.ts
|
|
2325
|
-
var
|
|
2357
|
+
var import_node_path4 = require("path");
|
|
2326
2358
|
var NotificationService = class {
|
|
2327
2359
|
constructor(sessionManager, expoChannel = null) {
|
|
2328
2360
|
this.sessionManager = sessionManager;
|
|
@@ -2418,7 +2450,7 @@ var NotificationService = class {
|
|
|
2418
2450
|
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
2419
2451
|
const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
|
|
2420
2452
|
const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
|
|
2421
|
-
const projectName = (0,
|
|
2453
|
+
const projectName = (0, import_node_path4.basename)(
|
|
2422
2454
|
this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
|
|
2423
2455
|
);
|
|
2424
2456
|
const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
|
|
@@ -2474,7 +2506,7 @@ var NotificationService = class {
|
|
|
2474
2506
|
/** 从审批请求中提取操作目标的简短描述 */
|
|
2475
2507
|
extractTarget(request) {
|
|
2476
2508
|
const input = request.toolInput;
|
|
2477
|
-
if (input.file_path) return (0,
|
|
2509
|
+
if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
|
|
2478
2510
|
if (input.command) return String(input.command).slice(0, 40);
|
|
2479
2511
|
return request.description.slice(0, 40);
|
|
2480
2512
|
}
|
|
@@ -2577,7 +2609,7 @@ var NotificationService = class {
|
|
|
2577
2609
|
getSessionTitle(sessionId) {
|
|
2578
2610
|
const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
|
|
2579
2611
|
if (!session) return "Unknown";
|
|
2580
|
-
return session.summary ?? (0,
|
|
2612
|
+
return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
|
|
2581
2613
|
}
|
|
2582
2614
|
/** 获取会话的 YOLO 模式状态 */
|
|
2583
2615
|
getYoloMode(sessionId) {
|
|
@@ -2585,9 +2617,9 @@ var NotificationService = class {
|
|
|
2585
2617
|
}
|
|
2586
2618
|
};
|
|
2587
2619
|
|
|
2588
|
-
// src/notification/
|
|
2589
|
-
var
|
|
2590
|
-
var
|
|
2620
|
+
// src/notification/DesktopNotificationChannel.ts
|
|
2621
|
+
var import_node_child_process3 = require("child_process");
|
|
2622
|
+
var DesktopNotificationChannel = class {
|
|
2591
2623
|
isAvailable() {
|
|
2592
2624
|
return process.platform === "darwin";
|
|
2593
2625
|
}
|
|
@@ -2598,9 +2630,9 @@ var MacNotificationChannel = class {
|
|
|
2598
2630
|
const sound = payload.sound ?? "Ping";
|
|
2599
2631
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
2600
2632
|
return new Promise((resolve) => {
|
|
2601
|
-
(0,
|
|
2633
|
+
(0, import_node_child_process3.execFile)("osascript", ["-e", script], (err) => {
|
|
2602
2634
|
if (err) {
|
|
2603
|
-
console.warn("[
|
|
2635
|
+
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
2604
2636
|
}
|
|
2605
2637
|
resolve();
|
|
2606
2638
|
});
|
|
@@ -3226,11 +3258,11 @@ var PairingManager = class {
|
|
|
3226
3258
|
};
|
|
3227
3259
|
|
|
3228
3260
|
// src/auth/AuthManager.ts
|
|
3261
|
+
var import_child_process2 = require("child_process");
|
|
3229
3262
|
var import_child_process3 = require("child_process");
|
|
3230
|
-
var import_child_process4 = require("child_process");
|
|
3231
3263
|
var import_util = require("util");
|
|
3232
3264
|
var import_events2 = require("events");
|
|
3233
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3265
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3234
3266
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3235
3267
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3236
3268
|
var AuthManager = class extends import_events2.EventEmitter {
|
|
@@ -3261,7 +3293,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3261
3293
|
}
|
|
3262
3294
|
this.clearLoginTimeout();
|
|
3263
3295
|
this.urlSent = false;
|
|
3264
|
-
const proc = (0,
|
|
3296
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3265
3297
|
env: { ...process.env, BROWSER: "echo" },
|
|
3266
3298
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3267
3299
|
});
|
|
@@ -3347,15 +3379,31 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3347
3379
|
var import_promises5 = require("fs/promises");
|
|
3348
3380
|
var WS_PORT = 3745;
|
|
3349
3381
|
var HTTP_PORT = 3746;
|
|
3350
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
3382
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process4.exec);
|
|
3351
3383
|
async function killPortProcess(port) {
|
|
3352
3384
|
try {
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
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
|
+
}
|
|
3358
3405
|
}
|
|
3406
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
3359
3407
|
} catch {
|
|
3360
3408
|
}
|
|
3361
3409
|
}
|
|
@@ -3373,8 +3421,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3373
3421
|
}
|
|
3374
3422
|
}
|
|
3375
3423
|
async function start(opts = {}) {
|
|
3376
|
-
const configDir = (0,
|
|
3377
|
-
const tokenFile = (0,
|
|
3424
|
+
const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
|
|
3425
|
+
const tokenFile = (0, import_node_path5.join)(configDir, "token");
|
|
3378
3426
|
let token;
|
|
3379
3427
|
if (opts.token !== void 0) {
|
|
3380
3428
|
token = opts.token;
|
|
@@ -3397,7 +3445,7 @@ async function start(opts = {}) {
|
|
|
3397
3445
|
const expoChannel = new ExpoNotificationChannel();
|
|
3398
3446
|
const notificationService = new NotificationService(sessionManager, expoChannel);
|
|
3399
3447
|
notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
|
|
3400
|
-
notificationService.addChannel("mac", new
|
|
3448
|
+
notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
|
|
3401
3449
|
if (opts.activityPush) {
|
|
3402
3450
|
try {
|
|
3403
3451
|
const activityChannel = new ActivityPushChannel(opts.activityPush);
|
|
@@ -3424,7 +3472,7 @@ async function start(opts = {}) {
|
|
|
3424
3472
|
let mdnsService = null;
|
|
3425
3473
|
const pairingManager = new PairingManager({
|
|
3426
3474
|
token,
|
|
3427
|
-
serverName: (0,
|
|
3475
|
+
serverName: (0, import_node_os6.hostname)(),
|
|
3428
3476
|
version: "0.2.0",
|
|
3429
3477
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3430
3478
|
});
|
|
@@ -3794,12 +3842,17 @@ async function start(opts = {}) {
|
|
|
3794
3842
|
}));
|
|
3795
3843
|
const startMdns = () => {
|
|
3796
3844
|
if (mdnsService) return;
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
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
|
+
}
|
|
3803
3856
|
};
|
|
3804
3857
|
const stopMdns = () => {
|
|
3805
3858
|
if (!mdnsService) return;
|
|
@@ -3876,7 +3929,7 @@ async function start(opts = {}) {
|
|
|
3876
3929
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
3877
3930
|
function getPackageVersion() {
|
|
3878
3931
|
try {
|
|
3879
|
-
const pkg = JSON.parse((0,
|
|
3932
|
+
const pkg = JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path6.join)(__dirname, "..", "package.json"), "utf8"));
|
|
3880
3933
|
return pkg.version ?? "0.0.0";
|
|
3881
3934
|
} catch {
|
|
3882
3935
|
return "0.0.0";
|
|
@@ -3978,10 +4031,11 @@ ${t("startup.pairingReopened")}`);
|
|
|
3978
4031
|
} else {
|
|
3979
4032
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
3980
4033
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
4034
|
+
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
3981
4035
|
}
|
|
3982
4036
|
}
|
|
3983
4037
|
function getLocalIp() {
|
|
3984
|
-
const interfaces = (0,
|
|
4038
|
+
const interfaces = (0, import_node_os7.networkInterfaces)();
|
|
3985
4039
|
for (const iface of Object.values(interfaces)) {
|
|
3986
4040
|
for (const addr of iface ?? []) {
|
|
3987
4041
|
if (addr.family === "IPv4" && !addr.internal) {
|
|
@@ -3989,7 +4043,7 @@ function getLocalIp() {
|
|
|
3989
4043
|
}
|
|
3990
4044
|
}
|
|
3991
4045
|
}
|
|
3992
|
-
return "<your-
|
|
4046
|
+
return "<your-ip>";
|
|
3993
4047
|
}
|
|
3994
4048
|
function buildQrUrl(ip, wsPort, token) {
|
|
3995
4049
|
const base = `sessix://${ip}:${wsPort}`;
|
package/dist/server.js
CHANGED
|
@@ -295,38 +295,87 @@ function t(key, params) {
|
|
|
295
295
|
// src/server.ts
|
|
296
296
|
var import_uuid4 = require("uuid");
|
|
297
297
|
var import_promises4 = require("fs/promises");
|
|
298
|
-
var
|
|
299
|
-
var
|
|
300
|
-
var
|
|
298
|
+
var import_node_os6 = require("os");
|
|
299
|
+
var import_node_path5 = require("path");
|
|
300
|
+
var import_node_child_process4 = require("child_process");
|
|
301
301
|
var import_node_util = require("util");
|
|
302
302
|
|
|
303
303
|
// src/providers/ProcessProvider.ts
|
|
304
|
-
var
|
|
304
|
+
var import_child_process = require("child_process");
|
|
305
305
|
var import_readline = require("readline");
|
|
306
306
|
var import_events = require("events");
|
|
307
|
-
var
|
|
307
|
+
var import_node_os2 = require("os");
|
|
308
308
|
var import_uuid = require("uuid");
|
|
309
309
|
|
|
310
310
|
// src/utils/claudePath.ts
|
|
311
|
-
var
|
|
311
|
+
var import_node_child_process2 = require("child_process");
|
|
312
|
+
var import_node_fs = require("fs");
|
|
313
|
+
var import_node_path = require("path");
|
|
314
|
+
var import_node_os = require("os");
|
|
315
|
+
|
|
316
|
+
// src/utils/platform.ts
|
|
317
|
+
var import_node_child_process = require("child_process");
|
|
318
|
+
var isWindows = process.platform === "win32";
|
|
319
|
+
function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
|
|
320
|
+
return new Promise((resolve) => {
|
|
321
|
+
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
322
|
+
resolve();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const onExit = () => {
|
|
326
|
+
clearTimeout(timer);
|
|
327
|
+
resolve();
|
|
328
|
+
};
|
|
329
|
+
proc.once("exit", onExit);
|
|
330
|
+
if (isWindows) {
|
|
331
|
+
if (proc.pid) {
|
|
332
|
+
(0, import_node_child_process.spawn)("taskkill", ["/PID", String(proc.pid), "/T", "/F"], { stdio: "ignore" });
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
proc.kill("SIGTERM");
|
|
336
|
+
}
|
|
337
|
+
const timer = setTimeout(() => {
|
|
338
|
+
if (proc.exitCode === null && proc.signalCode === null) {
|
|
339
|
+
if (!isWindows) {
|
|
340
|
+
proc.kill("SIGKILL");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
resolve();
|
|
344
|
+
}, timeoutMs);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function isNormalExit(code, signal) {
|
|
348
|
+
if (code === 0) return true;
|
|
349
|
+
if (isWindows) {
|
|
350
|
+
return code === 1;
|
|
351
|
+
}
|
|
352
|
+
return code === 143 || signal === "SIGTERM";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/utils/claudePath.ts
|
|
312
356
|
function findClaudePath() {
|
|
313
357
|
try {
|
|
314
|
-
|
|
358
|
+
const cmd = isWindows ? "where claude" : "which claude";
|
|
359
|
+
return (0, import_node_child_process2.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
315
360
|
} catch {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
361
|
+
}
|
|
362
|
+
const candidates = isWindows ? [
|
|
363
|
+
(0, import_node_path.join)(process.env.LOCALAPPDATA ?? "", "Programs", "claude", "claude.exe"),
|
|
364
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), "AppData", "Local", "Programs", "claude", "claude.exe"),
|
|
365
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "local", "claude.exe")
|
|
366
|
+
] : [
|
|
367
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "bin", "claude"),
|
|
368
|
+
"/usr/local/bin/claude",
|
|
369
|
+
"/opt/homebrew/bin/claude"
|
|
370
|
+
];
|
|
371
|
+
for (const candidate of candidates) {
|
|
372
|
+
try {
|
|
373
|
+
(0, import_node_fs.accessSync)(candidate, import_node_fs.constants.X_OK);
|
|
374
|
+
return candidate;
|
|
375
|
+
} catch {
|
|
327
376
|
}
|
|
328
|
-
return "claude";
|
|
329
377
|
}
|
|
378
|
+
return "claude";
|
|
330
379
|
}
|
|
331
380
|
|
|
332
381
|
// src/providers/ProcessProvider.ts
|
|
@@ -399,19 +448,7 @@ var ProcessProvider = class {
|
|
|
399
448
|
entry.process.stdin?.end();
|
|
400
449
|
} catch {
|
|
401
450
|
}
|
|
402
|
-
entry.process
|
|
403
|
-
await new Promise((resolve) => {
|
|
404
|
-
const timeout = setTimeout(() => {
|
|
405
|
-
if (entry.process.exitCode === null && entry.process.signalCode === null) {
|
|
406
|
-
entry.process.kill("SIGKILL");
|
|
407
|
-
}
|
|
408
|
-
resolve();
|
|
409
|
-
}, 3e3);
|
|
410
|
-
entry.process.once("exit", () => {
|
|
411
|
-
clearTimeout(timeout);
|
|
412
|
-
resolve();
|
|
413
|
-
});
|
|
414
|
-
});
|
|
451
|
+
await killProcessCrossPlatform(entry.process);
|
|
415
452
|
}
|
|
416
453
|
this.emittedQuestionToolUseIds.delete(sessionId);
|
|
417
454
|
this.activeSessions.delete(sessionId);
|
|
@@ -441,7 +478,7 @@ var ProcessProvider = class {
|
|
|
441
478
|
entry.process.stdin?.end();
|
|
442
479
|
} catch {
|
|
443
480
|
}
|
|
444
|
-
entry.process
|
|
481
|
+
killProcessCrossPlatform(entry.process);
|
|
445
482
|
}
|
|
446
483
|
} else {
|
|
447
484
|
console.log(`[ProcessProvider] Session ${sessionId}: process exited, respawning`);
|
|
@@ -523,7 +560,7 @@ var ProcessProvider = class {
|
|
|
523
560
|
}
|
|
524
561
|
const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
|
|
525
562
|
delete env.CLAUDECODE;
|
|
526
|
-
const proc = (0,
|
|
563
|
+
const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
|
|
527
564
|
cwd: projectPath,
|
|
528
565
|
env,
|
|
529
566
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -669,7 +706,7 @@ var ProcessProvider = class {
|
|
|
669
706
|
entry.session.lastActiveAt = Date.now();
|
|
670
707
|
const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
|
|
671
708
|
if (alreadyHasResult) return;
|
|
672
|
-
const isNormal = code
|
|
709
|
+
const isNormal = isNormalExit(code, signal);
|
|
673
710
|
entry.session.status = isNormal ? "idle" : "error";
|
|
674
711
|
if (!isNormal) {
|
|
675
712
|
console.error(
|
|
@@ -735,8 +772,8 @@ ${context}`;
|
|
|
735
772
|
return new Promise((resolve, reject) => {
|
|
736
773
|
const env = { ...process.env };
|
|
737
774
|
delete env.CLAUDECODE;
|
|
738
|
-
const proc = (0,
|
|
739
|
-
cwd: (0,
|
|
775
|
+
const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
|
|
776
|
+
cwd: (0, import_node_os2.homedir)(),
|
|
740
777
|
env,
|
|
741
778
|
stdio: ["pipe", "pipe", "pipe"]
|
|
742
779
|
});
|
|
@@ -1628,15 +1665,15 @@ var WsBridge = class _WsBridge {
|
|
|
1628
1665
|
|
|
1629
1666
|
// src/approval/ApprovalProxy.ts
|
|
1630
1667
|
var import_node_http = __toESM(require("http"));
|
|
1631
|
-
var
|
|
1632
|
-
var
|
|
1633
|
-
var
|
|
1668
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
1669
|
+
var import_node_path2 = __toESM(require("path"));
|
|
1670
|
+
var import_node_os3 = __toESM(require("os"));
|
|
1634
1671
|
var import_uuid3 = require("uuid");
|
|
1635
1672
|
var ApprovalProxy = class _ApprovalProxy {
|
|
1636
1673
|
server;
|
|
1637
1674
|
token;
|
|
1638
1675
|
port;
|
|
1639
|
-
settingsPath =
|
|
1676
|
+
settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
|
|
1640
1677
|
/** 待处理的审批请求:requestId -> { resolve, timer, request } */
|
|
1641
1678
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
1642
1679
|
/** 审批请求回调(通知外部推送到手机) */
|
|
@@ -1741,7 +1778,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1741
1778
|
isToolInClaudeSettings(toolName, projectPath) {
|
|
1742
1779
|
const checkPath = (filepath) => {
|
|
1743
1780
|
try {
|
|
1744
|
-
const raw =
|
|
1781
|
+
const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
|
|
1745
1782
|
const settings = JSON.parse(raw);
|
|
1746
1783
|
const allow = settings?.permissions?.allow ?? [];
|
|
1747
1784
|
return allow.some((entry) => {
|
|
@@ -1755,24 +1792,24 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1755
1792
|
}
|
|
1756
1793
|
};
|
|
1757
1794
|
if (projectPath) {
|
|
1758
|
-
const projectSettingsPath =
|
|
1795
|
+
const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
|
|
1759
1796
|
if (checkPath(projectSettingsPath)) return true;
|
|
1760
1797
|
}
|
|
1761
1798
|
return checkPath(this.settingsPath);
|
|
1762
1799
|
}
|
|
1763
1800
|
/** 将工具写入 settings.json permissions.allow(项目级或全局) */
|
|
1764
1801
|
addToClaudeSettings(projectPath, toolName) {
|
|
1765
|
-
const targetPath = projectPath ?
|
|
1802
|
+
const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
|
|
1766
1803
|
try {
|
|
1767
1804
|
if (projectPath) {
|
|
1768
|
-
const dir =
|
|
1769
|
-
if (!
|
|
1770
|
-
|
|
1805
|
+
const dir = import_node_path2.default.dirname(targetPath);
|
|
1806
|
+
if (!import_node_fs2.default.existsSync(dir)) {
|
|
1807
|
+
import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
1771
1808
|
}
|
|
1772
1809
|
}
|
|
1773
1810
|
let settings = {};
|
|
1774
1811
|
try {
|
|
1775
|
-
settings = JSON.parse(
|
|
1812
|
+
settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
|
|
1776
1813
|
} catch {
|
|
1777
1814
|
}
|
|
1778
1815
|
if (!settings.permissions) {
|
|
@@ -1786,7 +1823,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1786
1823
|
const entry = `${toolName}(*)`;
|
|
1787
1824
|
if (!allow.includes(entry)) {
|
|
1788
1825
|
allow.push(entry);
|
|
1789
|
-
|
|
1826
|
+
import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
1790
1827
|
const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
|
|
1791
1828
|
console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
|
|
1792
1829
|
}
|
|
@@ -2010,11 +2047,12 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2010
2047
|
|
|
2011
2048
|
// src/mdns/MdnsService.ts
|
|
2012
2049
|
var import_bonjour_service = __toESM(require("bonjour-service"));
|
|
2013
|
-
var
|
|
2050
|
+
var import_node_os4 = require("os");
|
|
2014
2051
|
function getLanAddresses() {
|
|
2015
2052
|
const results = [];
|
|
2016
|
-
for (const [name, addrs] of Object.entries((0,
|
|
2017
|
-
if (name.startsWith("utun") || name
|
|
2053
|
+
for (const [name, addrs] of Object.entries((0, import_node_os4.networkInterfaces)())) {
|
|
2054
|
+
if (name.startsWith("utun") || name === "lo") continue;
|
|
2055
|
+
if (isWindows && (name.startsWith("vEthernet") || name.includes("Loopback"))) continue;
|
|
2018
2056
|
for (const addr of addrs ?? []) {
|
|
2019
2057
|
if (addr.family === "IPv4" && !addr.internal) {
|
|
2020
2058
|
results.push(addr.address);
|
|
@@ -2116,72 +2154,62 @@ var MdnsService = class {
|
|
|
2116
2154
|
|
|
2117
2155
|
// src/hooks/HookInstaller.ts
|
|
2118
2156
|
var import_promises2 = require("fs/promises");
|
|
2119
|
-
var
|
|
2120
|
-
var
|
|
2121
|
-
var SESSIX_HOOKS_DIR = (0,
|
|
2122
|
-
var HOOK_SCRIPT_PATH = (0,
|
|
2123
|
-
var PERMISSION_ACCEPT_PATH = (0,
|
|
2124
|
-
var CLAUDE_SETTINGS_PATH = (0,
|
|
2125
|
-
var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.
|
|
2126
|
-
var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.
|
|
2127
|
-
var
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
# \u4ECE stdin \u8BFB\u53D6 hook payload
|
|
2136
|
-
PAYLOAD=$(cat)
|
|
2137
|
-
|
|
2138
|
-
# \u83B7\u53D6\u9879\u76EE\u8DEF\u5F84\uFF08\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09
|
|
2139
|
-
PROJECT_PATH="$PWD"
|
|
2140
|
-
|
|
2141
|
-
# \u53D1\u9001\u5BA1\u6279\u8BF7\u6C42\u5230 Sessix \u670D\u52A1\u5668\uFF08\u957F\u8F6E\u8BE2\uFF0C\u8D85\u65F6\u65F6\u95F4 > 300s\uFF09
|
|
2142
|
-
RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
|
|
2143
|
-
-H "Content-Type: application/json" \\
|
|
2144
|
-
-d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
|
|
2145
|
-
--max-time 320 \\
|
|
2146
|
-
2>/dev/null)
|
|
2147
|
-
|
|
2148
|
-
if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
|
|
2149
|
-
# \u5982\u679C Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C\uFF08exit 0 = \u6279\u51C6\uFF09
|
|
2150
|
-
exit 0
|
|
2151
|
-
fi
|
|
2157
|
+
var import_node_path3 = require("path");
|
|
2158
|
+
var import_node_os5 = require("os");
|
|
2159
|
+
var SESSIX_HOOKS_DIR = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".sessix", "hooks");
|
|
2160
|
+
var HOOK_SCRIPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
|
|
2161
|
+
var PERMISSION_ACCEPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
|
|
2162
|
+
var CLAUDE_SETTINGS_PATH = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude", "settings.json");
|
|
2163
|
+
var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
|
|
2164
|
+
var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
|
|
2165
|
+
var LEGACY_HOOK_COMMANDS = [
|
|
2166
|
+
"~/.sessix/hooks/approval-hook.sh",
|
|
2167
|
+
"~/.sessix/hooks/permission-accept.sh"
|
|
2168
|
+
];
|
|
2169
|
+
var HOOK_SCRIPT_TEMPLATE = `#!/usr/bin/env node
|
|
2170
|
+
// Sessix Approval Hook
|
|
2171
|
+
// \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
|
|
2152
2172
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2173
|
+
const sessionId = process.env.SESSIX_SESSION_ID
|
|
2174
|
+
if (!sessionId) process.exit(0)
|
|
2155
2175
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2176
|
+
let payload = ''
|
|
2177
|
+
process.stdin.on('data', (chunk) => { payload += chunk })
|
|
2178
|
+
process.stdin.on('end', async () => {
|
|
2179
|
+
try {
|
|
2180
|
+
const res = await fetch('http://localhost:3746/hook/approval', {
|
|
2181
|
+
method: 'POST',
|
|
2182
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2183
|
+
body: JSON.stringify({
|
|
2184
|
+
sessionId,
|
|
2185
|
+
projectPath: process.cwd(),
|
|
2186
|
+
payload: JSON.parse(payload),
|
|
2187
|
+
}),
|
|
2188
|
+
signal: AbortSignal.timeout(320000),
|
|
2189
|
+
})
|
|
2190
|
+
const data = await res.json()
|
|
2191
|
+
process.exit(data.decision === 'deny' ? 1 : 0)
|
|
2192
|
+
} catch {
|
|
2193
|
+
// Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C
|
|
2194
|
+
process.exit(0)
|
|
2195
|
+
}
|
|
2196
|
+
})
|
|
2166
2197
|
`;
|
|
2167
|
-
var PERMISSION_ACCEPT_TEMPLATE = `#!/bin/
|
|
2168
|
-
|
|
2169
|
-
|
|
2198
|
+
var PERMISSION_ACCEPT_TEMPLATE = `#!/usr/bin/env node
|
|
2199
|
+
// Sessix PermissionRequest \u515C\u5E95
|
|
2200
|
+
// \u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42\uFF0C\u907F\u514D Sessix \u4F1A\u8BDD\u963B\u585E
|
|
2170
2201
|
|
|
2171
|
-
if
|
|
2172
|
-
exit 0
|
|
2173
|
-
fi
|
|
2202
|
+
if (!process.env.SESSIX_SESSION_ID) process.exit(0)
|
|
2174
2203
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
exit 0
|
|
2204
|
+
process.stdout.write('{"decision":"allow"}\\n')
|
|
2205
|
+
process.exit(0)
|
|
2178
2206
|
`;
|
|
2179
2207
|
var HookInstaller = class {
|
|
2180
2208
|
/**
|
|
2181
2209
|
* 安装 hook
|
|
2182
2210
|
*
|
|
2183
2211
|
* 1. 创建 ~/.sessix/hooks/ 目录
|
|
2184
|
-
* 2. 写入 approval-hook.
|
|
2212
|
+
* 2. 写入 approval-hook.js 脚本
|
|
2185
2213
|
* 3. 赋予执行权限
|
|
2186
2214
|
* 4. 更新 Claude Code settings.json 添加 hook 配置
|
|
2187
2215
|
*/
|
|
@@ -2234,6 +2262,10 @@ var HookInstaller = class {
|
|
|
2234
2262
|
async addHookToSettings() {
|
|
2235
2263
|
let settings = await this.readClaudeSettings();
|
|
2236
2264
|
let changed = false;
|
|
2265
|
+
for (const cmd of LEGACY_HOOK_COMMANDS) {
|
|
2266
|
+
this.removeHookCommand(settings, "PreToolUse", cmd);
|
|
2267
|
+
this.removeHookCommand(settings, "PermissionRequest", cmd);
|
|
2268
|
+
}
|
|
2237
2269
|
if (!settings.hooks) {
|
|
2238
2270
|
settings.hooks = {};
|
|
2239
2271
|
}
|
|
@@ -2301,7 +2333,7 @@ var HookInstaller = class {
|
|
|
2301
2333
|
* 写入 Claude Code settings.json
|
|
2302
2334
|
*/
|
|
2303
2335
|
async writeClaudeSettings(settings) {
|
|
2304
|
-
await (0, import_promises2.mkdir)((0,
|
|
2336
|
+
await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
|
|
2305
2337
|
await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2306
2338
|
}
|
|
2307
2339
|
/**
|
|
@@ -2310,11 +2342,11 @@ var HookInstaller = class {
|
|
|
2310
2342
|
hasHookConfig(settings) {
|
|
2311
2343
|
return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
|
|
2312
2344
|
}
|
|
2313
|
-
/** 检查 PreToolUse 中是否有 approval-hook.
|
|
2345
|
+
/** 检查 PreToolUse 中是否有 approval-hook.js */
|
|
2314
2346
|
hasPreToolUseConfig(settings) {
|
|
2315
2347
|
return this.hasHookEntry(settings?.hooks?.PreToolUse, HOOK_COMMAND);
|
|
2316
2348
|
}
|
|
2317
|
-
/** 检查 PermissionRequest 中是否有 permission-accept.
|
|
2349
|
+
/** 检查 PermissionRequest 中是否有 permission-accept.js */
|
|
2318
2350
|
hasPermissionRequestConfig(settings) {
|
|
2319
2351
|
return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
|
|
2320
2352
|
}
|
|
@@ -2328,7 +2360,7 @@ var HookInstaller = class {
|
|
|
2328
2360
|
};
|
|
2329
2361
|
|
|
2330
2362
|
// src/notification/NotificationService.ts
|
|
2331
|
-
var
|
|
2363
|
+
var import_node_path4 = require("path");
|
|
2332
2364
|
var NotificationService = class {
|
|
2333
2365
|
constructor(sessionManager, expoChannel = null) {
|
|
2334
2366
|
this.sessionManager = sessionManager;
|
|
@@ -2424,7 +2456,7 @@ var NotificationService = class {
|
|
|
2424
2456
|
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
2425
2457
|
const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
|
|
2426
2458
|
const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
|
|
2427
|
-
const projectName = (0,
|
|
2459
|
+
const projectName = (0, import_node_path4.basename)(
|
|
2428
2460
|
this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
|
|
2429
2461
|
);
|
|
2430
2462
|
const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
|
|
@@ -2480,7 +2512,7 @@ var NotificationService = class {
|
|
|
2480
2512
|
/** 从审批请求中提取操作目标的简短描述 */
|
|
2481
2513
|
extractTarget(request) {
|
|
2482
2514
|
const input = request.toolInput;
|
|
2483
|
-
if (input.file_path) return (0,
|
|
2515
|
+
if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
|
|
2484
2516
|
if (input.command) return String(input.command).slice(0, 40);
|
|
2485
2517
|
return request.description.slice(0, 40);
|
|
2486
2518
|
}
|
|
@@ -2583,7 +2615,7 @@ var NotificationService = class {
|
|
|
2583
2615
|
getSessionTitle(sessionId) {
|
|
2584
2616
|
const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
|
|
2585
2617
|
if (!session) return "Unknown";
|
|
2586
|
-
return session.summary ?? (0,
|
|
2618
|
+
return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
|
|
2587
2619
|
}
|
|
2588
2620
|
/** 获取会话的 YOLO 模式状态 */
|
|
2589
2621
|
getYoloMode(sessionId) {
|
|
@@ -2591,9 +2623,9 @@ var NotificationService = class {
|
|
|
2591
2623
|
}
|
|
2592
2624
|
};
|
|
2593
2625
|
|
|
2594
|
-
// src/notification/
|
|
2595
|
-
var
|
|
2596
|
-
var
|
|
2626
|
+
// src/notification/DesktopNotificationChannel.ts
|
|
2627
|
+
var import_node_child_process3 = require("child_process");
|
|
2628
|
+
var DesktopNotificationChannel = class {
|
|
2597
2629
|
isAvailable() {
|
|
2598
2630
|
return process.platform === "darwin";
|
|
2599
2631
|
}
|
|
@@ -2604,9 +2636,9 @@ var MacNotificationChannel = class {
|
|
|
2604
2636
|
const sound = payload.sound ?? "Ping";
|
|
2605
2637
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
2606
2638
|
return new Promise((resolve) => {
|
|
2607
|
-
(0,
|
|
2639
|
+
(0, import_node_child_process3.execFile)("osascript", ["-e", script], (err) => {
|
|
2608
2640
|
if (err) {
|
|
2609
|
-
console.warn("[
|
|
2641
|
+
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
2610
2642
|
}
|
|
2611
2643
|
resolve();
|
|
2612
2644
|
});
|
|
@@ -3232,11 +3264,11 @@ var PairingManager = class {
|
|
|
3232
3264
|
};
|
|
3233
3265
|
|
|
3234
3266
|
// src/auth/AuthManager.ts
|
|
3267
|
+
var import_child_process2 = require("child_process");
|
|
3235
3268
|
var import_child_process3 = require("child_process");
|
|
3236
|
-
var import_child_process4 = require("child_process");
|
|
3237
3269
|
var import_util = require("util");
|
|
3238
3270
|
var import_events2 = require("events");
|
|
3239
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3271
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3240
3272
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3241
3273
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3242
3274
|
var AuthManager = class extends import_events2.EventEmitter {
|
|
@@ -3267,7 +3299,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3267
3299
|
}
|
|
3268
3300
|
this.clearLoginTimeout();
|
|
3269
3301
|
this.urlSent = false;
|
|
3270
|
-
const proc = (0,
|
|
3302
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3271
3303
|
env: { ...process.env, BROWSER: "echo" },
|
|
3272
3304
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3273
3305
|
});
|
|
@@ -3353,15 +3385,31 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3353
3385
|
var import_promises5 = require("fs/promises");
|
|
3354
3386
|
var WS_PORT = 3745;
|
|
3355
3387
|
var HTTP_PORT = 3746;
|
|
3356
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
3388
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process4.exec);
|
|
3357
3389
|
async function killPortProcess(port) {
|
|
3358
3390
|
try {
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3391
|
+
if (isWindows) {
|
|
3392
|
+
const { stdout } = await execAsync(
|
|
3393
|
+
`netstat -ano | findstr :${port} | findstr LISTENING`
|
|
3394
|
+
);
|
|
3395
|
+
const pids = /* @__PURE__ */ new Set();
|
|
3396
|
+
for (const line of stdout.trim().split("\n")) {
|
|
3397
|
+
const parts = line.trim().split(/\s+/);
|
|
3398
|
+
const pid = parts[parts.length - 1];
|
|
3399
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
3400
|
+
}
|
|
3401
|
+
for (const pid of pids) {
|
|
3402
|
+
await execAsync(`taskkill /PID ${pid} /F`).catch(() => {
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
} else {
|
|
3406
|
+
const { stdout } = await execAsync(`lsof -ti :${port}`);
|
|
3407
|
+
const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
|
|
3408
|
+
if (pids.length > 0) {
|
|
3409
|
+
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
3410
|
+
}
|
|
3364
3411
|
}
|
|
3412
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
3365
3413
|
} catch {
|
|
3366
3414
|
}
|
|
3367
3415
|
}
|
|
@@ -3379,8 +3427,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3379
3427
|
}
|
|
3380
3428
|
}
|
|
3381
3429
|
async function start(opts = {}) {
|
|
3382
|
-
const configDir = (0,
|
|
3383
|
-
const tokenFile = (0,
|
|
3430
|
+
const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
|
|
3431
|
+
const tokenFile = (0, import_node_path5.join)(configDir, "token");
|
|
3384
3432
|
let token;
|
|
3385
3433
|
if (opts.token !== void 0) {
|
|
3386
3434
|
token = opts.token;
|
|
@@ -3403,7 +3451,7 @@ async function start(opts = {}) {
|
|
|
3403
3451
|
const expoChannel = new ExpoNotificationChannel();
|
|
3404
3452
|
const notificationService = new NotificationService(sessionManager, expoChannel);
|
|
3405
3453
|
notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
|
|
3406
|
-
notificationService.addChannel("mac", new
|
|
3454
|
+
notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
|
|
3407
3455
|
if (opts.activityPush) {
|
|
3408
3456
|
try {
|
|
3409
3457
|
const activityChannel = new ActivityPushChannel(opts.activityPush);
|
|
@@ -3430,7 +3478,7 @@ async function start(opts = {}) {
|
|
|
3430
3478
|
let mdnsService = null;
|
|
3431
3479
|
const pairingManager = new PairingManager({
|
|
3432
3480
|
token,
|
|
3433
|
-
serverName: (0,
|
|
3481
|
+
serverName: (0, import_node_os6.hostname)(),
|
|
3434
3482
|
version: "0.2.0",
|
|
3435
3483
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3436
3484
|
});
|
|
@@ -3800,12 +3848,17 @@ async function start(opts = {}) {
|
|
|
3800
3848
|
}));
|
|
3801
3849
|
const startMdns = () => {
|
|
3802
3850
|
if (mdnsService) return;
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3851
|
+
try {
|
|
3852
|
+
mdnsService = new MdnsService({
|
|
3853
|
+
wsPort: WS_PORT,
|
|
3854
|
+
httpPort: HTTP_PORT,
|
|
3855
|
+
pairing: pairingManager.state
|
|
3856
|
+
});
|
|
3857
|
+
mdnsService.start();
|
|
3858
|
+
} catch (err) {
|
|
3859
|
+
console.warn(`[Server] mDNS failed to start (non-fatal): ${err.message}`);
|
|
3860
|
+
mdnsService = null;
|
|
3861
|
+
}
|
|
3809
3862
|
};
|
|
3810
3863
|
const stopMdns = () => {
|
|
3811
3864
|
if (!mdnsService) return;
|