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