sessix-server 0.2.5 → 0.2.7
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 +386 -158
- package/dist/server.d.ts +2 -0
- package/dist/server.js +361 -152
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -52,7 +52,10 @@ var zh = {
|
|
|
52
52
|
autoDiscoveryHint: " \u5982\u5728\u516C\u5171\u7F51\u7EDC\uFF0C\u5EFA\u8BAE\u5173\u95ED: SESSIX_AUTO_CONNECT=false npx sessix-server",
|
|
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
|
+
pressT: " \u{1F511} \u6309 t \u91CD\u7F6E Token\uFF08\u6CC4\u9732\u540E\u5237\u65B0\uFF09",
|
|
55
56
|
pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
|
|
57
|
+
tokenRegenerated: "\u{1F511} Token \u5DF2\u91CD\u7F6E\uFF0C\u6240\u6709\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u91CD\u65B0\u626B\u7801\u914D\u5BF9",
|
|
58
|
+
tokenRegenerateFailed: "Token \u91CD\u7F6E\u5931\u8D25:",
|
|
56
59
|
updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
|
|
57
60
|
receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
|
|
58
61
|
goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
|
|
@@ -81,7 +84,8 @@ var zh = {
|
|
|
81
84
|
activityPushEnabled: "ActivityKit Push \u5DF2\u542F\u7528",
|
|
82
85
|
activityPushFailed: "ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:",
|
|
83
86
|
activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09",
|
|
84
|
-
noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B"
|
|
87
|
+
noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B",
|
|
88
|
+
tokenRegenerated: "Token \u5DF2\u91CD\u7F6E: {{token}}"
|
|
85
89
|
},
|
|
86
90
|
ws: {
|
|
87
91
|
started: "WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
|
|
@@ -158,7 +162,10 @@ var en = {
|
|
|
158
162
|
autoDiscoveryHint: " On public networks, disable with: SESSIX_AUTO_CONNECT=false npx sessix-server",
|
|
159
163
|
autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
|
|
160
164
|
pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
|
|
165
|
+
pressT: " \u{1F511} Press t to regenerate token (refresh after leak)",
|
|
161
166
|
pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
|
|
167
|
+
tokenRegenerated: "\u{1F511} Token regenerated, all clients disconnected. Scan QR to re-pair",
|
|
168
|
+
tokenRegenerateFailed: "Token regeneration failed:",
|
|
162
169
|
updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
|
|
163
170
|
receivedSignal: "Received {{signal}}, graceful shutdown...",
|
|
164
171
|
goodbye: "All services closed, goodbye!",
|
|
@@ -187,7 +194,8 @@ var en = {
|
|
|
187
194
|
activityPushEnabled: "ActivityKit Push enabled",
|
|
188
195
|
activityPushFailed: "ActivityKit Push init failed:",
|
|
189
196
|
activityPushContinue: "Continuing startup (Live Activity background push unavailable)",
|
|
190
|
-
noActiveLoginProcess: "No active login process"
|
|
197
|
+
noActiveLoginProcess: "No active login process",
|
|
198
|
+
tokenRegenerated: "Token regenerated: {{token}}"
|
|
191
199
|
},
|
|
192
200
|
ws: {
|
|
193
201
|
started: "WebSocket server started on port {{port}}",
|
|
@@ -293,40 +301,89 @@ function t(key, params) {
|
|
|
293
301
|
}
|
|
294
302
|
|
|
295
303
|
// src/server.ts
|
|
296
|
-
var
|
|
304
|
+
var import_uuid5 = require("uuid");
|
|
297
305
|
var import_promises4 = require("fs/promises");
|
|
298
|
-
var
|
|
299
|
-
var
|
|
300
|
-
var
|
|
306
|
+
var import_node_os6 = require("os");
|
|
307
|
+
var import_node_path5 = require("path");
|
|
308
|
+
var import_node_child_process5 = require("child_process");
|
|
301
309
|
var import_node_util = require("util");
|
|
302
310
|
|
|
303
311
|
// src/providers/ProcessProvider.ts
|
|
304
|
-
var
|
|
312
|
+
var import_child_process = require("child_process");
|
|
305
313
|
var import_readline = require("readline");
|
|
306
314
|
var import_events = require("events");
|
|
307
|
-
var
|
|
315
|
+
var import_node_os2 = require("os");
|
|
308
316
|
var import_uuid = require("uuid");
|
|
309
317
|
|
|
310
318
|
// src/utils/claudePath.ts
|
|
311
|
-
var
|
|
319
|
+
var import_node_child_process2 = require("child_process");
|
|
320
|
+
var import_node_fs = require("fs");
|
|
321
|
+
var import_node_path = require("path");
|
|
322
|
+
var import_node_os = require("os");
|
|
323
|
+
|
|
324
|
+
// src/utils/platform.ts
|
|
325
|
+
var import_node_child_process = require("child_process");
|
|
326
|
+
var isWindows = process.platform === "win32";
|
|
327
|
+
function killProcessCrossPlatform(proc, timeoutMs = 3e3) {
|
|
328
|
+
return new Promise((resolve) => {
|
|
329
|
+
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
330
|
+
resolve();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const onExit = () => {
|
|
334
|
+
clearTimeout(timer);
|
|
335
|
+
resolve();
|
|
336
|
+
};
|
|
337
|
+
proc.once("exit", onExit);
|
|
338
|
+
if (isWindows) {
|
|
339
|
+
if (proc.pid) {
|
|
340
|
+
(0, import_node_child_process.spawn)("taskkill", ["/PID", String(proc.pid), "/T", "/F"], { stdio: "ignore" });
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
proc.kill("SIGTERM");
|
|
344
|
+
}
|
|
345
|
+
const timer = setTimeout(() => {
|
|
346
|
+
if (proc.exitCode === null && proc.signalCode === null) {
|
|
347
|
+
if (!isWindows) {
|
|
348
|
+
proc.kill("SIGKILL");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
resolve();
|
|
352
|
+
}, timeoutMs);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
function isNormalExit(code, signal) {
|
|
356
|
+
if (code === 0) return true;
|
|
357
|
+
if (isWindows) {
|
|
358
|
+
return code === 1;
|
|
359
|
+
}
|
|
360
|
+
return code === 143 || signal === "SIGTERM";
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/utils/claudePath.ts
|
|
312
364
|
function findClaudePath() {
|
|
313
365
|
try {
|
|
314
|
-
|
|
366
|
+
const cmd = isWindows ? "where claude" : "which claude";
|
|
367
|
+
return (0, import_node_child_process2.execSync)(cmd, { encoding: "utf-8" }).trim().split("\n")[0];
|
|
315
368
|
} catch {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
369
|
+
}
|
|
370
|
+
const candidates = isWindows ? [
|
|
371
|
+
(0, import_node_path.join)(process.env.LOCALAPPDATA ?? "", "Programs", "claude", "claude.exe"),
|
|
372
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), "AppData", "Local", "Programs", "claude", "claude.exe"),
|
|
373
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "local", "claude.exe")
|
|
374
|
+
] : [
|
|
375
|
+
(0, import_node_path.join)((0, import_node_os.homedir)(), ".local", "bin", "claude"),
|
|
376
|
+
"/usr/local/bin/claude",
|
|
377
|
+
"/opt/homebrew/bin/claude"
|
|
378
|
+
];
|
|
379
|
+
for (const candidate of candidates) {
|
|
380
|
+
try {
|
|
381
|
+
(0, import_node_fs.accessSync)(candidate, import_node_fs.constants.X_OK);
|
|
382
|
+
return candidate;
|
|
383
|
+
} catch {
|
|
327
384
|
}
|
|
328
|
-
return "claude";
|
|
329
385
|
}
|
|
386
|
+
return "claude";
|
|
330
387
|
}
|
|
331
388
|
|
|
332
389
|
// src/providers/ProcessProvider.ts
|
|
@@ -399,19 +456,7 @@ var ProcessProvider = class {
|
|
|
399
456
|
entry.process.stdin?.end();
|
|
400
457
|
} catch {
|
|
401
458
|
}
|
|
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
|
-
});
|
|
459
|
+
await killProcessCrossPlatform(entry.process);
|
|
415
460
|
}
|
|
416
461
|
this.emittedQuestionToolUseIds.delete(sessionId);
|
|
417
462
|
this.activeSessions.delete(sessionId);
|
|
@@ -441,7 +486,7 @@ var ProcessProvider = class {
|
|
|
441
486
|
entry.process.stdin?.end();
|
|
442
487
|
} catch {
|
|
443
488
|
}
|
|
444
|
-
entry.process
|
|
489
|
+
killProcessCrossPlatform(entry.process);
|
|
445
490
|
}
|
|
446
491
|
} else {
|
|
447
492
|
console.log(`[ProcessProvider] Session ${sessionId}: process exited, respawning`);
|
|
@@ -523,7 +568,7 @@ var ProcessProvider = class {
|
|
|
523
568
|
}
|
|
524
569
|
const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
|
|
525
570
|
delete env.CLAUDECODE;
|
|
526
|
-
const proc = (0,
|
|
571
|
+
const proc = (0, import_child_process.spawn)(CLAUDE_PATH, args, {
|
|
527
572
|
cwd: projectPath,
|
|
528
573
|
env,
|
|
529
574
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -669,7 +714,7 @@ var ProcessProvider = class {
|
|
|
669
714
|
entry.session.lastActiveAt = Date.now();
|
|
670
715
|
const alreadyHasResult = entry.session.status === "idle" || entry.session.status === "error";
|
|
671
716
|
if (alreadyHasResult) return;
|
|
672
|
-
const isNormal = code
|
|
717
|
+
const isNormal = isNormalExit(code, signal);
|
|
673
718
|
entry.session.status = isNormal ? "idle" : "error";
|
|
674
719
|
if (!isNormal) {
|
|
675
720
|
console.error(
|
|
@@ -735,8 +780,8 @@ ${context}`;
|
|
|
735
780
|
return new Promise((resolve, reject) => {
|
|
736
781
|
const env = { ...process.env };
|
|
737
782
|
delete env.CLAUDECODE;
|
|
738
|
-
const proc = (0,
|
|
739
|
-
cwd: (0,
|
|
783
|
+
const proc = (0, import_child_process.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
|
|
784
|
+
cwd: (0, import_node_os2.homedir)(),
|
|
740
785
|
env,
|
|
741
786
|
stdio: ["pipe", "pipe", "pipe"]
|
|
742
787
|
});
|
|
@@ -1507,6 +1552,13 @@ var WsBridge = class _WsBridge {
|
|
|
1507
1552
|
getConnectionCount() {
|
|
1508
1553
|
return this.wss.clients.size;
|
|
1509
1554
|
}
|
|
1555
|
+
/** 更新 token 并断开所有现有连接(token 刷新后需重新配对) */
|
|
1556
|
+
updateToken(newToken) {
|
|
1557
|
+
this.token = newToken;
|
|
1558
|
+
for (const ws of this.wss.clients) {
|
|
1559
|
+
ws.close(4001, "Token regenerated");
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1510
1562
|
/** 优雅关闭 WebSocket 服务 */
|
|
1511
1563
|
close() {
|
|
1512
1564
|
return new Promise((resolve, reject) => {
|
|
@@ -1628,15 +1680,15 @@ var WsBridge = class _WsBridge {
|
|
|
1628
1680
|
|
|
1629
1681
|
// src/approval/ApprovalProxy.ts
|
|
1630
1682
|
var import_node_http = __toESM(require("http"));
|
|
1631
|
-
var
|
|
1632
|
-
var
|
|
1633
|
-
var
|
|
1683
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
1684
|
+
var import_node_path2 = __toESM(require("path"));
|
|
1685
|
+
var import_node_os3 = __toESM(require("os"));
|
|
1634
1686
|
var import_uuid3 = require("uuid");
|
|
1635
1687
|
var ApprovalProxy = class _ApprovalProxy {
|
|
1636
1688
|
server;
|
|
1637
1689
|
token;
|
|
1638
1690
|
port;
|
|
1639
|
-
settingsPath =
|
|
1691
|
+
settingsPath = import_node_path2.default.join(import_node_os3.default.homedir(), ".claude", "settings.json");
|
|
1640
1692
|
/** 待处理的审批请求:requestId -> { resolve, timer, request } */
|
|
1641
1693
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
1642
1694
|
/** 审批请求回调(通知外部推送到手机) */
|
|
@@ -1741,7 +1793,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1741
1793
|
isToolInClaudeSettings(toolName, projectPath) {
|
|
1742
1794
|
const checkPath = (filepath) => {
|
|
1743
1795
|
try {
|
|
1744
|
-
const raw =
|
|
1796
|
+
const raw = import_node_fs2.default.readFileSync(filepath, "utf-8");
|
|
1745
1797
|
const settings = JSON.parse(raw);
|
|
1746
1798
|
const allow = settings?.permissions?.allow ?? [];
|
|
1747
1799
|
return allow.some((entry) => {
|
|
@@ -1755,24 +1807,24 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1755
1807
|
}
|
|
1756
1808
|
};
|
|
1757
1809
|
if (projectPath) {
|
|
1758
|
-
const projectSettingsPath =
|
|
1810
|
+
const projectSettingsPath = import_node_path2.default.join(projectPath, ".claude", "settings.json");
|
|
1759
1811
|
if (checkPath(projectSettingsPath)) return true;
|
|
1760
1812
|
}
|
|
1761
1813
|
return checkPath(this.settingsPath);
|
|
1762
1814
|
}
|
|
1763
1815
|
/** 将工具写入 settings.json permissions.allow(项目级或全局) */
|
|
1764
1816
|
addToClaudeSettings(projectPath, toolName) {
|
|
1765
|
-
const targetPath = projectPath ?
|
|
1817
|
+
const targetPath = projectPath ? import_node_path2.default.join(projectPath, ".claude", "settings.json") : this.settingsPath;
|
|
1766
1818
|
try {
|
|
1767
1819
|
if (projectPath) {
|
|
1768
|
-
const dir =
|
|
1769
|
-
if (!
|
|
1770
|
-
|
|
1820
|
+
const dir = import_node_path2.default.dirname(targetPath);
|
|
1821
|
+
if (!import_node_fs2.default.existsSync(dir)) {
|
|
1822
|
+
import_node_fs2.default.mkdirSync(dir, { recursive: true });
|
|
1771
1823
|
}
|
|
1772
1824
|
}
|
|
1773
1825
|
let settings = {};
|
|
1774
1826
|
try {
|
|
1775
|
-
settings = JSON.parse(
|
|
1827
|
+
settings = JSON.parse(import_node_fs2.default.readFileSync(targetPath, "utf-8"));
|
|
1776
1828
|
} catch {
|
|
1777
1829
|
}
|
|
1778
1830
|
if (!settings.permissions) {
|
|
@@ -1786,7 +1838,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1786
1838
|
const entry = `${toolName}(*)`;
|
|
1787
1839
|
if (!allow.includes(entry)) {
|
|
1788
1840
|
allow.push(entry);
|
|
1789
|
-
|
|
1841
|
+
import_node_fs2.default.writeFileSync(targetPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
1790
1842
|
const label = projectPath ? `${projectPath}/.claude/settings.json` : "~/.claude/settings.json";
|
|
1791
1843
|
console.log(`[ApprovalProxy] ${t("approval.alwaysAllowWritten", { entry, label })}`);
|
|
1792
1844
|
}
|
|
@@ -1946,6 +1998,10 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1946
1998
|
});
|
|
1947
1999
|
}
|
|
1948
2000
|
}
|
|
2001
|
+
/** 更新 token(token 刷新时调用) */
|
|
2002
|
+
updateToken(newToken) {
|
|
2003
|
+
this.token = newToken;
|
|
2004
|
+
}
|
|
1949
2005
|
/** 返回连接 token(仅本机访问) */
|
|
1950
2006
|
handleToken(req, res) {
|
|
1951
2007
|
const remoteAddress = req.socket.remoteAddress;
|
|
@@ -2010,11 +2066,12 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
2010
2066
|
|
|
2011
2067
|
// src/mdns/MdnsService.ts
|
|
2012
2068
|
var import_bonjour_service = __toESM(require("bonjour-service"));
|
|
2013
|
-
var
|
|
2069
|
+
var import_node_os4 = require("os");
|
|
2014
2070
|
function getLanAddresses() {
|
|
2015
2071
|
const results = [];
|
|
2016
|
-
for (const [name, addrs] of Object.entries((0,
|
|
2017
|
-
if (name.startsWith("utun") || name
|
|
2072
|
+
for (const [name, addrs] of Object.entries((0, import_node_os4.networkInterfaces)())) {
|
|
2073
|
+
if (name.startsWith("utun") || name === "lo") continue;
|
|
2074
|
+
if (isWindows && (name.startsWith("vEthernet") || name.includes("Loopback"))) continue;
|
|
2018
2075
|
for (const addr of addrs ?? []) {
|
|
2019
2076
|
if (addr.family === "IPv4" && !addr.internal) {
|
|
2020
2077
|
results.push(addr.address);
|
|
@@ -2116,72 +2173,62 @@ var MdnsService = class {
|
|
|
2116
2173
|
|
|
2117
2174
|
// src/hooks/HookInstaller.ts
|
|
2118
2175
|
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
|
|
2176
|
+
var import_node_path3 = require("path");
|
|
2177
|
+
var import_node_os5 = require("os");
|
|
2178
|
+
var SESSIX_HOOKS_DIR = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".sessix", "hooks");
|
|
2179
|
+
var HOOK_SCRIPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "approval-hook.js");
|
|
2180
|
+
var PERMISSION_ACCEPT_PATH = (0, import_node_path3.join)(SESSIX_HOOKS_DIR, "permission-accept.js");
|
|
2181
|
+
var CLAUDE_SETTINGS_PATH = (0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude", "settings.json");
|
|
2182
|
+
var HOOK_COMMAND = "node ~/.sessix/hooks/approval-hook.js";
|
|
2183
|
+
var PERMISSION_ACCEPT_COMMAND = "node ~/.sessix/hooks/permission-accept.js";
|
|
2184
|
+
var LEGACY_HOOK_COMMANDS = [
|
|
2185
|
+
"~/.sessix/hooks/approval-hook.sh",
|
|
2186
|
+
"~/.sessix/hooks/permission-accept.sh"
|
|
2187
|
+
];
|
|
2188
|
+
var HOOK_SCRIPT_TEMPLATE = `#!/usr/bin/env node
|
|
2189
|
+
// Sessix Approval Hook
|
|
2190
|
+
// \u4EC5\u5728 Sessix \u7BA1\u7406\u7684\u4F1A\u8BDD\u4E2D\u6FC0\u6D3B
|
|
2152
2191
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2192
|
+
const sessionId = process.env.SESSIX_SESSION_ID
|
|
2193
|
+
if (!sessionId) process.exit(0)
|
|
2155
2194
|
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2195
|
+
let payload = ''
|
|
2196
|
+
process.stdin.on('data', (chunk) => { payload += chunk })
|
|
2197
|
+
process.stdin.on('end', async () => {
|
|
2198
|
+
try {
|
|
2199
|
+
const res = await fetch('http://localhost:3746/hook/approval', {
|
|
2200
|
+
method: 'POST',
|
|
2201
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2202
|
+
body: JSON.stringify({
|
|
2203
|
+
sessionId,
|
|
2204
|
+
projectPath: process.cwd(),
|
|
2205
|
+
payload: JSON.parse(payload),
|
|
2206
|
+
}),
|
|
2207
|
+
signal: AbortSignal.timeout(320000),
|
|
2208
|
+
})
|
|
2209
|
+
const data = await res.json()
|
|
2210
|
+
process.exit(data.decision === 'deny' ? 1 : 0)
|
|
2211
|
+
} catch {
|
|
2212
|
+
// Sessix \u670D\u52A1\u5668\u4E0D\u53EF\u7528\uFF0C\u9ED8\u8BA4\u653E\u884C
|
|
2213
|
+
process.exit(0)
|
|
2214
|
+
}
|
|
2215
|
+
})
|
|
2166
2216
|
`;
|
|
2167
|
-
var PERMISSION_ACCEPT_TEMPLATE = `#!/bin/
|
|
2168
|
-
|
|
2169
|
-
|
|
2217
|
+
var PERMISSION_ACCEPT_TEMPLATE = `#!/usr/bin/env node
|
|
2218
|
+
// Sessix PermissionRequest \u515C\u5E95
|
|
2219
|
+
// \u81EA\u52A8\u63A5\u53D7\u6743\u9650\u8BF7\u6C42\uFF0C\u907F\u514D Sessix \u4F1A\u8BDD\u963B\u585E
|
|
2170
2220
|
|
|
2171
|
-
if
|
|
2172
|
-
exit 0
|
|
2173
|
-
fi
|
|
2221
|
+
if (!process.env.SESSIX_SESSION_ID) process.exit(0)
|
|
2174
2222
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
exit 0
|
|
2223
|
+
process.stdout.write('{"decision":"allow"}\\n')
|
|
2224
|
+
process.exit(0)
|
|
2178
2225
|
`;
|
|
2179
2226
|
var HookInstaller = class {
|
|
2180
2227
|
/**
|
|
2181
2228
|
* 安装 hook
|
|
2182
2229
|
*
|
|
2183
2230
|
* 1. 创建 ~/.sessix/hooks/ 目录
|
|
2184
|
-
* 2. 写入 approval-hook.
|
|
2231
|
+
* 2. 写入 approval-hook.js 脚本
|
|
2185
2232
|
* 3. 赋予执行权限
|
|
2186
2233
|
* 4. 更新 Claude Code settings.json 添加 hook 配置
|
|
2187
2234
|
*/
|
|
@@ -2234,6 +2281,10 @@ var HookInstaller = class {
|
|
|
2234
2281
|
async addHookToSettings() {
|
|
2235
2282
|
let settings = await this.readClaudeSettings();
|
|
2236
2283
|
let changed = false;
|
|
2284
|
+
for (const cmd of LEGACY_HOOK_COMMANDS) {
|
|
2285
|
+
this.removeHookCommand(settings, "PreToolUse", cmd);
|
|
2286
|
+
this.removeHookCommand(settings, "PermissionRequest", cmd);
|
|
2287
|
+
}
|
|
2237
2288
|
if (!settings.hooks) {
|
|
2238
2289
|
settings.hooks = {};
|
|
2239
2290
|
}
|
|
@@ -2301,7 +2352,7 @@ var HookInstaller = class {
|
|
|
2301
2352
|
* 写入 Claude Code settings.json
|
|
2302
2353
|
*/
|
|
2303
2354
|
async writeClaudeSettings(settings) {
|
|
2304
|
-
await (0, import_promises2.mkdir)((0,
|
|
2355
|
+
await (0, import_promises2.mkdir)((0, import_node_path3.join)((0, import_node_os5.homedir)(), ".claude"), { recursive: true });
|
|
2305
2356
|
await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2306
2357
|
}
|
|
2307
2358
|
/**
|
|
@@ -2310,11 +2361,11 @@ var HookInstaller = class {
|
|
|
2310
2361
|
hasHookConfig(settings) {
|
|
2311
2362
|
return this.hasPreToolUseConfig(settings) && this.hasPermissionRequestConfig(settings);
|
|
2312
2363
|
}
|
|
2313
|
-
/** 检查 PreToolUse 中是否有 approval-hook.
|
|
2364
|
+
/** 检查 PreToolUse 中是否有 approval-hook.js */
|
|
2314
2365
|
hasPreToolUseConfig(settings) {
|
|
2315
2366
|
return this.hasHookEntry(settings?.hooks?.PreToolUse, HOOK_COMMAND);
|
|
2316
2367
|
}
|
|
2317
|
-
/** 检查 PermissionRequest 中是否有 permission-accept.
|
|
2368
|
+
/** 检查 PermissionRequest 中是否有 permission-accept.js */
|
|
2318
2369
|
hasPermissionRequestConfig(settings) {
|
|
2319
2370
|
return this.hasHookEntry(settings?.hooks?.PermissionRequest, PERMISSION_ACCEPT_COMMAND);
|
|
2320
2371
|
}
|
|
@@ -2328,7 +2379,7 @@ var HookInstaller = class {
|
|
|
2328
2379
|
};
|
|
2329
2380
|
|
|
2330
2381
|
// src/notification/NotificationService.ts
|
|
2331
|
-
var
|
|
2382
|
+
var import_node_path4 = require("path");
|
|
2332
2383
|
var NotificationService = class {
|
|
2333
2384
|
constructor(sessionManager, expoChannel = null) {
|
|
2334
2385
|
this.sessionManager = sessionManager;
|
|
@@ -2424,7 +2475,7 @@ var NotificationService = class {
|
|
|
2424
2475
|
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
2425
2476
|
const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
|
|
2426
2477
|
const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
|
|
2427
|
-
const projectName = (0,
|
|
2478
|
+
const projectName = (0, import_node_path4.basename)(
|
|
2428
2479
|
this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
|
|
2429
2480
|
);
|
|
2430
2481
|
const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
|
|
@@ -2480,7 +2531,7 @@ var NotificationService = class {
|
|
|
2480
2531
|
/** 从审批请求中提取操作目标的简短描述 */
|
|
2481
2532
|
extractTarget(request) {
|
|
2482
2533
|
const input = request.toolInput;
|
|
2483
|
-
if (input.file_path) return (0,
|
|
2534
|
+
if (input.file_path) return (0, import_node_path4.basename)(String(input.file_path));
|
|
2484
2535
|
if (input.command) return String(input.command).slice(0, 40);
|
|
2485
2536
|
return request.description.slice(0, 40);
|
|
2486
2537
|
}
|
|
@@ -2583,7 +2634,7 @@ var NotificationService = class {
|
|
|
2583
2634
|
getSessionTitle(sessionId) {
|
|
2584
2635
|
const session = this.sessionManager.getActiveSessions().find((s) => s.id === sessionId);
|
|
2585
2636
|
if (!session) return "Unknown";
|
|
2586
|
-
return session.summary ?? (0,
|
|
2637
|
+
return session.summary ?? (0, import_node_path4.basename)(session.projectPath);
|
|
2587
2638
|
}
|
|
2588
2639
|
/** 获取会话的 YOLO 模式状态 */
|
|
2589
2640
|
getYoloMode(sessionId) {
|
|
@@ -2591,9 +2642,9 @@ var NotificationService = class {
|
|
|
2591
2642
|
}
|
|
2592
2643
|
};
|
|
2593
2644
|
|
|
2594
|
-
// src/notification/
|
|
2595
|
-
var
|
|
2596
|
-
var
|
|
2645
|
+
// src/notification/DesktopNotificationChannel.ts
|
|
2646
|
+
var import_node_child_process3 = require("child_process");
|
|
2647
|
+
var DesktopNotificationChannel = class {
|
|
2597
2648
|
isAvailable() {
|
|
2598
2649
|
return process.platform === "darwin";
|
|
2599
2650
|
}
|
|
@@ -2604,9 +2655,9 @@ var MacNotificationChannel = class {
|
|
|
2604
2655
|
const sound = payload.sound ?? "Ping";
|
|
2605
2656
|
const script = `display notification "${body}" with title "${title}" sound name "${sound}"`;
|
|
2606
2657
|
return new Promise((resolve) => {
|
|
2607
|
-
(0,
|
|
2658
|
+
(0, import_node_child_process3.execFile)("osascript", ["-e", script], (err) => {
|
|
2608
2659
|
if (err) {
|
|
2609
|
-
console.warn("[
|
|
2660
|
+
console.warn("[DesktopNotificationChannel] Send notification failed:", err.message);
|
|
2610
2661
|
}
|
|
2611
2662
|
resolve();
|
|
2612
2663
|
});
|
|
@@ -3219,6 +3270,9 @@ var PairingManager = class {
|
|
|
3219
3270
|
this.close();
|
|
3220
3271
|
return result;
|
|
3221
3272
|
}
|
|
3273
|
+
updateToken(newToken) {
|
|
3274
|
+
this.token = newToken;
|
|
3275
|
+
}
|
|
3222
3276
|
getRemainingSeconds() {
|
|
3223
3277
|
if (this._state !== "open") return 0;
|
|
3224
3278
|
return Math.max(0, Math.ceil((this.deadline - Date.now()) / 1e3));
|
|
@@ -3232,11 +3286,11 @@ var PairingManager = class {
|
|
|
3232
3286
|
};
|
|
3233
3287
|
|
|
3234
3288
|
// src/auth/AuthManager.ts
|
|
3289
|
+
var import_child_process2 = require("child_process");
|
|
3235
3290
|
var import_child_process3 = require("child_process");
|
|
3236
|
-
var import_child_process4 = require("child_process");
|
|
3237
3291
|
var import_util = require("util");
|
|
3238
3292
|
var import_events2 = require("events");
|
|
3239
|
-
var execFileAsync = (0, import_util.promisify)(
|
|
3293
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3240
3294
|
var CLAUDE_PATH2 = findClaudePath();
|
|
3241
3295
|
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3242
3296
|
var AuthManager = class extends import_events2.EventEmitter {
|
|
@@ -3267,7 +3321,7 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3267
3321
|
}
|
|
3268
3322
|
this.clearLoginTimeout();
|
|
3269
3323
|
this.urlSent = false;
|
|
3270
|
-
const proc = (0,
|
|
3324
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3271
3325
|
env: { ...process.env, BROWSER: "echo" },
|
|
3272
3326
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3273
3327
|
});
|
|
@@ -3351,17 +3405,122 @@ var AuthManager = class extends import_events2.EventEmitter {
|
|
|
3351
3405
|
|
|
3352
3406
|
// src/server.ts
|
|
3353
3407
|
var import_promises5 = require("fs/promises");
|
|
3408
|
+
|
|
3409
|
+
// src/terminal/TerminalExecutor.ts
|
|
3410
|
+
var import_node_child_process4 = require("child_process");
|
|
3411
|
+
var import_uuid4 = require("uuid");
|
|
3412
|
+
var EXEC_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3413
|
+
var TerminalExecutor = class {
|
|
3414
|
+
processes = /* @__PURE__ */ new Map();
|
|
3415
|
+
eventCallbacks = [];
|
|
3416
|
+
onEvent(callback) {
|
|
3417
|
+
this.eventCallbacks.push(callback);
|
|
3418
|
+
return () => {
|
|
3419
|
+
const idx = this.eventCallbacks.indexOf(callback);
|
|
3420
|
+
if (idx !== -1) this.eventCallbacks.splice(idx, 1);
|
|
3421
|
+
};
|
|
3422
|
+
}
|
|
3423
|
+
emit(event) {
|
|
3424
|
+
for (const cb of this.eventCallbacks) {
|
|
3425
|
+
try {
|
|
3426
|
+
cb(event);
|
|
3427
|
+
} catch (err) {
|
|
3428
|
+
console.error("[TerminalExecutor] Event callback error:", err);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
exec(sessionId, command, cwd) {
|
|
3433
|
+
const execId = (0, import_uuid4.v4)();
|
|
3434
|
+
const shell = isWindows ? "powershell" : "bash";
|
|
3435
|
+
const args = isWindows ? ["-Command", command] : ["-c", command];
|
|
3436
|
+
const proc = (0, import_node_child_process4.spawn)(shell, args, {
|
|
3437
|
+
cwd,
|
|
3438
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3439
|
+
env: { ...process.env }
|
|
3440
|
+
});
|
|
3441
|
+
this.processes.set(execId, proc);
|
|
3442
|
+
proc.stdout?.on("data", (chunk) => {
|
|
3443
|
+
this.emit({
|
|
3444
|
+
type: "terminal_output",
|
|
3445
|
+
sessionId,
|
|
3446
|
+
execId,
|
|
3447
|
+
stream: "stdout",
|
|
3448
|
+
data: chunk.toString()
|
|
3449
|
+
});
|
|
3450
|
+
});
|
|
3451
|
+
proc.stderr?.on("data", (chunk) => {
|
|
3452
|
+
this.emit({
|
|
3453
|
+
type: "terminal_output",
|
|
3454
|
+
sessionId,
|
|
3455
|
+
execId,
|
|
3456
|
+
stream: "stderr",
|
|
3457
|
+
data: chunk.toString()
|
|
3458
|
+
});
|
|
3459
|
+
});
|
|
3460
|
+
proc.on("exit", (code, signal) => {
|
|
3461
|
+
clearTimeout(timer);
|
|
3462
|
+
this.processes.delete(execId);
|
|
3463
|
+
this.emit({
|
|
3464
|
+
type: "terminal_exit",
|
|
3465
|
+
sessionId,
|
|
3466
|
+
execId,
|
|
3467
|
+
code,
|
|
3468
|
+
signal
|
|
3469
|
+
});
|
|
3470
|
+
});
|
|
3471
|
+
const timer = setTimeout(() => {
|
|
3472
|
+
if (this.processes.has(execId)) {
|
|
3473
|
+
killProcessCrossPlatform(proc);
|
|
3474
|
+
}
|
|
3475
|
+
}, EXEC_TIMEOUT_MS);
|
|
3476
|
+
console.log(`[TerminalExecutor] exec ${execId}: ${command.substring(0, 100)} (cwd: ${cwd})`);
|
|
3477
|
+
return execId;
|
|
3478
|
+
}
|
|
3479
|
+
kill(execId) {
|
|
3480
|
+
const proc = this.processes.get(execId);
|
|
3481
|
+
if (proc) {
|
|
3482
|
+
killProcessCrossPlatform(proc);
|
|
3483
|
+
console.log(`[TerminalExecutor] kill ${execId}`);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
destroy() {
|
|
3487
|
+
for (const [execId, proc] of this.processes) {
|
|
3488
|
+
killProcessCrossPlatform(proc);
|
|
3489
|
+
console.log(`[TerminalExecutor] cleanup ${execId}`);
|
|
3490
|
+
}
|
|
3491
|
+
this.processes.clear();
|
|
3492
|
+
this.eventCallbacks.length = 0;
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
|
|
3496
|
+
// src/server.ts
|
|
3354
3497
|
var WS_PORT = 3745;
|
|
3355
3498
|
var HTTP_PORT = 3746;
|
|
3356
|
-
var execAsync = (0, import_node_util.promisify)(
|
|
3499
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process5.exec);
|
|
3357
3500
|
async function killPortProcess(port) {
|
|
3358
3501
|
try {
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3502
|
+
if (isWindows) {
|
|
3503
|
+
const { stdout } = await execAsync(
|
|
3504
|
+
`netstat -ano | findstr :${port} | findstr LISTENING`
|
|
3505
|
+
);
|
|
3506
|
+
const pids = /* @__PURE__ */ new Set();
|
|
3507
|
+
for (const line of stdout.trim().split("\n")) {
|
|
3508
|
+
const parts = line.trim().split(/\s+/);
|
|
3509
|
+
const pid = parts[parts.length - 1];
|
|
3510
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
3511
|
+
}
|
|
3512
|
+
for (const pid of pids) {
|
|
3513
|
+
await execAsync(`taskkill /PID ${pid} /F`).catch(() => {
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
} else {
|
|
3517
|
+
const { stdout } = await execAsync(`lsof -ti :${port}`);
|
|
3518
|
+
const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
|
|
3519
|
+
if (pids.length > 0) {
|
|
3520
|
+
await execAsync(`kill -9 ${pids.join(" ")}`);
|
|
3521
|
+
}
|
|
3364
3522
|
}
|
|
3523
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
3365
3524
|
} catch {
|
|
3366
3525
|
}
|
|
3367
3526
|
}
|
|
@@ -3379,8 +3538,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
3379
3538
|
}
|
|
3380
3539
|
}
|
|
3381
3540
|
async function start(opts = {}) {
|
|
3382
|
-
const configDir = (0,
|
|
3383
|
-
const tokenFile = (0,
|
|
3541
|
+
const configDir = (0, import_node_path5.join)((0, import_node_os6.homedir)(), ".sessix");
|
|
3542
|
+
const tokenFile = (0, import_node_path5.join)(configDir, "token");
|
|
3384
3543
|
let token;
|
|
3385
3544
|
if (opts.token !== void 0) {
|
|
3386
3545
|
token = opts.token;
|
|
@@ -3392,7 +3551,7 @@ async function start(opts = {}) {
|
|
|
3392
3551
|
try {
|
|
3393
3552
|
token = (await (0, import_promises4.readFile)(tokenFile, "utf8")).trim();
|
|
3394
3553
|
} catch {
|
|
3395
|
-
token = (0,
|
|
3554
|
+
token = (0, import_uuid5.v4)();
|
|
3396
3555
|
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
3397
3556
|
await (0, import_promises4.writeFile)(tokenFile, token, "utf8");
|
|
3398
3557
|
}
|
|
@@ -3400,10 +3559,24 @@ async function start(opts = {}) {
|
|
|
3400
3559
|
}
|
|
3401
3560
|
const provider = new ProcessProvider();
|
|
3402
3561
|
const sessionManager = new SessionManager(provider);
|
|
3562
|
+
const terminalExecutor = new TerminalExecutor();
|
|
3563
|
+
const wsBridge = await createWithRetry(
|
|
3564
|
+
"WsBridge",
|
|
3565
|
+
WS_PORT,
|
|
3566
|
+
() => WsBridge.create({ port: WS_PORT, token })
|
|
3567
|
+
);
|
|
3568
|
+
const unreadSessionIds = /* @__PURE__ */ new Set();
|
|
3569
|
+
sessionManager.onEvent((event) => {
|
|
3570
|
+
if (event.type === "status_change" && (event.status === "idle" || event.status === "error")) {
|
|
3571
|
+
if (!wsBridge.isViewingSession(event.sessionId)) {
|
|
3572
|
+
unreadSessionIds.add(event.sessionId);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
});
|
|
3403
3576
|
const expoChannel = new ExpoNotificationChannel();
|
|
3404
3577
|
const notificationService = new NotificationService(sessionManager, expoChannel);
|
|
3405
3578
|
notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
|
|
3406
|
-
notificationService.addChannel("mac", new
|
|
3579
|
+
notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
|
|
3407
3580
|
if (opts.activityPush) {
|
|
3408
3581
|
try {
|
|
3409
3582
|
const activityChannel = new ActivityPushChannel(opts.activityPush);
|
|
@@ -3414,11 +3587,6 @@ async function start(opts = {}) {
|
|
|
3414
3587
|
console.log(`[Server] ${t("server.activityPushContinue")}`);
|
|
3415
3588
|
}
|
|
3416
3589
|
}
|
|
3417
|
-
const wsBridge = await createWithRetry(
|
|
3418
|
-
"WsBridge",
|
|
3419
|
-
WS_PORT,
|
|
3420
|
-
() => WsBridge.create({ port: WS_PORT, token })
|
|
3421
|
-
);
|
|
3422
3590
|
const sessionFileWatcher = new SessionFileWatcher((event) => {
|
|
3423
3591
|
wsBridge.broadcast(event);
|
|
3424
3592
|
});
|
|
@@ -3430,7 +3598,7 @@ async function start(opts = {}) {
|
|
|
3430
3598
|
let mdnsService = null;
|
|
3431
3599
|
const pairingManager = new PairingManager({
|
|
3432
3600
|
token,
|
|
3433
|
-
serverName: (0,
|
|
3601
|
+
serverName: (0, import_node_os6.hostname)(),
|
|
3434
3602
|
version: "0.2.0",
|
|
3435
3603
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3436
3604
|
});
|
|
@@ -3447,7 +3615,6 @@ async function start(opts = {}) {
|
|
|
3447
3615
|
});
|
|
3448
3616
|
}
|
|
3449
3617
|
});
|
|
3450
|
-
const unreadSessionIds = /* @__PURE__ */ new Set();
|
|
3451
3618
|
notificationService.setGlobalPendingCountProvider(
|
|
3452
3619
|
() => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
|
|
3453
3620
|
);
|
|
@@ -3478,6 +3645,8 @@ async function start(opts = {}) {
|
|
|
3478
3645
|
switch (event.type) {
|
|
3479
3646
|
case "create_session": {
|
|
3480
3647
|
await (0, import_promises4.mkdir)(event.projectPath, { recursive: true });
|
|
3648
|
+
const resumeId = event.resumeSessionId ?? event.newSessionId;
|
|
3649
|
+
if (resumeId) sessionFileWatcher.unwatch(resumeId);
|
|
3481
3650
|
await sessionManager.createSession(
|
|
3482
3651
|
event.projectPath,
|
|
3483
3652
|
event.message,
|
|
@@ -3495,6 +3664,7 @@ async function start(opts = {}) {
|
|
|
3495
3664
|
break;
|
|
3496
3665
|
}
|
|
3497
3666
|
case "send_message": {
|
|
3667
|
+
sessionFileWatcher.unwatch(event.sessionId);
|
|
3498
3668
|
await sessionManager.sendMessage(event.sessionId, event.message, event.permissionMode, event.images);
|
|
3499
3669
|
wsBridge.broadcast({
|
|
3500
3670
|
type: "session_list",
|
|
@@ -3583,6 +3753,10 @@ async function start(opts = {}) {
|
|
|
3583
3753
|
code: "PROJECT_LIST_ERROR"
|
|
3584
3754
|
});
|
|
3585
3755
|
}
|
|
3756
|
+
wsBridge.send(ws, {
|
|
3757
|
+
type: "session_list",
|
|
3758
|
+
sessions: sessionManager.getActiveSessions()
|
|
3759
|
+
});
|
|
3586
3760
|
break;
|
|
3587
3761
|
}
|
|
3588
3762
|
case "list_sessions": {
|
|
@@ -3677,6 +3851,20 @@ async function start(opts = {}) {
|
|
|
3677
3851
|
notificationService.setSoundPreferences(event.preferences);
|
|
3678
3852
|
break;
|
|
3679
3853
|
}
|
|
3854
|
+
case "terminal_exec": {
|
|
3855
|
+
const activeSession = sessionManager.getActiveSessions().find((s) => s.id === event.sessionId);
|
|
3856
|
+
const cwd = activeSession?.projectPath ?? sessionManager.getSessionProjectPath(event.sessionId);
|
|
3857
|
+
if (!cwd) {
|
|
3858
|
+
wsBridge.send(ws, { type: "error", code: "TERMINAL_EXEC_ERROR", message: "Session not found or no project path", sessionId: event.sessionId });
|
|
3859
|
+
break;
|
|
3860
|
+
}
|
|
3861
|
+
terminalExecutor.exec(event.sessionId, event.command, cwd);
|
|
3862
|
+
break;
|
|
3863
|
+
}
|
|
3864
|
+
case "terminal_kill": {
|
|
3865
|
+
terminalExecutor.kill(event.execId);
|
|
3866
|
+
break;
|
|
3867
|
+
}
|
|
3680
3868
|
case "register_activity_push_token": {
|
|
3681
3869
|
notificationService.addActivityPushToken(event.sessionId, event.token);
|
|
3682
3870
|
break;
|
|
@@ -3748,12 +3936,14 @@ async function start(opts = {}) {
|
|
|
3748
3936
|
sessionManager.onEvent((event) => {
|
|
3749
3937
|
wsBridge.broadcast(event);
|
|
3750
3938
|
if (event.type === "status_change" && (event.status === "idle" || event.status === "error")) {
|
|
3751
|
-
if (
|
|
3752
|
-
unreadSessionIds.add(event.sessionId);
|
|
3939
|
+
if (unreadSessionIds.has(event.sessionId)) {
|
|
3753
3940
|
broadcastUnreadSessions();
|
|
3754
3941
|
}
|
|
3755
3942
|
}
|
|
3756
3943
|
});
|
|
3944
|
+
terminalExecutor.onEvent((event) => {
|
|
3945
|
+
wsBridge.broadcast(event);
|
|
3946
|
+
});
|
|
3757
3947
|
wsBridge.onDisconnect(() => {
|
|
3758
3948
|
if (wsBridge.getConnectionCount() === 0 && approvalProxy.getPendingCount() > 0) {
|
|
3759
3949
|
approvalProxy.approveAll(t("server.phoneDisconnected"));
|
|
@@ -3800,12 +3990,17 @@ async function start(opts = {}) {
|
|
|
3800
3990
|
}));
|
|
3801
3991
|
const startMdns = () => {
|
|
3802
3992
|
if (mdnsService) return;
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3993
|
+
try {
|
|
3994
|
+
mdnsService = new MdnsService({
|
|
3995
|
+
wsPort: WS_PORT,
|
|
3996
|
+
httpPort: HTTP_PORT,
|
|
3997
|
+
pairing: pairingManager.state
|
|
3998
|
+
});
|
|
3999
|
+
mdnsService.start();
|
|
4000
|
+
} catch (err) {
|
|
4001
|
+
console.warn(`[Server] mDNS failed to start (non-fatal): ${err.message}`);
|
|
4002
|
+
mdnsService = null;
|
|
4003
|
+
}
|
|
3809
4004
|
};
|
|
3810
4005
|
const stopMdns = () => {
|
|
3811
4006
|
if (!mdnsService) return;
|
|
@@ -3848,6 +4043,7 @@ async function start(opts = {}) {
|
|
|
3848
4043
|
await attempt(() => wsBridge.close(), "WebSocket");
|
|
3849
4044
|
await attempt(() => approvalProxy.close(), "ApprovalProxy");
|
|
3850
4045
|
await attempt(() => sessionManager.destroy(), "SessionManager");
|
|
4046
|
+
await attempt(() => terminalExecutor.destroy(), "TerminalExecutor");
|
|
3851
4047
|
await attempt(() => notificationService.destroy(), "NotificationService");
|
|
3852
4048
|
await attempt(() => sessionFileWatcher.destroy(), "SessionFileWatcher");
|
|
3853
4049
|
if (errors.length > 0) {
|
|
@@ -3856,7 +4052,7 @@ async function start(opts = {}) {
|
|
|
3856
4052
|
}
|
|
3857
4053
|
console.log(`[Server] ${t("server.shutdownComplete")}`);
|
|
3858
4054
|
};
|
|
3859
|
-
|
|
4055
|
+
const instance = {
|
|
3860
4056
|
token,
|
|
3861
4057
|
wsPort: WS_PORT,
|
|
3862
4058
|
httpPort: HTTP_PORT,
|
|
@@ -3874,8 +4070,21 @@ async function start(opts = {}) {
|
|
|
3874
4070
|
}
|
|
3875
4071
|
},
|
|
3876
4072
|
openPairing: (duration) => pairingManager.open(duration),
|
|
3877
|
-
closePairing: () => pairingManager.close()
|
|
4073
|
+
closePairing: () => pairingManager.close(),
|
|
4074
|
+
regenerateToken: async () => {
|
|
4075
|
+
const newToken = (0, import_uuid5.v4)();
|
|
4076
|
+
await (0, import_promises4.mkdir)(configDir, { recursive: true });
|
|
4077
|
+
await (0, import_promises4.writeFile)(tokenFile, newToken, "utf8");
|
|
4078
|
+
instance.token = newToken;
|
|
4079
|
+
wsBridge.updateToken(newToken);
|
|
4080
|
+
approvalProxy.updateToken(newToken);
|
|
4081
|
+
pairingManager.updateToken(newToken);
|
|
4082
|
+
pairingManager.open();
|
|
4083
|
+
console.log(`[Server] ${t("server.tokenRegenerated", { token: newToken })}`);
|
|
4084
|
+
return newToken;
|
|
4085
|
+
}
|
|
3878
4086
|
};
|
|
4087
|
+
return instance;
|
|
3879
4088
|
}
|
|
3880
4089
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3881
4090
|
0 && (module.exports = {
|