sessix-server 0.5.0 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +342 -81
- package/dist/server.js +337 -76
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
27
|
+
var import_node_os12 = require("os");
|
|
28
|
+
var import_node_fs6 = require("fs");
|
|
29
|
+
var import_node_path12 = require("path");
|
|
30
30
|
var import_node_child_process13 = require("child_process");
|
|
31
31
|
|
|
32
32
|
// src/i18n/locales/zh.ts
|
|
@@ -304,10 +304,12 @@ function t(key, params) {
|
|
|
304
304
|
// src/server.ts
|
|
305
305
|
var import_uuid8 = require("uuid");
|
|
306
306
|
var import_promises7 = require("fs/promises");
|
|
307
|
-
var
|
|
308
|
-
var
|
|
307
|
+
var import_node_os11 = require("os");
|
|
308
|
+
var import_node_path11 = require("path");
|
|
309
309
|
var import_node_child_process12 = require("child_process");
|
|
310
310
|
var import_node_util3 = require("util");
|
|
311
|
+
var import_node_v8 = require("v8");
|
|
312
|
+
var import_node_vm = require("vm");
|
|
311
313
|
|
|
312
314
|
// src/providers/ProcessProvider.ts
|
|
313
315
|
var import_child_process = require("child_process");
|
|
@@ -444,32 +446,54 @@ var import_promises = require("fs/promises");
|
|
|
444
446
|
var import_readline = require("readline");
|
|
445
447
|
var import_path = require("path");
|
|
446
448
|
var import_os = require("os");
|
|
449
|
+
|
|
450
|
+
// src/utils/modelValidation.ts
|
|
451
|
+
function isUsableModel(model) {
|
|
452
|
+
if (typeof model !== "string") return false;
|
|
453
|
+
const trimmed = model.trim();
|
|
454
|
+
if (!trimmed) return false;
|
|
455
|
+
if (trimmed === "unknown") return false;
|
|
456
|
+
if (trimmed.startsWith("<")) return false;
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
function sanitizeModel(model) {
|
|
460
|
+
return isUsableModel(model) ? model.trim() : void 0;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/session/ProjectReader.ts
|
|
447
464
|
var CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
|
|
448
465
|
function getSessionFilePath(projectPath, sessionId) {
|
|
449
466
|
return (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodeDirName(projectPath), `${sessionId}.jsonl`);
|
|
450
467
|
}
|
|
451
468
|
async function getSessionModel(projectPath, sessionId) {
|
|
452
469
|
const filePath = getSessionFilePath(projectPath, sessionId);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
470
|
+
let fileHandle;
|
|
471
|
+
try {
|
|
472
|
+
fileHandle = await (0, import_promises.open)(filePath, "r");
|
|
473
|
+
const rl = (0, import_readline.createInterface)({
|
|
474
|
+
input: fileHandle.createReadStream({ encoding: "utf-8" }),
|
|
475
|
+
crlfDelay: Infinity
|
|
476
|
+
});
|
|
477
|
+
let lastModel;
|
|
478
|
+
for await (const line of rl) {
|
|
479
|
+
if (!line.trim()) continue;
|
|
480
|
+
try {
|
|
481
|
+
const obj = JSON.parse(line);
|
|
482
|
+
if (obj.type !== "assistant" || !obj.message) continue;
|
|
483
|
+
const model = obj.message.model;
|
|
484
|
+
if (isUsableModel(model)) {
|
|
485
|
+
lastModel = model;
|
|
486
|
+
}
|
|
487
|
+
} catch {
|
|
468
488
|
}
|
|
469
|
-
} catch {
|
|
470
489
|
}
|
|
490
|
+
return lastModel;
|
|
491
|
+
} catch (err) {
|
|
492
|
+
if (err.code === "ENOENT") return void 0;
|
|
493
|
+
throw err;
|
|
494
|
+
} finally {
|
|
495
|
+
await fileHandle?.close();
|
|
471
496
|
}
|
|
472
|
-
return void 0;
|
|
473
497
|
}
|
|
474
498
|
async function getProjects() {
|
|
475
499
|
try {
|
|
@@ -598,17 +622,23 @@ async function getHistoricalSessions(projectPath) {
|
|
|
598
622
|
}
|
|
599
623
|
}
|
|
600
624
|
async function getSessionHistory(projectPath, sessionId) {
|
|
625
|
+
let fileHandle;
|
|
601
626
|
try {
|
|
602
627
|
const encodedPath = encodeDirName(projectPath);
|
|
603
628
|
const filePath = (0, import_path.join)(CLAUDE_PROJECTS_DIR, encodedPath, `${sessionId}.jsonl`);
|
|
604
|
-
|
|
605
|
-
|
|
629
|
+
try {
|
|
630
|
+
fileHandle = await (0, import_promises.open)(filePath, "r");
|
|
631
|
+
} catch (err) {
|
|
632
|
+
if (err.code === "ENOENT") return { ok: true, value: [] };
|
|
606
633
|
throw err;
|
|
634
|
+
}
|
|
635
|
+
const rl = (0, import_readline.createInterface)({
|
|
636
|
+
input: fileHandle.createReadStream({ encoding: "utf-8" }),
|
|
637
|
+
crlfDelay: Infinity
|
|
607
638
|
});
|
|
608
|
-
if (raw === null) return { ok: true, value: [] };
|
|
609
|
-
const lines = raw.split("\n").filter((l) => l.trim());
|
|
610
639
|
const events = [];
|
|
611
|
-
for (const line of
|
|
640
|
+
for await (const line of rl) {
|
|
641
|
+
if (!line.trim()) continue;
|
|
612
642
|
try {
|
|
613
643
|
const obj = JSON.parse(line);
|
|
614
644
|
const type = obj.type;
|
|
@@ -681,6 +711,8 @@ async function getSessionHistory(projectPath, sessionId) {
|
|
|
681
711
|
ok: false,
|
|
682
712
|
error: err instanceof Error ? err : new Error(String(err))
|
|
683
713
|
};
|
|
714
|
+
} finally {
|
|
715
|
+
await fileHandle?.close();
|
|
684
716
|
}
|
|
685
717
|
}
|
|
686
718
|
async function extractLastTimestamp(filePath) {
|
|
@@ -1025,6 +1057,27 @@ var ProcessProvider = class {
|
|
|
1025
1057
|
}
|
|
1026
1058
|
return swept;
|
|
1027
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* 枚举可淘汰的老会话
|
|
1062
|
+
*
|
|
1063
|
+
* 进程已退出(已被空闲 GC kill)且空闲超过 maxIdleMs 的会话——其 entry 与各 Map
|
|
1064
|
+
* 仍长期占内存。调用方对返回 id 执行 killSession 彻底清除;淘汰后手机端发消息
|
|
1065
|
+
* 会自动走 resume 路径(--resume + JSONL),不影响继续对话。
|
|
1066
|
+
*
|
|
1067
|
+
* @returns 可淘汰的 sessionId 列表(仅枚举,不删除)
|
|
1068
|
+
*/
|
|
1069
|
+
listEvictableSessions(maxIdleMs) {
|
|
1070
|
+
if (maxIdleMs <= 0) return [];
|
|
1071
|
+
const now = Date.now();
|
|
1072
|
+
const evictable = [];
|
|
1073
|
+
for (const [sessionId, entry] of this.activeSessions) {
|
|
1074
|
+
if (entry.process.exitCode === null && entry.process.signalCode === null) continue;
|
|
1075
|
+
if (entry.session.status === "running" || entry.session.status === "waiting_question" || entry.session.status === "waiting_approval") continue;
|
|
1076
|
+
if (now - entry.session.lastActiveAt < maxIdleMs) continue;
|
|
1077
|
+
evictable.push(sessionId);
|
|
1078
|
+
}
|
|
1079
|
+
return evictable;
|
|
1080
|
+
}
|
|
1028
1081
|
// ============================================
|
|
1029
1082
|
// 私有方法
|
|
1030
1083
|
// ============================================
|
|
@@ -1046,17 +1099,22 @@ var ProcessProvider = class {
|
|
|
1046
1099
|
} else {
|
|
1047
1100
|
args.push("--session-id", sessionId);
|
|
1048
1101
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1102
|
+
const safeModel = sanitizeModel(model);
|
|
1103
|
+
if (model && !safeModel) {
|
|
1104
|
+
console.warn(`[ProcessProvider] Session ${sessionId}: ignoring invalid model "${model}", falling back to CLI default`);
|
|
1105
|
+
}
|
|
1106
|
+
if (safeModel) {
|
|
1107
|
+
args.push("--model", safeModel);
|
|
1051
1108
|
}
|
|
1109
|
+
const safeFallbackModel = sanitizeModel(fallbackModel);
|
|
1052
1110
|
if (permissionMode && permissionMode !== "default") {
|
|
1053
1111
|
args.push("--permission-mode", permissionMode);
|
|
1054
1112
|
}
|
|
1055
1113
|
if (effort) {
|
|
1056
1114
|
args.push("--effort", effort);
|
|
1057
1115
|
}
|
|
1058
|
-
if (
|
|
1059
|
-
args.push("--fallback-model",
|
|
1116
|
+
if (safeFallbackModel) {
|
|
1117
|
+
args.push("--fallback-model", safeFallbackModel);
|
|
1060
1118
|
}
|
|
1061
1119
|
if (maxBudgetUsd != null) {
|
|
1062
1120
|
args.push("--max-budget-usd", String(maxBudgetUsd));
|
|
@@ -2041,10 +2099,18 @@ var SessionManager = class {
|
|
|
2041
2099
|
sessionAgentType = /* @__PURE__ */ new Map();
|
|
2042
2100
|
/** 事件回调列表(事件会被转发到 WsBridge) */
|
|
2043
2101
|
eventCallbacks = [];
|
|
2102
|
+
/** 会话被移除(kill / 淘汰)时的回调列表(用于释放外部模块的会话级状态,如 NotificationService) */
|
|
2103
|
+
sessionRemovedCallbacks = [];
|
|
2044
2104
|
/** 每个会话的事件流取消订阅函数 */
|
|
2045
2105
|
unsubscribeMap = /* @__PURE__ */ new Map();
|
|
2046
2106
|
/** 每个会话的事件缓冲区(用于新订阅者重放)*/
|
|
2047
2107
|
sessionEventBuffers = /* @__PURE__ */ new Map();
|
|
2108
|
+
/**
|
|
2109
|
+
* 每个会话最近一次 AskUserQuestion tool_use 的真实 id(从 claude_event 流捕获)。
|
|
2110
|
+
* PreToolUse hook payload 不含 tool_use_id,但内联卡片需要它来匹配状态,
|
|
2111
|
+
* 故在转发流事件时记录,askQuestion 时兜底回填。
|
|
2112
|
+
*/
|
|
2113
|
+
lastAskQuestionToolUseId = /* @__PURE__ */ new Map();
|
|
2048
2114
|
/** AskUserQuestion 问题映射:requestId → resolve 回调 + 原始问题内容 */
|
|
2049
2115
|
pendingQuestions = /* @__PURE__ */ new Map();
|
|
2050
2116
|
/**
|
|
@@ -2153,6 +2219,7 @@ var SessionManager = class {
|
|
|
2153
2219
|
this.bufferTruncated.delete(sessionId);
|
|
2154
2220
|
this.sessionProjectPaths.delete(sessionId);
|
|
2155
2221
|
this.sessionStats.delete(sessionId);
|
|
2222
|
+
this.lastAskQuestionToolUseId.delete(sessionId);
|
|
2156
2223
|
const pending = this.pendingAssistantEvents.get(sessionId);
|
|
2157
2224
|
if (pending) {
|
|
2158
2225
|
clearTimeout(pending.timer);
|
|
@@ -2161,6 +2228,13 @@ var SessionManager = class {
|
|
|
2161
2228
|
const provider = this.getProviderForSession(sessionId);
|
|
2162
2229
|
await provider.killSession(sessionId);
|
|
2163
2230
|
this.sessionAgentType.delete(sessionId);
|
|
2231
|
+
for (const cb of this.sessionRemovedCallbacks) {
|
|
2232
|
+
try {
|
|
2233
|
+
cb(sessionId);
|
|
2234
|
+
} catch (err) {
|
|
2235
|
+
console.error("[SessionManager] sessionRemoved callback failed:", err);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2164
2238
|
console.log(`[SessionManager] Session killed: ${sessionId}`);
|
|
2165
2239
|
}
|
|
2166
2240
|
/**
|
|
@@ -2351,6 +2425,21 @@ var SessionManager = class {
|
|
|
2351
2425
|
}
|
|
2352
2426
|
};
|
|
2353
2427
|
}
|
|
2428
|
+
/**
|
|
2429
|
+
* 注册"会话被移除"回调(会话 kill 或淘汰时触发,传入 sessionId)。
|
|
2430
|
+
* 用于让外部模块释放会话级状态,如 NotificationService.releaseSession。
|
|
2431
|
+
*
|
|
2432
|
+
* @returns 取消注册的函数
|
|
2433
|
+
*/
|
|
2434
|
+
onSessionRemoved(callback) {
|
|
2435
|
+
this.sessionRemovedCallbacks.push(callback);
|
|
2436
|
+
return () => {
|
|
2437
|
+
const index = this.sessionRemovedCallbacks.indexOf(callback);
|
|
2438
|
+
if (index !== -1) {
|
|
2439
|
+
this.sessionRemovedCallbacks.splice(index, 1);
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2354
2443
|
/**
|
|
2355
2444
|
* 清理所有资源
|
|
2356
2445
|
*/
|
|
@@ -2374,6 +2463,7 @@ var SessionManager = class {
|
|
|
2374
2463
|
this.pendingQuestions.clear();
|
|
2375
2464
|
this.lastBroadcastStatus.clear();
|
|
2376
2465
|
this.eventCallbacks.length = 0;
|
|
2466
|
+
this.sessionRemovedCallbacks.length = 0;
|
|
2377
2467
|
console.log("[SessionManager] Destroyed");
|
|
2378
2468
|
}
|
|
2379
2469
|
// ============================================
|
|
@@ -2422,6 +2512,13 @@ var SessionManager = class {
|
|
|
2422
2512
|
console.log(`[SessionManager] \u{1F9E0} thinking block detected in ${sessionId}: msgId=${event.message.id}, blocks=${thinkingBlocks.length}, len=${thinkingBlocks.map((b) => (b.thinking || "").length).join(",")}`);
|
|
2423
2513
|
}
|
|
2424
2514
|
}
|
|
2515
|
+
if (event.type === "assistant" && Array.isArray(event.message?.content)) {
|
|
2516
|
+
for (const block of event.message.content) {
|
|
2517
|
+
if (block.type === "tool_use" && (block.name === "AskUserQuestion" || block.name === "AskFollowupQuestion") && typeof block.id === "string") {
|
|
2518
|
+
this.lastAskQuestionToolUseId.set(sessionId, block.id);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2425
2522
|
switch (event.type) {
|
|
2426
2523
|
case "assistant":
|
|
2427
2524
|
this.bufferAssistantEvent(sessionId, event);
|
|
@@ -2542,10 +2639,11 @@ var SessionManager = class {
|
|
|
2542
2639
|
* 返回的 Promise 在 handleQuestionResponse 时 resolve。
|
|
2543
2640
|
*/
|
|
2544
2641
|
askQuestion(sessionId, toolUseId, questions, requestId) {
|
|
2642
|
+
const resolvedToolUseId = toolUseId || this.lastAskQuestionToolUseId.get(sessionId) || "";
|
|
2545
2643
|
const request = {
|
|
2546
2644
|
id: requestId,
|
|
2547
2645
|
sessionId,
|
|
2548
|
-
toolUseId,
|
|
2646
|
+
toolUseId: resolvedToolUseId,
|
|
2549
2647
|
question: questions[0]?.question ?? "",
|
|
2550
2648
|
options: questions[0]?.options?.map((o) => o.label),
|
|
2551
2649
|
questions,
|
|
@@ -2557,7 +2655,7 @@ var SessionManager = class {
|
|
|
2557
2655
|
return new Promise((resolve) => {
|
|
2558
2656
|
this.pendingQuestions.set(requestId, {
|
|
2559
2657
|
sessionId,
|
|
2560
|
-
toolUseId,
|
|
2658
|
+
toolUseId: resolvedToolUseId,
|
|
2561
2659
|
question: request.question,
|
|
2562
2660
|
options: request.options,
|
|
2563
2661
|
questions,
|
|
@@ -4079,7 +4177,8 @@ var HookInstaller = class {
|
|
|
4079
4177
|
const isLatestVersion = approvalScriptContent.includes("permissionDecision") && approvalScriptContent.includes("Sessix Approval Hook v2");
|
|
4080
4178
|
const settings = await this.readClaudeSettings();
|
|
4081
4179
|
const configExists = this.hasHookConfig(settings);
|
|
4082
|
-
|
|
4180
|
+
const hasLegacyHook = this.hasHookEntry(settings?.hooks?.PreToolUse, LEGACY_HOOK_COMMANDS[0]) || this.hasHookEntry(settings?.hooks?.PermissionRequest, LEGACY_HOOK_COMMANDS[1]);
|
|
4181
|
+
return isLatestVersion && permissionScriptExists && compactScriptExists && postCompactScriptExists && permissionDeniedScriptExists && configExists && !hasLegacyHook;
|
|
4083
4182
|
}
|
|
4084
4183
|
// ============================================
|
|
4085
4184
|
// 内部方法
|
|
@@ -4091,8 +4190,14 @@ var HookInstaller = class {
|
|
|
4091
4190
|
let settings = await this.readClaudeSettings();
|
|
4092
4191
|
let changed = false;
|
|
4093
4192
|
for (const cmd of LEGACY_HOOK_COMMANDS) {
|
|
4094
|
-
this.
|
|
4095
|
-
|
|
4193
|
+
if (this.hasHookEntry(settings?.hooks?.PreToolUse, cmd)) {
|
|
4194
|
+
this.removeHookCommand(settings, "PreToolUse", cmd);
|
|
4195
|
+
changed = true;
|
|
4196
|
+
}
|
|
4197
|
+
if (this.hasHookEntry(settings?.hooks?.PermissionRequest, cmd)) {
|
|
4198
|
+
this.removeHookCommand(settings, "PermissionRequest", cmd);
|
|
4199
|
+
changed = true;
|
|
4200
|
+
}
|
|
4096
4201
|
}
|
|
4097
4202
|
if (!settings.hooks) {
|
|
4098
4203
|
settings.hooks = {};
|
|
@@ -4489,12 +4594,40 @@ var NotificationService = class _NotificationService {
|
|
|
4489
4594
|
this.latestAssistantText.clear();
|
|
4490
4595
|
for (const timer of this.activityPushTimers.values()) clearTimeout(timer);
|
|
4491
4596
|
this.activityPushTimers.clear();
|
|
4597
|
+
for (const timer of this.idleEndTimers.values()) clearTimeout(timer);
|
|
4598
|
+
this.idleEndTimers.clear();
|
|
4599
|
+
for (const timer of this.laHeartbeatTimers.values()) clearInterval(timer);
|
|
4600
|
+
this.laHeartbeatTimers.clear();
|
|
4492
4601
|
this.recentActivityState.clear();
|
|
4493
4602
|
this.lastActivityPushAt.clear();
|
|
4494
4603
|
this.pendingPriority.clear();
|
|
4495
4604
|
this.activityCounters.clear();
|
|
4496
4605
|
this.lastPushedFingerprint.clear();
|
|
4497
4606
|
}
|
|
4607
|
+
/**
|
|
4608
|
+
* 释放单个会话的全部内存状态(会话被 kill 或淘汰时调用)。
|
|
4609
|
+
* 由 SessionManager.onSessionRemoved 钩子触发,覆盖用户主动 kill 和自动淘汰两条路径。
|
|
4610
|
+
* 幂等:重复调用或对未知会话调用都安全。
|
|
4611
|
+
*/
|
|
4612
|
+
releaseSession(sessionId) {
|
|
4613
|
+
this.clearActivityPushTimer(sessionId);
|
|
4614
|
+
this.cancelIdleEndTimer(sessionId);
|
|
4615
|
+
this.stopLaHeartbeat(sessionId);
|
|
4616
|
+
this.clearSessionActivityState(sessionId);
|
|
4617
|
+
this.yoloModeState.delete(sessionId);
|
|
4618
|
+
this.lastActivityPushAt.delete(sessionId);
|
|
4619
|
+
this.lastPushedFingerprint.delete(sessionId);
|
|
4620
|
+
this.pendingPriority.delete(sessionId);
|
|
4621
|
+
}
|
|
4622
|
+
/**
|
|
4623
|
+
* 清空单会话可重建的重状态(recentActivity / 计数器 / 最新文本)。
|
|
4624
|
+
* 会话走到 idle 时调用即可释放内存——resume 后这些状态会随新事件自动重建。
|
|
4625
|
+
*/
|
|
4626
|
+
clearSessionActivityState(sessionId) {
|
|
4627
|
+
this.recentActivityState.delete(sessionId);
|
|
4628
|
+
this.activityCounters.delete(sessionId);
|
|
4629
|
+
this.latestAssistantText.delete(sessionId);
|
|
4630
|
+
}
|
|
4498
4631
|
// ============================================
|
|
4499
4632
|
// 内部方法
|
|
4500
4633
|
// ============================================
|
|
@@ -4532,6 +4665,7 @@ var NotificationService = class _NotificationService {
|
|
|
4532
4665
|
badge: this.getGlobalPendingCount(),
|
|
4533
4666
|
data: { type: "task_complete", sessionId: event.sessionId }
|
|
4534
4667
|
});
|
|
4668
|
+
this.clearSessionActivityState(event.sessionId);
|
|
4535
4669
|
}
|
|
4536
4670
|
} else if (event.status === "running" || event.status === "waiting_approval" || event.status === "waiting_question") {
|
|
4537
4671
|
this.cancelIdleEndTimer(event.sessionId);
|
|
@@ -4820,9 +4954,8 @@ var NotificationService = class _NotificationService {
|
|
|
4820
4954
|
});
|
|
4821
4955
|
}
|
|
4822
4956
|
this.stopLaHeartbeat(sessionId);
|
|
4823
|
-
this.
|
|
4957
|
+
this.clearSessionActivityState(sessionId);
|
|
4824
4958
|
this.lastActivityPushAt.delete(sessionId);
|
|
4825
|
-
this.activityCounters.delete(sessionId);
|
|
4826
4959
|
this.lastPushedFingerprint.delete(sessionId);
|
|
4827
4960
|
console.log(`[NotificationService] \u{1F3C1} LA end (${reason}) session=${sessionId.slice(0, 8)}\u2026`);
|
|
4828
4961
|
}
|
|
@@ -5015,6 +5148,36 @@ var DesktopNotificationChannel = class {
|
|
|
5015
5148
|
}
|
|
5016
5149
|
};
|
|
5017
5150
|
|
|
5151
|
+
// src/notification/pushTokenStore.ts
|
|
5152
|
+
var import_node_fs4 = require("fs");
|
|
5153
|
+
var import_node_os7 = require("os");
|
|
5154
|
+
var import_node_path6 = require("path");
|
|
5155
|
+
var DEFAULT_PUSH_TOKENS_FILE = (0, import_node_path6.join)((0, import_node_os7.homedir)(), ".sessix", "push-tokens.json");
|
|
5156
|
+
function loadPushTokens(filePath = DEFAULT_PUSH_TOKENS_FILE) {
|
|
5157
|
+
try {
|
|
5158
|
+
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
|
|
5159
|
+
if (!Array.isArray(parsed)) return [];
|
|
5160
|
+
return parsed.filter((t2) => typeof t2 === "string");
|
|
5161
|
+
} catch {
|
|
5162
|
+
return [];
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
function savePushTokens(tokens, filePath = DEFAULT_PUSH_TOKENS_FILE) {
|
|
5166
|
+
const deduped = [];
|
|
5167
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5168
|
+
for (const t2 of tokens) {
|
|
5169
|
+
if (typeof t2 !== "string" || seen.has(t2)) continue;
|
|
5170
|
+
seen.add(t2);
|
|
5171
|
+
deduped.push(t2);
|
|
5172
|
+
}
|
|
5173
|
+
try {
|
|
5174
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(filePath), { recursive: true });
|
|
5175
|
+
(0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(deduped, null, 2), "utf-8");
|
|
5176
|
+
} catch (err) {
|
|
5177
|
+
console.warn("[pushTokenStore] \u5199\u5165 push token \u5931\u8D25:", err);
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5018
5181
|
// src/notification/ExpoNotificationChannel.ts
|
|
5019
5182
|
var EXPO_PUSH_API = "https://exp.host/--/api/v2/push/send";
|
|
5020
5183
|
var EXPO_RECEIPT_API = "https://exp.host/--/api/v2/push/getReceipts";
|
|
@@ -5025,18 +5188,35 @@ var ExpoNotificationChannel = class {
|
|
|
5025
5188
|
tokenWsMap = /* @__PURE__ */ new Map();
|
|
5026
5189
|
/** per-token 通知音效偏好 */
|
|
5027
5190
|
soundPreferences = /* @__PURE__ */ new Map();
|
|
5191
|
+
/** push token 持久化文件路径 */
|
|
5192
|
+
pushTokensFile;
|
|
5193
|
+
constructor(opts = {}) {
|
|
5194
|
+
this.pushTokensFile = opts.pushTokensFile ?? DEFAULT_PUSH_TOKENS_FILE;
|
|
5195
|
+
for (const token of loadPushTokens(this.pushTokensFile)) {
|
|
5196
|
+
this.tokens.add(token);
|
|
5197
|
+
}
|
|
5198
|
+
if (this.tokens.size > 0) {
|
|
5199
|
+
console.log(`[ExpoNotificationChannel] \u4ECE\u78C1\u76D8\u91CD\u8F7D ${this.tokens.size} \u4E2A push token`);
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
/** 把当前 token 集合落盘,供进程重启后重载。 */
|
|
5203
|
+
persist() {
|
|
5204
|
+
savePushTokens(Array.from(this.tokens), this.pushTokensFile);
|
|
5205
|
+
}
|
|
5028
5206
|
isAvailable() {
|
|
5029
5207
|
return this.tokens.size > 0;
|
|
5030
5208
|
}
|
|
5031
5209
|
addToken(token, ws) {
|
|
5032
5210
|
this.tokens.add(token);
|
|
5033
5211
|
if (ws) this.tokenWsMap.set(token, ws);
|
|
5212
|
+
this.persist();
|
|
5034
5213
|
console.log(`[ExpoNotificationChannel] ${t("notification.tokenRegistered", { count: this.tokens.size })}`);
|
|
5035
5214
|
}
|
|
5036
5215
|
removeToken(token) {
|
|
5037
5216
|
this.tokens.delete(token);
|
|
5038
5217
|
this.tokenWsMap.delete(token);
|
|
5039
5218
|
this.soundPreferences.delete(token);
|
|
5219
|
+
this.persist();
|
|
5040
5220
|
console.log(`[ExpoNotificationChannel] ${t("notification.tokenRemoved", { count: this.tokens.size })}`);
|
|
5041
5221
|
}
|
|
5042
5222
|
/** 更新某个 token 的音效偏好 */
|
|
@@ -5108,6 +5288,7 @@ var ExpoNotificationChannel = class {
|
|
|
5108
5288
|
this.tokens.delete(staleToken);
|
|
5109
5289
|
this.tokenWsMap.delete(staleToken);
|
|
5110
5290
|
this.soundPreferences.delete(staleToken);
|
|
5291
|
+
this.persist();
|
|
5111
5292
|
console.warn(`[ExpoNotificationChannel] \u26A0\uFE0F \u5DF2\u79FB\u9664\u5931\u6548 token\uFF08DeviceNotRegistered\uFF09\u3002\u82E5\u901A\u77E5\u672A\u6062\u590D\uFF0C\u8BF7\u91CD\u542F App \u91CD\u65B0\u6CE8\u518C push token\u3002`);
|
|
5112
5293
|
}
|
|
5113
5294
|
} else if (ticket.status === "ok" && typeof ticket.id === "string" && targetTokens[i]) {
|
|
@@ -5154,6 +5335,7 @@ var ExpoNotificationChannel = class {
|
|
|
5154
5335
|
this.tokens.delete(token);
|
|
5155
5336
|
this.tokenWsMap.delete(token);
|
|
5156
5337
|
this.soundPreferences.delete(token);
|
|
5338
|
+
this.persist();
|
|
5157
5339
|
console.warn("[ExpoNotificationChannel] \u26A0\uFE0F \u5DF2\u79FB\u9664\u5931\u6548 token\uFF08receipt DeviceNotRegistered\uFF09\u3002\u91CD\u542F App \u53EF\u91CD\u65B0\u6CE8\u518C\u3002");
|
|
5158
5340
|
} else if (errorCode === "InvalidCredentials" || errorCode === "MismatchSenderId") {
|
|
5159
5341
|
console.error(
|
|
@@ -5653,6 +5835,9 @@ var PairingManager = class {
|
|
|
5653
5835
|
|
|
5654
5836
|
// src/utils/shellPath.ts
|
|
5655
5837
|
var import_node_child_process7 = require("child_process");
|
|
5838
|
+
var import_node_fs5 = require("fs");
|
|
5839
|
+
var import_node_path7 = require("path");
|
|
5840
|
+
var import_node_os8 = require("os");
|
|
5656
5841
|
var fixed = false;
|
|
5657
5842
|
function fixShellPath() {
|
|
5658
5843
|
if (fixed || isWindows) {
|
|
@@ -5660,23 +5845,62 @@ function fixShellPath() {
|
|
|
5660
5845
|
return;
|
|
5661
5846
|
}
|
|
5662
5847
|
fixed = true;
|
|
5848
|
+
const fromShell = readLoginShellPath();
|
|
5849
|
+
if (fromShell) {
|
|
5850
|
+
process.env.PATH = mergePath(fromShell, process.env.PATH || "");
|
|
5851
|
+
}
|
|
5852
|
+
const stableNodeDir = resolveStableNodeDir();
|
|
5853
|
+
if (stableNodeDir) {
|
|
5854
|
+
process.env.PATH = mergePath(stableNodeDir, process.env.PATH || "");
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
function readLoginShellPath() {
|
|
5663
5858
|
const shell = process.env.SHELL || "/bin/zsh";
|
|
5664
5859
|
const isFish = /\/fish$/.test(shell);
|
|
5665
5860
|
const printPathCmd = isFish ? "string join : $PATH" : 'printf "%s" "$PATH"';
|
|
5666
|
-
|
|
5861
|
+
for (const flags of [["-i", "-l", "-c"], ["-l", "-c"]]) {
|
|
5862
|
+
try {
|
|
5863
|
+
const raw = (0, import_node_child_process7.execFileSync)(shell, [...flags, printPathCmd], {
|
|
5864
|
+
encoding: "utf8",
|
|
5865
|
+
timeout: 4e3,
|
|
5866
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5867
|
+
}).trim();
|
|
5868
|
+
if (raw) return raw;
|
|
5869
|
+
} catch {
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
return null;
|
|
5873
|
+
}
|
|
5874
|
+
function resolveStableNodeDir() {
|
|
5875
|
+
const exe = isWindows ? "node.exe" : "node";
|
|
5876
|
+
for (const dir of (process.env.PATH || "").split(":")) {
|
|
5877
|
+
if (!dir) continue;
|
|
5878
|
+
try {
|
|
5879
|
+
const p = (0, import_node_path7.join)(dir, exe);
|
|
5880
|
+
(0, import_node_fs5.accessSync)(p, import_node_fs5.constants.X_OK);
|
|
5881
|
+
return (0, import_node_path7.dirname)((0, import_node_fs5.realpathSync)(p));
|
|
5882
|
+
} catch {
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
return scanFnmNodeDir();
|
|
5886
|
+
}
|
|
5887
|
+
function scanFnmNodeDir() {
|
|
5888
|
+
const base = (0, import_node_path7.join)((0, import_node_os8.homedir)(), ".fnm", "node-versions");
|
|
5667
5889
|
try {
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5890
|
+
const versions = (0, import_node_fs5.readdirSync)(base).filter((v) => /^v?\d+\./.test(v)).sort(
|
|
5891
|
+
(a, b) => b.localeCompare(a, void 0, { numeric: true, sensitivity: "base" })
|
|
5892
|
+
);
|
|
5893
|
+
for (const v of versions) {
|
|
5894
|
+
const dir = (0, import_node_path7.join)(base, v, "installation", "bin");
|
|
5895
|
+
try {
|
|
5896
|
+
(0, import_node_fs5.accessSync)((0, import_node_path7.join)(dir, "node"), import_node_fs5.constants.X_OK);
|
|
5897
|
+
return dir;
|
|
5898
|
+
} catch {
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
} catch {
|
|
5676
5902
|
}
|
|
5677
|
-
|
|
5678
|
-
if (!fromShell) return;
|
|
5679
|
-
process.env.PATH = mergePath(fromShell, process.env.PATH || "");
|
|
5903
|
+
return null;
|
|
5680
5904
|
}
|
|
5681
5905
|
function mergePath(primary, secondary) {
|
|
5682
5906
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5910,20 +6134,19 @@ var TerminalExecutor = class {
|
|
|
5910
6134
|
var import_node_child_process9 = require("child_process");
|
|
5911
6135
|
var import_node_util = require("util");
|
|
5912
6136
|
var import_promises4 = require("fs/promises");
|
|
5913
|
-
var
|
|
5914
|
-
var
|
|
6137
|
+
var import_node_path8 = require("path");
|
|
6138
|
+
var import_node_os9 = require("os");
|
|
5915
6139
|
var import_uuid5 = require("uuid");
|
|
5916
6140
|
var execAsync = (0, import_node_util.promisify)(import_node_child_process9.exec);
|
|
5917
6141
|
var BUILD_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
5918
6142
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5919
|
-
var CONFIG_FILE = (0,
|
|
6143
|
+
var CONFIG_FILE = (0, import_node_path8.join)((0, import_node_os9.homedir)(), ".sessix", "xcode-config.json");
|
|
5920
6144
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5921
6145
|
"node_modules",
|
|
5922
6146
|
".git",
|
|
5923
6147
|
"DerivedData",
|
|
5924
6148
|
"Pods",
|
|
5925
6149
|
".build",
|
|
5926
|
-
"build",
|
|
5927
6150
|
"dist",
|
|
5928
6151
|
"__pycache__",
|
|
5929
6152
|
".next",
|
|
@@ -5970,7 +6193,7 @@ var XcodeBuildExecutor = class {
|
|
|
5970
6193
|
return this.configCache;
|
|
5971
6194
|
}
|
|
5972
6195
|
async writeConfigs(store) {
|
|
5973
|
-
await (0, import_promises4.mkdir)((0,
|
|
6196
|
+
await (0, import_promises4.mkdir)((0, import_node_path8.join)((0, import_node_os9.homedir)(), ".sessix"), { recursive: true });
|
|
5974
6197
|
await (0, import_promises4.writeFile)(CONFIG_FILE, JSON.stringify(store, null, 2), "utf8");
|
|
5975
6198
|
this.configCache = store;
|
|
5976
6199
|
}
|
|
@@ -6023,7 +6246,7 @@ var XcodeBuildExecutor = class {
|
|
|
6023
6246
|
if (SKIP_DIRS.has(name)) continue;
|
|
6024
6247
|
if (name.startsWith(".")) continue;
|
|
6025
6248
|
if (name.endsWith(".xcodeproj") || name.endsWith(".xcworkspace")) continue;
|
|
6026
|
-
const childPath = (0,
|
|
6249
|
+
const childPath = (0, import_node_path8.join)(currentPath, name);
|
|
6027
6250
|
await this.scanDir(rootPath, childPath, depth + 1, results);
|
|
6028
6251
|
}
|
|
6029
6252
|
}
|
|
@@ -6250,7 +6473,7 @@ ${e.stderr ?? ""}`);
|
|
|
6250
6473
|
if (!builtDir || !productName) {
|
|
6251
6474
|
throw new Error("\u65E0\u6CD5\u4ECE -showBuildSettings \u4E2D\u8BFB\u53D6 BUILT_PRODUCTS_DIR / FULL_PRODUCT_NAME");
|
|
6252
6475
|
}
|
|
6253
|
-
return (0,
|
|
6476
|
+
return (0, import_node_path8.join)(builtDir, productName);
|
|
6254
6477
|
}
|
|
6255
6478
|
// ============================================
|
|
6256
6479
|
// 清理
|
|
@@ -6322,7 +6545,7 @@ function kindOrder(k) {
|
|
|
6322
6545
|
|
|
6323
6546
|
// src/commands/CommandDiscovery.ts
|
|
6324
6547
|
var import_promises5 = require("fs/promises");
|
|
6325
|
-
var
|
|
6548
|
+
var import_node_path9 = require("path");
|
|
6326
6549
|
var import_node_crypto = require("crypto");
|
|
6327
6550
|
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
6328
6551
|
var MAX_README_BYTES = 256 * 1024;
|
|
@@ -6346,7 +6569,7 @@ var CommandDiscovery = class {
|
|
|
6346
6569
|
this.scanReadme(projectPath, "CLAUDE.md", "claude.md", collector)
|
|
6347
6570
|
]);
|
|
6348
6571
|
for (const sub of SUBPACKAGE_DIRS) {
|
|
6349
|
-
const subRoot = (0,
|
|
6572
|
+
const subRoot = (0, import_node_path9.join)(projectPath, sub);
|
|
6350
6573
|
let entries;
|
|
6351
6574
|
try {
|
|
6352
6575
|
entries = await (0, import_promises5.readdir)(subRoot);
|
|
@@ -6356,7 +6579,7 @@ var CommandDiscovery = class {
|
|
|
6356
6579
|
let scanned = 0;
|
|
6357
6580
|
for (const name of entries) {
|
|
6358
6581
|
if (name.startsWith(".") || scanned >= MAX_SCAN_PER_DIR) continue;
|
|
6359
|
-
const childAbs = (0,
|
|
6582
|
+
const childAbs = (0, import_node_path9.join)(subRoot, name);
|
|
6360
6583
|
try {
|
|
6361
6584
|
const s = await (0, import_promises5.stat)(childAbs);
|
|
6362
6585
|
if (!s.isDirectory()) continue;
|
|
@@ -6397,7 +6620,7 @@ var CommandDiscovery = class {
|
|
|
6397
6620
|
// ============================================
|
|
6398
6621
|
async scanPackageJson(rootPath, subDir, out) {
|
|
6399
6622
|
const file = subDir ? `${subDir}/package.json` : "package.json";
|
|
6400
|
-
const abs = (0,
|
|
6623
|
+
const abs = (0, import_node_path9.join)(rootPath, file);
|
|
6401
6624
|
let raw;
|
|
6402
6625
|
try {
|
|
6403
6626
|
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
@@ -6428,7 +6651,7 @@ var CommandDiscovery = class {
|
|
|
6428
6651
|
}
|
|
6429
6652
|
async scanMakefile(rootPath, subDir, out) {
|
|
6430
6653
|
const file = subDir ? `${subDir}/Makefile` : "Makefile";
|
|
6431
|
-
const abs = (0,
|
|
6654
|
+
const abs = (0, import_node_path9.join)(rootPath, file);
|
|
6432
6655
|
let raw;
|
|
6433
6656
|
try {
|
|
6434
6657
|
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
@@ -6469,7 +6692,7 @@ var CommandDiscovery = class {
|
|
|
6469
6692
|
}
|
|
6470
6693
|
async scanJustfile(rootPath, subDir, out) {
|
|
6471
6694
|
const file = subDir ? `${subDir}/justfile` : "justfile";
|
|
6472
|
-
const abs = (0,
|
|
6695
|
+
const abs = (0, import_node_path9.join)(rootPath, file);
|
|
6473
6696
|
let raw;
|
|
6474
6697
|
try {
|
|
6475
6698
|
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
@@ -6510,7 +6733,7 @@ var CommandDiscovery = class {
|
|
|
6510
6733
|
}
|
|
6511
6734
|
async scanCargo(rootPath, subDir, out) {
|
|
6512
6735
|
const file = subDir ? `${subDir}/Cargo.toml` : "Cargo.toml";
|
|
6513
|
-
const abs = (0,
|
|
6736
|
+
const abs = (0, import_node_path9.join)(rootPath, file);
|
|
6514
6737
|
try {
|
|
6515
6738
|
await (0, import_promises5.stat)(abs);
|
|
6516
6739
|
} catch {
|
|
@@ -6541,7 +6764,7 @@ var CommandDiscovery = class {
|
|
|
6541
6764
|
for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
|
|
6542
6765
|
const file = subDir ? `${subDir}/${name}` : name;
|
|
6543
6766
|
try {
|
|
6544
|
-
await (0, import_promises5.stat)((0,
|
|
6767
|
+
await (0, import_promises5.stat)((0, import_node_path9.join)(rootPath, file));
|
|
6545
6768
|
} catch {
|
|
6546
6769
|
continue;
|
|
6547
6770
|
}
|
|
@@ -6567,7 +6790,7 @@ var CommandDiscovery = class {
|
|
|
6567
6790
|
}
|
|
6568
6791
|
}
|
|
6569
6792
|
async scanReadme(rootPath, fileName, source, out) {
|
|
6570
|
-
const abs = (0,
|
|
6793
|
+
const abs = (0, import_node_path9.join)(rootPath, fileName);
|
|
6571
6794
|
let raw;
|
|
6572
6795
|
try {
|
|
6573
6796
|
const s = await (0, import_promises5.stat)(abs);
|
|
@@ -6947,8 +7170,8 @@ var GitExecutor = class {
|
|
|
6947
7170
|
|
|
6948
7171
|
// src/scheduling/ScheduledSessionManager.ts
|
|
6949
7172
|
var import_promises6 = require("fs/promises");
|
|
6950
|
-
var
|
|
6951
|
-
var
|
|
7173
|
+
var import_node_os10 = require("os");
|
|
7174
|
+
var import_node_path10 = require("path");
|
|
6952
7175
|
var import_uuid7 = require("uuid");
|
|
6953
7176
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
6954
7177
|
var ScheduledSessionManager = class {
|
|
@@ -6959,7 +7182,7 @@ var ScheduledSessionManager = class {
|
|
|
6959
7182
|
onFired;
|
|
6960
7183
|
persistTimer = null;
|
|
6961
7184
|
constructor(opts) {
|
|
6962
|
-
this.storeFile = opts.storeFile ?? (0,
|
|
7185
|
+
this.storeFile = opts.storeFile ?? (0, import_node_path10.join)((0, import_node_os10.homedir)(), ".sessix", "scheduled-sessions.json");
|
|
6963
7186
|
this.onFire = opts.onFire;
|
|
6964
7187
|
this.onChange = opts.onChange;
|
|
6965
7188
|
this.onFired = opts.onFired;
|
|
@@ -7064,7 +7287,7 @@ var ScheduledSessionManager = class {
|
|
|
7064
7287
|
this.persistTimer = setTimeout(() => {
|
|
7065
7288
|
this.persistTimer = null;
|
|
7066
7289
|
const tasks = [...this.tasks.values()].map((e) => e.task);
|
|
7067
|
-
(0, import_promises6.mkdir)((0,
|
|
7290
|
+
(0, import_promises6.mkdir)((0, import_node_path10.join)(this.storeFile, ".."), { recursive: true }).then(() => (0, import_promises6.writeFile)(this.storeFile, JSON.stringify(tasks, null, 2), "utf8")).catch((err) => {
|
|
7068
7291
|
console.error("[ScheduledSessionManager] persist error:", err);
|
|
7069
7292
|
});
|
|
7070
7293
|
}, 500);
|
|
@@ -7091,10 +7314,11 @@ function isValidTask(value) {
|
|
|
7091
7314
|
// src/utils/cliCapabilities.ts
|
|
7092
7315
|
var import_node_child_process11 = require("child_process");
|
|
7093
7316
|
var DEFAULT_MODELS = [
|
|
7094
|
-
{ value: "opus", label: "Opus 4.
|
|
7095
|
-
{ value: "claude-opus-4-
|
|
7096
|
-
{ value: "
|
|
7097
|
-
{ value: "
|
|
7317
|
+
{ value: "opus", label: "Opus 4.8", sublabel: "Most capable for ambitious work", maxEffort: "max", defaultEffort: "xhigh" },
|
|
7318
|
+
{ value: "claude-opus-4-7", label: "Opus 4.7", sublabel: "Previous generation flagship", maxEffort: "max", defaultEffort: "xhigh" },
|
|
7319
|
+
{ value: "claude-opus-4-6", label: "Opus 4.6", sublabel: "Earlier generation flagship", maxEffort: "max", defaultEffort: "xhigh" },
|
|
7320
|
+
{ value: "sonnet", label: "Sonnet 4.6", sublabel: "Most efficient for everyday tasks", maxEffort: "high", defaultEffort: "high" },
|
|
7321
|
+
{ value: "haiku", label: "Haiku 4.5", sublabel: "Fastest for quick answers", maxEffort: "medium", defaultEffort: "medium" }
|
|
7098
7322
|
];
|
|
7099
7323
|
var DEFAULT_CAPABILITIES = {
|
|
7100
7324
|
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
@@ -7167,7 +7391,7 @@ async function killPortProcess(port) {
|
|
|
7167
7391
|
}
|
|
7168
7392
|
}
|
|
7169
7393
|
async function loadApnsConfigFromFile() {
|
|
7170
|
-
const path2 = (0,
|
|
7394
|
+
const path2 = (0, import_node_path11.join)((0, import_node_os11.homedir)(), ".sessix", "apns.json");
|
|
7171
7395
|
try {
|
|
7172
7396
|
const raw = await (0, import_promises7.readFile)(path2, "utf8");
|
|
7173
7397
|
const cfg = JSON.parse(raw);
|
|
@@ -7214,8 +7438,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
7214
7438
|
}
|
|
7215
7439
|
async function start(opts = {}) {
|
|
7216
7440
|
fixShellPath();
|
|
7217
|
-
const configDir = (0,
|
|
7218
|
-
const tokenFile = (0,
|
|
7441
|
+
const configDir = (0, import_node_path11.join)((0, import_node_os11.homedir)(), ".sessix");
|
|
7442
|
+
const tokenFile = (0, import_node_path11.join)(configDir, "token");
|
|
7219
7443
|
let token;
|
|
7220
7444
|
if (opts.token !== void 0) {
|
|
7221
7445
|
token = opts.token;
|
|
@@ -7262,6 +7486,7 @@ async function start(opts = {}) {
|
|
|
7262
7486
|
const notificationService = new NotificationService(sessionManager, expoChannel);
|
|
7263
7487
|
notificationService.addChannel("expo", expoChannel, opts.enableExpoPush !== false);
|
|
7264
7488
|
notificationService.addChannel("mac", new DesktopNotificationChannel(), opts.enableMacNotification !== false);
|
|
7489
|
+
sessionManager.onSessionRemoved((sessionId) => notificationService.releaseSession(sessionId));
|
|
7265
7490
|
const activityPushOpts = opts.activityPush ?? await loadApnsConfigFromFile();
|
|
7266
7491
|
if (activityPushOpts) {
|
|
7267
7492
|
try {
|
|
@@ -7279,7 +7504,7 @@ async function start(opts = {}) {
|
|
|
7279
7504
|
let mdnsService = null;
|
|
7280
7505
|
const pairingManager = new PairingManager({
|
|
7281
7506
|
token,
|
|
7282
|
-
serverName: (0,
|
|
7507
|
+
serverName: (0, import_node_os11.hostname)(),
|
|
7283
7508
|
version: "0.2.0",
|
|
7284
7509
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
7285
7510
|
});
|
|
@@ -7950,8 +8175,30 @@ async function start(opts = {}) {
|
|
|
7950
8175
|
const idleTimeoutMs = Number(process.env.SESSIX_IDLE_TIMEOUT_MS ?? 30 * 60 * 1e3);
|
|
7951
8176
|
const idleSweepIntervalMs = Number(process.env.SESSIX_IDLE_SWEEP_INTERVAL_MS ?? 5 * 60 * 1e3);
|
|
7952
8177
|
const maxActiveProcesses = Number(process.env.SESSIX_MAX_ACTIVE_PROCESSES ?? 15);
|
|
8178
|
+
const sessionEvictMs = Number(process.env.SESSIX_SESSION_EVICT_MS ?? 2 * 60 * 60 * 1e3);
|
|
8179
|
+
let gcFn;
|
|
8180
|
+
const maybeGc = () => {
|
|
8181
|
+
if (gcFn === void 0) {
|
|
8182
|
+
gcFn = globalThis.gc ?? null;
|
|
8183
|
+
if (!gcFn) {
|
|
8184
|
+
try {
|
|
8185
|
+
(0, import_node_v8.setFlagsFromString)("--expose-gc");
|
|
8186
|
+
const fn = (0, import_node_vm.runInNewContext)("gc");
|
|
8187
|
+
gcFn = typeof fn === "function" ? fn : null;
|
|
8188
|
+
} catch {
|
|
8189
|
+
gcFn = null;
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
}
|
|
8193
|
+
if (gcFn) {
|
|
8194
|
+
try {
|
|
8195
|
+
gcFn();
|
|
8196
|
+
} catch {
|
|
8197
|
+
}
|
|
8198
|
+
}
|
|
8199
|
+
};
|
|
7953
8200
|
let idleSweepTimer = null;
|
|
7954
|
-
if (idleSweepIntervalMs > 0 && (idleTimeoutMs > 0 || maxActiveProcesses > 0)) {
|
|
8201
|
+
if (idleSweepIntervalMs > 0 && (idleTimeoutMs > 0 || maxActiveProcesses > 0 || sessionEvictMs > 0)) {
|
|
7955
8202
|
idleSweepTimer = setInterval(async () => {
|
|
7956
8203
|
try {
|
|
7957
8204
|
let totalSwept = 0;
|
|
@@ -7970,7 +8217,18 @@ async function start(opts = {}) {
|
|
|
7970
8217
|
swept.forEach(broadcastShrink);
|
|
7971
8218
|
totalSwept += swept.length;
|
|
7972
8219
|
}
|
|
8220
|
+
if (sessionEvictMs > 0 && typeof provider.listEvictableSessions === "function") {
|
|
8221
|
+
const evictable = provider.listEvictableSessions(sessionEvictMs);
|
|
8222
|
+
for (const id of evictable) {
|
|
8223
|
+
await sessionManager.killSession(id);
|
|
8224
|
+
}
|
|
8225
|
+
if (evictable.length > 0) {
|
|
8226
|
+
console.log(`[Server] Idle GC: evicted ${evictable.length} stale session(s)`);
|
|
8227
|
+
totalSwept += evictable.length;
|
|
8228
|
+
}
|
|
8229
|
+
}
|
|
7973
8230
|
}
|
|
8231
|
+
const hasRunning = sessionManager.getActiveSessions().some((s) => s.status === "running");
|
|
7974
8232
|
if (totalSwept > 0) {
|
|
7975
8233
|
console.log(`[Server] Idle GC: swept ${totalSwept} idle session(s)`);
|
|
7976
8234
|
wsBridge.broadcast({
|
|
@@ -7978,6 +8236,9 @@ async function start(opts = {}) {
|
|
|
7978
8236
|
sessions: sessionManager.getActiveSessions()
|
|
7979
8237
|
});
|
|
7980
8238
|
}
|
|
8239
|
+
if (totalSwept > 0 || !hasRunning) {
|
|
8240
|
+
maybeGc();
|
|
8241
|
+
}
|
|
7981
8242
|
} catch (err) {
|
|
7982
8243
|
console.error("[Server] Idle GC failed:", err);
|
|
7983
8244
|
}
|
|
@@ -8051,7 +8312,7 @@ async function start(opts = {}) {
|
|
|
8051
8312
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
8052
8313
|
function getPackageVersion() {
|
|
8053
8314
|
try {
|
|
8054
|
-
const pkg = JSON.parse((0,
|
|
8315
|
+
const pkg = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path12.join)(__dirname, "..", "package.json"), "utf8"));
|
|
8055
8316
|
return pkg.version ?? "0.0.0";
|
|
8056
8317
|
} catch {
|
|
8057
8318
|
return "0.0.0";
|
|
@@ -8195,7 +8456,7 @@ ${t("startup.pairingReopened")}`);
|
|
|
8195
8456
|
}
|
|
8196
8457
|
}
|
|
8197
8458
|
function getLocalIp() {
|
|
8198
|
-
const interfaces = (0,
|
|
8459
|
+
const interfaces = (0, import_node_os12.networkInterfaces)();
|
|
8199
8460
|
for (const iface of Object.values(interfaces)) {
|
|
8200
8461
|
for (const addr of iface ?? []) {
|
|
8201
8462
|
if (addr.family === "IPv4" && !addr.internal) {
|