sessix-server 0.2.2 → 0.2.4
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 +83 -22
- package/dist/server.js +81 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ 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
28
|
var import_node_fs2 = require("fs");
|
|
29
29
|
var import_node_path5 = require("path");
|
|
30
30
|
|
|
@@ -85,7 +85,8 @@ var zh = {
|
|
|
85
85
|
alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
|
|
86
86
|
started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
|
|
87
87
|
stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
|
|
88
|
-
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}}"
|
|
89
90
|
},
|
|
90
91
|
approval: {
|
|
91
92
|
httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
|
|
@@ -190,7 +191,8 @@ var en = {
|
|
|
190
191
|
alreadyRunning: "Service is already running",
|
|
191
192
|
started: "mDNS broadcast started: _sessix._tcp port {{port}}",
|
|
192
193
|
stopped: "Service broadcast stopped",
|
|
193
|
-
closed: "mDNS service closed"
|
|
194
|
+
closed: "mDNS service closed",
|
|
195
|
+
boundInterface: "mDNS bound to LAN interface {{ip}}"
|
|
194
196
|
},
|
|
195
197
|
approval: {
|
|
196
198
|
httpStarted: "HTTP approval server started on port {{port}}",
|
|
@@ -287,7 +289,7 @@ function t(key, params) {
|
|
|
287
289
|
// src/server.ts
|
|
288
290
|
var import_uuid4 = require("uuid");
|
|
289
291
|
var import_promises4 = require("fs/promises");
|
|
290
|
-
var
|
|
292
|
+
var import_node_os5 = require("os");
|
|
291
293
|
var import_node_path4 = require("path");
|
|
292
294
|
var import_node_child_process2 = require("child_process");
|
|
293
295
|
var import_node_util = require("util");
|
|
@@ -328,8 +330,8 @@ var ProcessProvider = class {
|
|
|
328
330
|
activeSessions = /* @__PURE__ */ new Map();
|
|
329
331
|
/** 事件发射器,用于分发 Claude 事件流 */
|
|
330
332
|
emitter = new import_events.EventEmitter();
|
|
331
|
-
/** 已发射的 AskUserQuestion toolUseId
|
|
332
|
-
emittedQuestionToolUseIds = /* @__PURE__ */ new
|
|
333
|
+
/** 已发射的 AskUserQuestion toolUseId 集合,按会话隔离(避免 partial message 重复触发) */
|
|
334
|
+
emittedQuestionToolUseIds = /* @__PURE__ */ new Map();
|
|
333
335
|
/**
|
|
334
336
|
* 启动新会话或恢复已有会话
|
|
335
337
|
*
|
|
@@ -405,6 +407,7 @@ var ProcessProvider = class {
|
|
|
405
407
|
});
|
|
406
408
|
});
|
|
407
409
|
}
|
|
410
|
+
this.emittedQuestionToolUseIds.delete(sessionId);
|
|
408
411
|
this.activeSessions.delete(sessionId);
|
|
409
412
|
}
|
|
410
413
|
/**
|
|
@@ -601,8 +604,13 @@ var ProcessProvider = class {
|
|
|
601
604
|
const question = input.question ?? "";
|
|
602
605
|
if (!question) continue;
|
|
603
606
|
const prevKey = `${block.id}:${question}:${JSON.stringify(input.options ?? [])}`;
|
|
604
|
-
|
|
605
|
-
|
|
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);
|
|
606
614
|
this.emitter.emit(this.getQuestionEventName(sessionId), {
|
|
607
615
|
toolUseId: block.id,
|
|
608
616
|
question,
|
|
@@ -1105,6 +1113,7 @@ var SessionManager = class {
|
|
|
1105
1113
|
}
|
|
1106
1114
|
/**
|
|
1107
1115
|
* 刷新缓冲的 assistant 事件,批量发送
|
|
1116
|
+
* Public:subscribe 时需要在读取缓冲区前先刷出,避免事件同时出现在 session_history 和广播中
|
|
1108
1117
|
*/
|
|
1109
1118
|
flushPendingAssistant(sessionId) {
|
|
1110
1119
|
const pending = this.pendingAssistantEvents.get(sessionId);
|
|
@@ -1706,6 +1715,17 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1706
1715
|
isPending(requestId) {
|
|
1707
1716
|
return this.pendingApprovals.has(requestId);
|
|
1708
1717
|
}
|
|
1718
|
+
/** 清理指定会话的所有待处理审批(会话被 kill 时调用,默认放行避免阻塞 hook 脚本) */
|
|
1719
|
+
clearPendingForSession(sessionId) {
|
|
1720
|
+
for (const [requestId, pending] of this.pendingApprovals) {
|
|
1721
|
+
if (pending.request.sessionId === sessionId) {
|
|
1722
|
+
clearTimeout(pending.timer);
|
|
1723
|
+
pending.resolve({ decision: "allow" });
|
|
1724
|
+
this.pendingApprovals.delete(requestId);
|
|
1725
|
+
console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1709
1729
|
/** 检查工具是否已被"始终允许"(内存缓存 + settings.json 双重检查) */
|
|
1710
1730
|
isToolAlwaysAllowed(toolName, projectPath) {
|
|
1711
1731
|
if (this.alwaysAllowedTools.has(toolName)) return true;
|
|
@@ -1984,6 +2004,19 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1984
2004
|
|
|
1985
2005
|
// src/mdns/MdnsService.ts
|
|
1986
2006
|
var import_bonjour_service = __toESM(require("bonjour-service"));
|
|
2007
|
+
var import_node_os3 = require("os");
|
|
2008
|
+
function getLanAddresses() {
|
|
2009
|
+
const results = [];
|
|
2010
|
+
for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
|
|
2011
|
+
if (name.startsWith("utun") || name.startsWith("lo")) continue;
|
|
2012
|
+
for (const addr of addrs ?? []) {
|
|
2013
|
+
if (addr.family === "IPv4" && !addr.internal) {
|
|
2014
|
+
results.push(addr.address);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
return results;
|
|
2019
|
+
}
|
|
1987
2020
|
var MdnsService = class {
|
|
1988
2021
|
bonjour = null;
|
|
1989
2022
|
service = null;
|
|
@@ -2005,7 +2038,11 @@ var MdnsService = class {
|
|
|
2005
2038
|
console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
|
|
2006
2039
|
return;
|
|
2007
2040
|
}
|
|
2008
|
-
|
|
2041
|
+
const lanAddrs = getLanAddresses();
|
|
2042
|
+
this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
|
|
2043
|
+
if (lanAddrs.length > 0) {
|
|
2044
|
+
console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
|
|
2045
|
+
}
|
|
2009
2046
|
this.service = this.bonjour.publish({
|
|
2010
2047
|
name: "Sessix",
|
|
2011
2048
|
type: "sessix",
|
|
@@ -2068,11 +2105,11 @@ var MdnsService = class {
|
|
|
2068
2105
|
// src/hooks/HookInstaller.ts
|
|
2069
2106
|
var import_promises2 = require("fs/promises");
|
|
2070
2107
|
var import_node_path2 = require("path");
|
|
2071
|
-
var
|
|
2072
|
-
var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0,
|
|
2108
|
+
var import_node_os4 = require("os");
|
|
2109
|
+
var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
|
|
2073
2110
|
var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
|
|
2074
2111
|
var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
|
|
2075
|
-
var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0,
|
|
2112
|
+
var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
|
|
2076
2113
|
var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
|
|
2077
2114
|
var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
|
|
2078
2115
|
var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
|
|
@@ -2093,7 +2130,7 @@ PROJECT_PATH="$PWD"
|
|
|
2093
2130
|
RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
|
|
2094
2131
|
-H "Content-Type: application/json" \\
|
|
2095
2132
|
-d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
|
|
2096
|
-
--max-time
|
|
2133
|
+
--max-time 320 \\
|
|
2097
2134
|
2>/dev/null)
|
|
2098
2135
|
|
|
2099
2136
|
if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
|
|
@@ -2252,7 +2289,7 @@ var HookInstaller = class {
|
|
|
2252
2289
|
* 写入 Claude Code settings.json
|
|
2253
2290
|
*/
|
|
2254
2291
|
async writeClaudeSettings(settings) {
|
|
2255
|
-
await (0, import_promises2.mkdir)((0, import_node_path2.join)((0,
|
|
2292
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
|
|
2256
2293
|
await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2257
2294
|
}
|
|
2258
2295
|
/**
|
|
@@ -2350,7 +2387,7 @@ var NotificationService = class {
|
|
|
2350
2387
|
const title = pendingCount > 1 ? t("notification.pendingApprovals", { title: sessionTitle, count: pendingCount }) : sessionTitle;
|
|
2351
2388
|
const body = pendingCount > 1 ? `\u{1F527} \u6700\u65B0: ${request.toolName}: ${request.description}` : `\u{1F527} ${request.toolName}: ${request.description}`;
|
|
2352
2389
|
if (this.activityPushChannel?.hasToken(request.sessionId)) {
|
|
2353
|
-
const
|
|
2390
|
+
const dangerLevel2 = this.getDangerLevel(request.toolName);
|
|
2354
2391
|
const isYoloMode = this.getYoloMode(request.sessionId);
|
|
2355
2392
|
this.activityPushChannel.updateActivityWithAlert(
|
|
2356
2393
|
request.sessionId,
|
|
@@ -2362,7 +2399,7 @@ var NotificationService = class {
|
|
|
2362
2399
|
requestId: request.id,
|
|
2363
2400
|
toolName: request.toolName,
|
|
2364
2401
|
description: request.description.slice(0, 80),
|
|
2365
|
-
dangerLevel,
|
|
2402
|
+
dangerLevel: dangerLevel2,
|
|
2366
2403
|
pendingCount
|
|
2367
2404
|
},
|
|
2368
2405
|
isYoloMode,
|
|
@@ -2372,15 +2409,28 @@ var NotificationService = class {
|
|
|
2372
2409
|
);
|
|
2373
2410
|
return;
|
|
2374
2411
|
}
|
|
2412
|
+
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
2413
|
+
const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
|
|
2414
|
+
const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
|
|
2415
|
+
const projectName = (0, import_node_path3.basename)(
|
|
2416
|
+
this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
|
|
2417
|
+
);
|
|
2418
|
+
const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
|
|
2419
|
+
const subtitle = `\u{1F527} ${request.toolName}: ${this.extractTarget(request)}`;
|
|
2375
2420
|
this.notify({
|
|
2376
|
-
title,
|
|
2421
|
+
title: pushTitle,
|
|
2422
|
+
subtitle,
|
|
2377
2423
|
body,
|
|
2378
2424
|
sound: "default",
|
|
2379
2425
|
badge: this.getGlobalPendingCount(),
|
|
2426
|
+
categoryId,
|
|
2380
2427
|
data: {
|
|
2381
2428
|
type: "approval_request",
|
|
2382
2429
|
sessionId: request.sessionId,
|
|
2383
|
-
requestId: request.id
|
|
2430
|
+
requestId: request.id,
|
|
2431
|
+
toolName: request.toolName,
|
|
2432
|
+
projectName,
|
|
2433
|
+
dangerLevel
|
|
2384
2434
|
}
|
|
2385
2435
|
});
|
|
2386
2436
|
}
|
|
@@ -2415,6 +2465,13 @@ var NotificationService = class {
|
|
|
2415
2465
|
}
|
|
2416
2466
|
});
|
|
2417
2467
|
}
|
|
2468
|
+
/** 从审批请求中提取操作目标的简短描述 */
|
|
2469
|
+
extractTarget(request) {
|
|
2470
|
+
const input = request.toolInput;
|
|
2471
|
+
if (input.file_path) return (0, import_node_path3.basename)(String(input.file_path));
|
|
2472
|
+
if (input.command) return String(input.command).slice(0, 40);
|
|
2473
|
+
return request.description.slice(0, 40);
|
|
2474
|
+
}
|
|
2418
2475
|
/** 简单的工具危险等级判断 */
|
|
2419
2476
|
getDangerLevel(toolName) {
|
|
2420
2477
|
if (toolName === "Bash") return "danger";
|
|
@@ -2594,9 +2651,11 @@ var ExpoNotificationChannel = class {
|
|
|
2594
2651
|
return {
|
|
2595
2652
|
to,
|
|
2596
2653
|
title: payload.title,
|
|
2654
|
+
subtitle: payload.subtitle,
|
|
2597
2655
|
body: payload.body,
|
|
2598
2656
|
badge: payload.badge,
|
|
2599
2657
|
sound: pushSound,
|
|
2658
|
+
categoryId: payload.categoryId,
|
|
2600
2659
|
data: payload.data ?? {}
|
|
2601
2660
|
};
|
|
2602
2661
|
});
|
|
@@ -3308,7 +3367,7 @@ async function createWithRetry(label, port, factory) {
|
|
|
3308
3367
|
}
|
|
3309
3368
|
}
|
|
3310
3369
|
async function start(opts = {}) {
|
|
3311
|
-
const configDir = (0, import_node_path4.join)((0,
|
|
3370
|
+
const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
|
|
3312
3371
|
const tokenFile = (0, import_node_path4.join)(configDir, "token");
|
|
3313
3372
|
let token;
|
|
3314
3373
|
if (opts.token !== void 0) {
|
|
@@ -3359,7 +3418,7 @@ async function start(opts = {}) {
|
|
|
3359
3418
|
let mdnsService = null;
|
|
3360
3419
|
const pairingManager = new PairingManager({
|
|
3361
3420
|
token,
|
|
3362
|
-
serverName: (0,
|
|
3421
|
+
serverName: (0, import_node_os5.hostname)(),
|
|
3363
3422
|
version: "0.2.0",
|
|
3364
3423
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3365
3424
|
});
|
|
@@ -3433,6 +3492,7 @@ async function start(opts = {}) {
|
|
|
3433
3492
|
}
|
|
3434
3493
|
case "kill_session": {
|
|
3435
3494
|
wsBridge.broadcast({ type: "status_change", sessionId: event.sessionId, status: "idle" });
|
|
3495
|
+
approvalProxy.clearPendingForSession(event.sessionId);
|
|
3436
3496
|
await sessionManager.killSession(event.sessionId);
|
|
3437
3497
|
wsBridge.broadcast({
|
|
3438
3498
|
type: "session_list",
|
|
@@ -3458,7 +3518,8 @@ async function start(opts = {}) {
|
|
|
3458
3518
|
type: "session_list",
|
|
3459
3519
|
sessions: sessionManager.getActiveSessions()
|
|
3460
3520
|
});
|
|
3461
|
-
|
|
3521
|
+
sessionManager.flushPendingAssistant(event.sessionId);
|
|
3522
|
+
const bufferedEvents = [...sessionManager.getSessionEvents(event.sessionId)];
|
|
3462
3523
|
if (sessionManager.isBufferTruncated(event.sessionId)) {
|
|
3463
3524
|
const projectPath = sessionManager.getSessionProjectPath(event.sessionId);
|
|
3464
3525
|
if (projectPath) {
|
|
@@ -3914,7 +3975,7 @@ ${t("startup.pairingReopened")}`);
|
|
|
3914
3975
|
}
|
|
3915
3976
|
}
|
|
3916
3977
|
function getLocalIp() {
|
|
3917
|
-
const interfaces = (0,
|
|
3978
|
+
const interfaces = (0, import_node_os6.networkInterfaces)();
|
|
3918
3979
|
for (const iface of Object.values(interfaces)) {
|
|
3919
3980
|
for (const addr of iface ?? []) {
|
|
3920
3981
|
if (addr.family === "IPv4" && !addr.internal) {
|
package/dist/server.js
CHANGED
|
@@ -91,7 +91,8 @@ var zh = {
|
|
|
91
91
|
alreadyRunning: "\u670D\u52A1\u5DF2\u5728\u8FD0\u884C\u4E2D",
|
|
92
92
|
started: "mDNS \u5E7F\u64AD\u5DF2\u542F\u52A8: _sessix._tcp \u7AEF\u53E3 {{port}}",
|
|
93
93
|
stopped: "\u670D\u52A1\u5E7F\u64AD\u5DF2\u505C\u6B62",
|
|
94
|
-
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}}"
|
|
95
96
|
},
|
|
96
97
|
approval: {
|
|
97
98
|
httpStarted: "HTTP \u5BA1\u6279\u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
|
|
@@ -196,7 +197,8 @@ var en = {
|
|
|
196
197
|
alreadyRunning: "Service is already running",
|
|
197
198
|
started: "mDNS broadcast started: _sessix._tcp port {{port}}",
|
|
198
199
|
stopped: "Service broadcast stopped",
|
|
199
|
-
closed: "mDNS service closed"
|
|
200
|
+
closed: "mDNS service closed",
|
|
201
|
+
boundInterface: "mDNS bound to LAN interface {{ip}}"
|
|
200
202
|
},
|
|
201
203
|
approval: {
|
|
202
204
|
httpStarted: "HTTP approval server started on port {{port}}",
|
|
@@ -293,7 +295,7 @@ function t(key, params) {
|
|
|
293
295
|
// src/server.ts
|
|
294
296
|
var import_uuid4 = require("uuid");
|
|
295
297
|
var import_promises4 = require("fs/promises");
|
|
296
|
-
var
|
|
298
|
+
var import_node_os5 = require("os");
|
|
297
299
|
var import_node_path4 = require("path");
|
|
298
300
|
var import_node_child_process2 = require("child_process");
|
|
299
301
|
var import_node_util = require("util");
|
|
@@ -334,8 +336,8 @@ var ProcessProvider = class {
|
|
|
334
336
|
activeSessions = /* @__PURE__ */ new Map();
|
|
335
337
|
/** 事件发射器,用于分发 Claude 事件流 */
|
|
336
338
|
emitter = new import_events.EventEmitter();
|
|
337
|
-
/** 已发射的 AskUserQuestion toolUseId
|
|
338
|
-
emittedQuestionToolUseIds = /* @__PURE__ */ new
|
|
339
|
+
/** 已发射的 AskUserQuestion toolUseId 集合,按会话隔离(避免 partial message 重复触发) */
|
|
340
|
+
emittedQuestionToolUseIds = /* @__PURE__ */ new Map();
|
|
339
341
|
/**
|
|
340
342
|
* 启动新会话或恢复已有会话
|
|
341
343
|
*
|
|
@@ -411,6 +413,7 @@ var ProcessProvider = class {
|
|
|
411
413
|
});
|
|
412
414
|
});
|
|
413
415
|
}
|
|
416
|
+
this.emittedQuestionToolUseIds.delete(sessionId);
|
|
414
417
|
this.activeSessions.delete(sessionId);
|
|
415
418
|
}
|
|
416
419
|
/**
|
|
@@ -607,8 +610,13 @@ var ProcessProvider = class {
|
|
|
607
610
|
const question = input.question ?? "";
|
|
608
611
|
if (!question) continue;
|
|
609
612
|
const prevKey = `${block.id}:${question}:${JSON.stringify(input.options ?? [])}`;
|
|
610
|
-
|
|
611
|
-
|
|
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);
|
|
612
620
|
this.emitter.emit(this.getQuestionEventName(sessionId), {
|
|
613
621
|
toolUseId: block.id,
|
|
614
622
|
question,
|
|
@@ -1111,6 +1119,7 @@ var SessionManager = class {
|
|
|
1111
1119
|
}
|
|
1112
1120
|
/**
|
|
1113
1121
|
* 刷新缓冲的 assistant 事件,批量发送
|
|
1122
|
+
* Public:subscribe 时需要在读取缓冲区前先刷出,避免事件同时出现在 session_history 和广播中
|
|
1114
1123
|
*/
|
|
1115
1124
|
flushPendingAssistant(sessionId) {
|
|
1116
1125
|
const pending = this.pendingAssistantEvents.get(sessionId);
|
|
@@ -1712,6 +1721,17 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1712
1721
|
isPending(requestId) {
|
|
1713
1722
|
return this.pendingApprovals.has(requestId);
|
|
1714
1723
|
}
|
|
1724
|
+
/** 清理指定会话的所有待处理审批(会话被 kill 时调用,默认放行避免阻塞 hook 脚本) */
|
|
1725
|
+
clearPendingForSession(sessionId) {
|
|
1726
|
+
for (const [requestId, pending] of this.pendingApprovals) {
|
|
1727
|
+
if (pending.request.sessionId === sessionId) {
|
|
1728
|
+
clearTimeout(pending.timer);
|
|
1729
|
+
pending.resolve({ decision: "allow" });
|
|
1730
|
+
this.pendingApprovals.delete(requestId);
|
|
1731
|
+
console.log(`[ApprovalProxy] Session ${sessionId} killed, auto-allowed pending approval ${requestId}`);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1715
1735
|
/** 检查工具是否已被"始终允许"(内存缓存 + settings.json 双重检查) */
|
|
1716
1736
|
isToolAlwaysAllowed(toolName, projectPath) {
|
|
1717
1737
|
if (this.alwaysAllowedTools.has(toolName)) return true;
|
|
@@ -1990,6 +2010,19 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1990
2010
|
|
|
1991
2011
|
// src/mdns/MdnsService.ts
|
|
1992
2012
|
var import_bonjour_service = __toESM(require("bonjour-service"));
|
|
2013
|
+
var import_node_os3 = require("os");
|
|
2014
|
+
function getLanAddresses() {
|
|
2015
|
+
const results = [];
|
|
2016
|
+
for (const [name, addrs] of Object.entries((0, import_node_os3.networkInterfaces)())) {
|
|
2017
|
+
if (name.startsWith("utun") || name.startsWith("lo")) continue;
|
|
2018
|
+
for (const addr of addrs ?? []) {
|
|
2019
|
+
if (addr.family === "IPv4" && !addr.internal) {
|
|
2020
|
+
results.push(addr.address);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return results;
|
|
2025
|
+
}
|
|
1993
2026
|
var MdnsService = class {
|
|
1994
2027
|
bonjour = null;
|
|
1995
2028
|
service = null;
|
|
@@ -2011,7 +2044,11 @@ var MdnsService = class {
|
|
|
2011
2044
|
console.warn(`[MdnsService] ${t("mdns.alreadyRunning")}`);
|
|
2012
2045
|
return;
|
|
2013
2046
|
}
|
|
2014
|
-
|
|
2047
|
+
const lanAddrs = getLanAddresses();
|
|
2048
|
+
this.bonjour = lanAddrs.length > 0 ? new import_bonjour_service.default({ interface: lanAddrs[0] }) : new import_bonjour_service.default();
|
|
2049
|
+
if (lanAddrs.length > 0) {
|
|
2050
|
+
console.log(`[MdnsService] ${t("mdns.boundInterface", { ip: lanAddrs[0] })}`);
|
|
2051
|
+
}
|
|
2015
2052
|
this.service = this.bonjour.publish({
|
|
2016
2053
|
name: "Sessix",
|
|
2017
2054
|
type: "sessix",
|
|
@@ -2074,11 +2111,11 @@ var MdnsService = class {
|
|
|
2074
2111
|
// src/hooks/HookInstaller.ts
|
|
2075
2112
|
var import_promises2 = require("fs/promises");
|
|
2076
2113
|
var import_node_path2 = require("path");
|
|
2077
|
-
var
|
|
2078
|
-
var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0,
|
|
2114
|
+
var import_node_os4 = require("os");
|
|
2115
|
+
var SESSIX_HOOKS_DIR = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".sessix", "hooks");
|
|
2079
2116
|
var HOOK_SCRIPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "approval-hook.sh");
|
|
2080
2117
|
var PERMISSION_ACCEPT_PATH = (0, import_node_path2.join)(SESSIX_HOOKS_DIR, "permission-accept.sh");
|
|
2081
|
-
var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0,
|
|
2118
|
+
var CLAUDE_SETTINGS_PATH = (0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude", "settings.json");
|
|
2082
2119
|
var HOOK_COMMAND = "~/.sessix/hooks/approval-hook.sh";
|
|
2083
2120
|
var PERMISSION_ACCEPT_COMMAND = "~/.sessix/hooks/permission-accept.sh";
|
|
2084
2121
|
var HOOK_SCRIPT_TEMPLATE = `#!/bin/bash
|
|
@@ -2099,7 +2136,7 @@ PROJECT_PATH="$PWD"
|
|
|
2099
2136
|
RESPONSE=$(curl -s -X POST "http://localhost:3746/hook/approval" \\
|
|
2100
2137
|
-H "Content-Type: application/json" \\
|
|
2101
2138
|
-d "{\\"sessionId\\": \\"$SESSIX_SESSION_ID\\", \\"projectPath\\": \\"$PROJECT_PATH\\", \\"payload\\": $PAYLOAD}" \\
|
|
2102
|
-
--max-time
|
|
2139
|
+
--max-time 320 \\
|
|
2103
2140
|
2>/dev/null)
|
|
2104
2141
|
|
|
2105
2142
|
if [ $? -ne 0 ] || [ -z "$RESPONSE" ]; then
|
|
@@ -2258,7 +2295,7 @@ var HookInstaller = class {
|
|
|
2258
2295
|
* 写入 Claude Code settings.json
|
|
2259
2296
|
*/
|
|
2260
2297
|
async writeClaudeSettings(settings) {
|
|
2261
|
-
await (0, import_promises2.mkdir)((0, import_node_path2.join)((0,
|
|
2298
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.join)((0, import_node_os4.homedir)(), ".claude"), { recursive: true });
|
|
2262
2299
|
await (0, import_promises2.writeFile)(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
2263
2300
|
}
|
|
2264
2301
|
/**
|
|
@@ -2356,7 +2393,7 @@ var NotificationService = class {
|
|
|
2356
2393
|
const title = pendingCount > 1 ? t("notification.pendingApprovals", { title: sessionTitle, count: pendingCount }) : sessionTitle;
|
|
2357
2394
|
const body = pendingCount > 1 ? `\u{1F527} \u6700\u65B0: ${request.toolName}: ${request.description}` : `\u{1F527} ${request.toolName}: ${request.description}`;
|
|
2358
2395
|
if (this.activityPushChannel?.hasToken(request.sessionId)) {
|
|
2359
|
-
const
|
|
2396
|
+
const dangerLevel2 = this.getDangerLevel(request.toolName);
|
|
2360
2397
|
const isYoloMode = this.getYoloMode(request.sessionId);
|
|
2361
2398
|
this.activityPushChannel.updateActivityWithAlert(
|
|
2362
2399
|
request.sessionId,
|
|
@@ -2368,7 +2405,7 @@ var NotificationService = class {
|
|
|
2368
2405
|
requestId: request.id,
|
|
2369
2406
|
toolName: request.toolName,
|
|
2370
2407
|
description: request.description.slice(0, 80),
|
|
2371
|
-
dangerLevel,
|
|
2408
|
+
dangerLevel: dangerLevel2,
|
|
2372
2409
|
pendingCount
|
|
2373
2410
|
},
|
|
2374
2411
|
isYoloMode,
|
|
@@ -2378,15 +2415,28 @@ var NotificationService = class {
|
|
|
2378
2415
|
);
|
|
2379
2416
|
return;
|
|
2380
2417
|
}
|
|
2418
|
+
const dangerLevel = this.getDangerLevel(request.toolName);
|
|
2419
|
+
const isDangerous = dangerLevel === "danger" || dangerLevel === "write";
|
|
2420
|
+
const categoryId = isDangerous ? "APPROVAL_DANGEROUS" : "APPROVAL_NORMAL";
|
|
2421
|
+
const projectName = (0, import_node_path3.basename)(
|
|
2422
|
+
this.sessionManager.getActiveSessions().find((s) => s.id === request.sessionId)?.projectPath ?? ""
|
|
2423
|
+
);
|
|
2424
|
+
const pushTitle = isDangerous ? `\u26A0\uFE0F ${title}` : title;
|
|
2425
|
+
const subtitle = `\u{1F527} ${request.toolName}: ${this.extractTarget(request)}`;
|
|
2381
2426
|
this.notify({
|
|
2382
|
-
title,
|
|
2427
|
+
title: pushTitle,
|
|
2428
|
+
subtitle,
|
|
2383
2429
|
body,
|
|
2384
2430
|
sound: "default",
|
|
2385
2431
|
badge: this.getGlobalPendingCount(),
|
|
2432
|
+
categoryId,
|
|
2386
2433
|
data: {
|
|
2387
2434
|
type: "approval_request",
|
|
2388
2435
|
sessionId: request.sessionId,
|
|
2389
|
-
requestId: request.id
|
|
2436
|
+
requestId: request.id,
|
|
2437
|
+
toolName: request.toolName,
|
|
2438
|
+
projectName,
|
|
2439
|
+
dangerLevel
|
|
2390
2440
|
}
|
|
2391
2441
|
});
|
|
2392
2442
|
}
|
|
@@ -2421,6 +2471,13 @@ var NotificationService = class {
|
|
|
2421
2471
|
}
|
|
2422
2472
|
});
|
|
2423
2473
|
}
|
|
2474
|
+
/** 从审批请求中提取操作目标的简短描述 */
|
|
2475
|
+
extractTarget(request) {
|
|
2476
|
+
const input = request.toolInput;
|
|
2477
|
+
if (input.file_path) return (0, import_node_path3.basename)(String(input.file_path));
|
|
2478
|
+
if (input.command) return String(input.command).slice(0, 40);
|
|
2479
|
+
return request.description.slice(0, 40);
|
|
2480
|
+
}
|
|
2424
2481
|
/** 简单的工具危险等级判断 */
|
|
2425
2482
|
getDangerLevel(toolName) {
|
|
2426
2483
|
if (toolName === "Bash") return "danger";
|
|
@@ -2600,9 +2657,11 @@ var ExpoNotificationChannel = class {
|
|
|
2600
2657
|
return {
|
|
2601
2658
|
to,
|
|
2602
2659
|
title: payload.title,
|
|
2660
|
+
subtitle: payload.subtitle,
|
|
2603
2661
|
body: payload.body,
|
|
2604
2662
|
badge: payload.badge,
|
|
2605
2663
|
sound: pushSound,
|
|
2664
|
+
categoryId: payload.categoryId,
|
|
2606
2665
|
data: payload.data ?? {}
|
|
2607
2666
|
};
|
|
2608
2667
|
});
|
|
@@ -3314,7 +3373,7 @@ async function createWithRetry(label, port, factory) {
|
|
|
3314
3373
|
}
|
|
3315
3374
|
}
|
|
3316
3375
|
async function start(opts = {}) {
|
|
3317
|
-
const configDir = (0, import_node_path4.join)((0,
|
|
3376
|
+
const configDir = (0, import_node_path4.join)((0, import_node_os5.homedir)(), ".sessix");
|
|
3318
3377
|
const tokenFile = (0, import_node_path4.join)(configDir, "token");
|
|
3319
3378
|
let token;
|
|
3320
3379
|
if (opts.token !== void 0) {
|
|
@@ -3365,7 +3424,7 @@ async function start(opts = {}) {
|
|
|
3365
3424
|
let mdnsService = null;
|
|
3366
3425
|
const pairingManager = new PairingManager({
|
|
3367
3426
|
token,
|
|
3368
|
-
serverName: (0,
|
|
3427
|
+
serverName: (0, import_node_os5.hostname)(),
|
|
3369
3428
|
version: "0.2.0",
|
|
3370
3429
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3371
3430
|
});
|
|
@@ -3439,6 +3498,7 @@ async function start(opts = {}) {
|
|
|
3439
3498
|
}
|
|
3440
3499
|
case "kill_session": {
|
|
3441
3500
|
wsBridge.broadcast({ type: "status_change", sessionId: event.sessionId, status: "idle" });
|
|
3501
|
+
approvalProxy.clearPendingForSession(event.sessionId);
|
|
3442
3502
|
await sessionManager.killSession(event.sessionId);
|
|
3443
3503
|
wsBridge.broadcast({
|
|
3444
3504
|
type: "session_list",
|
|
@@ -3464,7 +3524,8 @@ async function start(opts = {}) {
|
|
|
3464
3524
|
type: "session_list",
|
|
3465
3525
|
sessions: sessionManager.getActiveSessions()
|
|
3466
3526
|
});
|
|
3467
|
-
|
|
3527
|
+
sessionManager.flushPendingAssistant(event.sessionId);
|
|
3528
|
+
const bufferedEvents = [...sessionManager.getSessionEvents(event.sessionId)];
|
|
3468
3529
|
if (sessionManager.isBufferTruncated(event.sessionId)) {
|
|
3469
3530
|
const projectPath = sessionManager.getSessionProjectPath(event.sessionId);
|
|
3470
3531
|
if (projectPath) {
|