sessix-server 0.4.2 → 0.4.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 +1021 -32
- package/dist/server.js +1015 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,10 +24,10 @@ 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_os10 = require("os");
|
|
28
28
|
var import_node_fs4 = require("fs");
|
|
29
|
-
var
|
|
30
|
-
var
|
|
29
|
+
var import_node_path10 = require("path");
|
|
30
|
+
var import_node_child_process12 = require("child_process");
|
|
31
31
|
|
|
32
32
|
// src/i18n/locales/zh.ts
|
|
33
33
|
var zh = {
|
|
@@ -302,12 +302,12 @@ function t(key, params) {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// src/server.ts
|
|
305
|
-
var
|
|
306
|
-
var
|
|
307
|
-
var
|
|
308
|
-
var
|
|
309
|
-
var
|
|
310
|
-
var
|
|
305
|
+
var import_uuid9 = require("uuid");
|
|
306
|
+
var import_promises7 = require("fs/promises");
|
|
307
|
+
var import_node_os9 = require("os");
|
|
308
|
+
var import_node_path9 = require("path");
|
|
309
|
+
var import_node_child_process11 = require("child_process");
|
|
310
|
+
var import_node_util3 = require("util");
|
|
311
311
|
|
|
312
312
|
// src/providers/ProcessProvider.ts
|
|
313
313
|
var import_child_process = require("child_process");
|
|
@@ -538,6 +538,77 @@ var ProcessProvider = class {
|
|
|
538
538
|
getActiveSessions() {
|
|
539
539
|
return Array.from(this.activeSessions.values()).map((entry) => entry.session);
|
|
540
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* 清理空闲进程
|
|
543
|
+
*
|
|
544
|
+
* 找出所有 status='idle' 且 lastActiveAt 距今超过 maxIdleMs 的活跃进程,
|
|
545
|
+
* kill 进程释放内存。entry 保留在 activeSessions 中,用户下次 sendMessage
|
|
546
|
+
* 走 slow path 自动 --resume 重启进程。
|
|
547
|
+
*
|
|
548
|
+
* @returns 被 sweep 的 sessionId 列表
|
|
549
|
+
*/
|
|
550
|
+
async sweepIdleProcesses(maxIdleMs) {
|
|
551
|
+
const now = Date.now();
|
|
552
|
+
const swept = [];
|
|
553
|
+
for (const [sessionId, entry] of this.activeSessions) {
|
|
554
|
+
if (entry.process.exitCode !== null || entry.process.signalCode !== null) continue;
|
|
555
|
+
if (entry.session.status !== "idle") continue;
|
|
556
|
+
if (now - entry.session.lastActiveAt < maxIdleMs) continue;
|
|
557
|
+
const idleMin = Math.round((now - entry.session.lastActiveAt) / 6e4);
|
|
558
|
+
console.log(`[ProcessProvider] sweeping idle process: ${sessionId} (idle ${idleMin}m)`);
|
|
559
|
+
try {
|
|
560
|
+
entry.process.stdin?.end();
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
await killProcessCrossPlatform(entry.process);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
console.error(`[ProcessProvider] sweep kill failed for ${sessionId}:`, err);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
swept.push(sessionId);
|
|
570
|
+
}
|
|
571
|
+
return swept;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* LRU 上限清理
|
|
575
|
+
*
|
|
576
|
+
* 当活跃进程数超过 maxAlive 时,按 lastActiveAt 升序(最久未用优先)kill
|
|
577
|
+
* 状态为 idle 的进程,直到活跃数回到上限以内。
|
|
578
|
+
* running / waiting_question 状态的进程永远不会被 kill。
|
|
579
|
+
*
|
|
580
|
+
* @returns 被 sweep 的 sessionId 列表
|
|
581
|
+
*/
|
|
582
|
+
async sweepLruProcesses(maxAlive) {
|
|
583
|
+
const swept = [];
|
|
584
|
+
if (maxAlive <= 0) return swept;
|
|
585
|
+
const aliveEntries = Array.from(this.activeSessions.entries()).filter(
|
|
586
|
+
([, e]) => e.process.exitCode === null && e.process.signalCode === null
|
|
587
|
+
);
|
|
588
|
+
if (aliveEntries.length <= maxAlive) return swept;
|
|
589
|
+
const idleSorted = aliveEntries.filter(([, e]) => e.session.status === "idle").sort((a, b) => a[1].session.lastActiveAt - b[1].session.lastActiveAt);
|
|
590
|
+
let aliveCount = aliveEntries.length;
|
|
591
|
+
for (const [sessionId, entry] of idleSorted) {
|
|
592
|
+
if (aliveCount <= maxAlive) break;
|
|
593
|
+
const idleMin = Math.round((Date.now() - entry.session.lastActiveAt) / 6e4);
|
|
594
|
+
console.log(`[ProcessProvider] LRU sweep: ${sessionId} (idle ${idleMin}m, alive=${aliveCount}/${maxAlive})`);
|
|
595
|
+
try {
|
|
596
|
+
entry.process.stdin?.end();
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
await killProcessCrossPlatform(entry.process);
|
|
601
|
+
swept.push(sessionId);
|
|
602
|
+
aliveCount--;
|
|
603
|
+
} catch (err) {
|
|
604
|
+
console.error(`[ProcessProvider] LRU kill failed for ${sessionId}:`, err);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (aliveCount > maxAlive) {
|
|
608
|
+
console.warn(`[ProcessProvider] LRU sweep: ${aliveCount} alive after sweep > limit ${maxAlive}; remaining are running/waiting`);
|
|
609
|
+
}
|
|
610
|
+
return swept;
|
|
611
|
+
}
|
|
541
612
|
// ============================================
|
|
542
613
|
// 私有方法
|
|
543
614
|
// ============================================
|
|
@@ -1765,6 +1836,20 @@ var SessionManager = class {
|
|
|
1765
1836
|
isBufferTruncated(sessionId) {
|
|
1766
1837
|
return this.bufferTruncated.has(sessionId);
|
|
1767
1838
|
}
|
|
1839
|
+
/**
|
|
1840
|
+
* 缩减指定会话的事件缓冲区到最后 N 条,并标记 truncated
|
|
1841
|
+
*
|
|
1842
|
+
* 用于空闲进程被 sweep 后释放内存:缓冲区只为新订阅者重放服务,
|
|
1843
|
+
* 进程已死的会话可以通过 JSONL 文件补全完整历史,不需要保留全部内存事件。
|
|
1844
|
+
* 设置 bufferTruncated 后,客户端 subscribe 收到 session_history 时会从 JSONL 补齐。
|
|
1845
|
+
*/
|
|
1846
|
+
shrinkSessionBuffer(sessionId, keepLast = 100) {
|
|
1847
|
+
const buffer = this.sessionEventBuffers.get(sessionId);
|
|
1848
|
+
if (!buffer || buffer.length <= keepLast) return;
|
|
1849
|
+
buffer.splice(0, buffer.length - keepLast);
|
|
1850
|
+
this.bufferTruncated.add(sessionId);
|
|
1851
|
+
console.log(`[SessionManager] Session ${sessionId}: buffer shrunk to ${keepLast}, marked truncated`);
|
|
1852
|
+
}
|
|
1768
1853
|
/**
|
|
1769
1854
|
* 获取会话的项目路径(用于截断时从 JSONL 补全历史)
|
|
1770
1855
|
*/
|
|
@@ -4689,7 +4774,7 @@ var AuthManager = class extends import_events3.EventEmitter {
|
|
|
4689
4774
|
};
|
|
4690
4775
|
|
|
4691
4776
|
// src/server.ts
|
|
4692
|
-
var
|
|
4777
|
+
var import_promises8 = require("fs/promises");
|
|
4693
4778
|
|
|
4694
4779
|
// src/terminal/TerminalExecutor.ts
|
|
4695
4780
|
var import_node_child_process7 = require("child_process");
|
|
@@ -5192,8 +5277,762 @@ function kindOrder(k) {
|
|
|
5192
5277
|
return k === "device" ? 0 : k === "simulator" ? 1 : k === "mac" ? 2 : 3;
|
|
5193
5278
|
}
|
|
5194
5279
|
|
|
5195
|
-
// src/
|
|
5280
|
+
// src/commands/CommandDiscovery.ts
|
|
5281
|
+
var import_promises5 = require("fs/promises");
|
|
5282
|
+
var import_node_path7 = require("path");
|
|
5283
|
+
var import_node_crypto = require("crypto");
|
|
5284
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
5285
|
+
var MAX_README_BYTES = 256 * 1024;
|
|
5286
|
+
var SUBPACKAGE_DIRS = ["packages", "apps", "crates", "services"];
|
|
5287
|
+
var MAX_SCAN_PER_DIR = 30;
|
|
5288
|
+
var CommandDiscovery = class {
|
|
5289
|
+
cache = /* @__PURE__ */ new Map();
|
|
5290
|
+
async scan(projectPath, refresh = false) {
|
|
5291
|
+
if (!refresh) {
|
|
5292
|
+
const hit = this.cache.get(projectPath);
|
|
5293
|
+
if (hit && hit.expiresAt > Date.now()) return hit.commands;
|
|
5294
|
+
}
|
|
5295
|
+
const collector = [];
|
|
5296
|
+
await Promise.all([
|
|
5297
|
+
this.scanPackageJson(projectPath, "", collector),
|
|
5298
|
+
this.scanMakefile(projectPath, "", collector),
|
|
5299
|
+
this.scanJustfile(projectPath, "", collector),
|
|
5300
|
+
this.scanCargo(projectPath, "", collector),
|
|
5301
|
+
this.scanCompose(projectPath, "", collector),
|
|
5302
|
+
this.scanReadme(projectPath, "README.md", "readme", collector),
|
|
5303
|
+
this.scanReadme(projectPath, "CLAUDE.md", "claude.md", collector)
|
|
5304
|
+
]);
|
|
5305
|
+
for (const sub of SUBPACKAGE_DIRS) {
|
|
5306
|
+
const subRoot = (0, import_node_path7.join)(projectPath, sub);
|
|
5307
|
+
let entries;
|
|
5308
|
+
try {
|
|
5309
|
+
entries = await (0, import_promises5.readdir)(subRoot);
|
|
5310
|
+
} catch {
|
|
5311
|
+
continue;
|
|
5312
|
+
}
|
|
5313
|
+
let scanned = 0;
|
|
5314
|
+
for (const name of entries) {
|
|
5315
|
+
if (name.startsWith(".") || scanned >= MAX_SCAN_PER_DIR) continue;
|
|
5316
|
+
const childAbs = (0, import_node_path7.join)(subRoot, name);
|
|
5317
|
+
try {
|
|
5318
|
+
const s = await (0, import_promises5.stat)(childAbs);
|
|
5319
|
+
if (!s.isDirectory()) continue;
|
|
5320
|
+
} catch {
|
|
5321
|
+
continue;
|
|
5322
|
+
}
|
|
5323
|
+
scanned++;
|
|
5324
|
+
const rel = `${sub}/${name}`;
|
|
5325
|
+
await Promise.all([
|
|
5326
|
+
this.scanPackageJson(projectPath, rel, collector),
|
|
5327
|
+
this.scanCargo(projectPath, rel, collector)
|
|
5328
|
+
]);
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5332
|
+
const deduped = [];
|
|
5333
|
+
for (const c of collector) {
|
|
5334
|
+
if (seen.has(c.id)) continue;
|
|
5335
|
+
seen.add(c.id);
|
|
5336
|
+
deduped.push(c);
|
|
5337
|
+
}
|
|
5338
|
+
deduped.sort((a, b) => {
|
|
5339
|
+
const ca = categoryWeight(a.category) - categoryWeight(b.category);
|
|
5340
|
+
if (ca !== 0) return ca;
|
|
5341
|
+
const sa = sourceWeight(a.source) - sourceWeight(b.source);
|
|
5342
|
+
if (sa !== 0) return sa;
|
|
5343
|
+
return a.title.localeCompare(b.title);
|
|
5344
|
+
});
|
|
5345
|
+
this.cache.set(projectPath, { commands: deduped, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
5346
|
+
return deduped;
|
|
5347
|
+
}
|
|
5348
|
+
invalidate(projectPath) {
|
|
5349
|
+
if (projectPath) this.cache.delete(projectPath);
|
|
5350
|
+
else this.cache.clear();
|
|
5351
|
+
}
|
|
5352
|
+
// ============================================
|
|
5353
|
+
// 各来源扫描器
|
|
5354
|
+
// ============================================
|
|
5355
|
+
async scanPackageJson(rootPath, subDir, out) {
|
|
5356
|
+
const file = subDir ? `${subDir}/package.json` : "package.json";
|
|
5357
|
+
const abs = (0, import_node_path7.join)(rootPath, file);
|
|
5358
|
+
let raw;
|
|
5359
|
+
try {
|
|
5360
|
+
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
5361
|
+
} catch {
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5364
|
+
let pkg;
|
|
5365
|
+
try {
|
|
5366
|
+
pkg = JSON.parse(raw);
|
|
5367
|
+
} catch {
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
if (!pkg.scripts) return;
|
|
5371
|
+
for (const [name, script] of Object.entries(pkg.scripts)) {
|
|
5372
|
+
if (typeof script !== "string") continue;
|
|
5373
|
+
const command = subDir ? `npm --workspace=${subDir} run ${name}` : `npm run ${name}`;
|
|
5374
|
+
const title = subDir ? `${pkg.name ?? subDir.split("/").pop()}: ${name}` : name;
|
|
5375
|
+
out.push(makeCommand({
|
|
5376
|
+
title,
|
|
5377
|
+
command,
|
|
5378
|
+
cwd: "",
|
|
5379
|
+
source: "package.json",
|
|
5380
|
+
sourceFile: file,
|
|
5381
|
+
description: script,
|
|
5382
|
+
category: classifyByName(name) ?? classifyByCommand(script)
|
|
5383
|
+
}));
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
async scanMakefile(rootPath, subDir, out) {
|
|
5387
|
+
const file = subDir ? `${subDir}/Makefile` : "Makefile";
|
|
5388
|
+
const abs = (0, import_node_path7.join)(rootPath, file);
|
|
5389
|
+
let raw;
|
|
5390
|
+
try {
|
|
5391
|
+
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
5392
|
+
} catch {
|
|
5393
|
+
return;
|
|
5394
|
+
}
|
|
5395
|
+
const lines = raw.split("\n");
|
|
5396
|
+
let lastComment;
|
|
5397
|
+
const targetRegex = /^([a-zA-Z][a-zA-Z0-9_-]*)\s*:(?!=)/;
|
|
5398
|
+
for (const line of lines) {
|
|
5399
|
+
const trim = line.trim();
|
|
5400
|
+
if (trim.startsWith("#")) {
|
|
5401
|
+
lastComment = trim.replace(/^#+\s?/, "").trim() || void 0;
|
|
5402
|
+
continue;
|
|
5403
|
+
}
|
|
5404
|
+
if (trim === "") {
|
|
5405
|
+
lastComment = void 0;
|
|
5406
|
+
continue;
|
|
5407
|
+
}
|
|
5408
|
+
const match = targetRegex.exec(line);
|
|
5409
|
+
if (!match) {
|
|
5410
|
+
lastComment = void 0;
|
|
5411
|
+
continue;
|
|
5412
|
+
}
|
|
5413
|
+
const target = match[1];
|
|
5414
|
+
if (target === ".PHONY" || target === "default" && trim.startsWith("default:")) continue;
|
|
5415
|
+
out.push(makeCommand({
|
|
5416
|
+
title: target,
|
|
5417
|
+
command: `make ${target}`,
|
|
5418
|
+
cwd: subDir,
|
|
5419
|
+
source: "makefile",
|
|
5420
|
+
sourceFile: file,
|
|
5421
|
+
description: lastComment,
|
|
5422
|
+
category: classifyByName(target)
|
|
5423
|
+
}));
|
|
5424
|
+
lastComment = void 0;
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
async scanJustfile(rootPath, subDir, out) {
|
|
5428
|
+
const file = subDir ? `${subDir}/justfile` : "justfile";
|
|
5429
|
+
const abs = (0, import_node_path7.join)(rootPath, file);
|
|
5430
|
+
let raw;
|
|
5431
|
+
try {
|
|
5432
|
+
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
5433
|
+
} catch {
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
const lines = raw.split("\n");
|
|
5437
|
+
let lastComment;
|
|
5438
|
+
const recipeRegex = /^([a-zA-Z][a-zA-Z0-9_-]*)\s*(?:[a-zA-Z0-9_=" ]*)?\s*:/;
|
|
5439
|
+
for (const line of lines) {
|
|
5440
|
+
const trim = line.trim();
|
|
5441
|
+
if (trim.startsWith("#")) {
|
|
5442
|
+
lastComment = trim.replace(/^#+\s?/, "").trim() || void 0;
|
|
5443
|
+
continue;
|
|
5444
|
+
}
|
|
5445
|
+
if (trim === "") {
|
|
5446
|
+
lastComment = void 0;
|
|
5447
|
+
continue;
|
|
5448
|
+
}
|
|
5449
|
+
if (line.startsWith(" ") || line.startsWith(" ")) continue;
|
|
5450
|
+
const match = recipeRegex.exec(line);
|
|
5451
|
+
if (!match) {
|
|
5452
|
+
lastComment = void 0;
|
|
5453
|
+
continue;
|
|
5454
|
+
}
|
|
5455
|
+
const recipe = match[1];
|
|
5456
|
+
out.push(makeCommand({
|
|
5457
|
+
title: recipe,
|
|
5458
|
+
command: `just ${recipe}`,
|
|
5459
|
+
cwd: subDir,
|
|
5460
|
+
source: "justfile",
|
|
5461
|
+
sourceFile: file,
|
|
5462
|
+
description: lastComment,
|
|
5463
|
+
category: classifyByName(recipe)
|
|
5464
|
+
}));
|
|
5465
|
+
lastComment = void 0;
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
async scanCargo(rootPath, subDir, out) {
|
|
5469
|
+
const file = subDir ? `${subDir}/Cargo.toml` : "Cargo.toml";
|
|
5470
|
+
const abs = (0, import_node_path7.join)(rootPath, file);
|
|
5471
|
+
try {
|
|
5472
|
+
await (0, import_promises5.stat)(abs);
|
|
5473
|
+
} catch {
|
|
5474
|
+
return;
|
|
5475
|
+
}
|
|
5476
|
+
const presets = [
|
|
5477
|
+
{ title: "cargo build", command: "cargo build", category: "build", description: "Compile in debug mode" },
|
|
5478
|
+
{ title: "cargo build --release", command: "cargo build --release", category: "build", description: "Compile in release mode" },
|
|
5479
|
+
{ title: "cargo run", command: "cargo run", category: "dev", description: "Build and run" },
|
|
5480
|
+
{ title: "cargo test", command: "cargo test", category: "test", description: "Run all tests" },
|
|
5481
|
+
{ title: "cargo check", command: "cargo check", category: "lint", description: "Type-check without producing binary" },
|
|
5482
|
+
{ title: "cargo clippy", command: "cargo clippy", category: "lint", description: "Lint with clippy" },
|
|
5483
|
+
{ title: "cargo fmt", command: "cargo fmt", category: "lint", description: "Format source" }
|
|
5484
|
+
];
|
|
5485
|
+
for (const p of presets) {
|
|
5486
|
+
out.push(makeCommand({
|
|
5487
|
+
title: subDir ? `${subDir.split("/").pop()}: ${p.title}` : p.title,
|
|
5488
|
+
command: p.command,
|
|
5489
|
+
cwd: subDir,
|
|
5490
|
+
source: "cargo",
|
|
5491
|
+
sourceFile: file,
|
|
5492
|
+
description: p.description,
|
|
5493
|
+
category: p.category
|
|
5494
|
+
}));
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
5497
|
+
async scanCompose(rootPath, subDir, out) {
|
|
5498
|
+
for (const name of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
|
|
5499
|
+
const file = subDir ? `${subDir}/${name}` : name;
|
|
5500
|
+
try {
|
|
5501
|
+
await (0, import_promises5.stat)((0, import_node_path7.join)(rootPath, file));
|
|
5502
|
+
} catch {
|
|
5503
|
+
continue;
|
|
5504
|
+
}
|
|
5505
|
+
const presets = [
|
|
5506
|
+
{ title: "docker compose up", cmd: "docker compose up", cat: "dev", desc: "Start services" },
|
|
5507
|
+
{ title: "docker compose up -d", cmd: "docker compose up -d", cat: "dev", desc: "Start services in background" },
|
|
5508
|
+
{ title: "docker compose down", cmd: "docker compose down", cat: "other", desc: "Stop and remove services" },
|
|
5509
|
+
{ title: "docker compose build", cmd: "docker compose build", cat: "build", desc: "Build service images" },
|
|
5510
|
+
{ title: "docker compose logs -f", cmd: "docker compose logs -f", cat: "other", desc: "Tail service logs" }
|
|
5511
|
+
];
|
|
5512
|
+
for (const p of presets) {
|
|
5513
|
+
out.push(makeCommand({
|
|
5514
|
+
title: p.title,
|
|
5515
|
+
command: p.cmd,
|
|
5516
|
+
cwd: subDir,
|
|
5517
|
+
source: "compose",
|
|
5518
|
+
sourceFile: file,
|
|
5519
|
+
description: p.desc,
|
|
5520
|
+
category: p.cat
|
|
5521
|
+
}));
|
|
5522
|
+
}
|
|
5523
|
+
return;
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
async scanReadme(rootPath, fileName, source, out) {
|
|
5527
|
+
const abs = (0, import_node_path7.join)(rootPath, fileName);
|
|
5528
|
+
let raw;
|
|
5529
|
+
try {
|
|
5530
|
+
const s = await (0, import_promises5.stat)(abs);
|
|
5531
|
+
if (s.size > MAX_README_BYTES) return;
|
|
5532
|
+
raw = await (0, import_promises5.readFile)(abs, "utf8");
|
|
5533
|
+
} catch {
|
|
5534
|
+
return;
|
|
5535
|
+
}
|
|
5536
|
+
const fenceRegex = /```(?:bash|sh|shell|zsh)\s*\n([\s\S]*?)```/gi;
|
|
5537
|
+
let match;
|
|
5538
|
+
while ((match = fenceRegex.exec(raw)) !== null) {
|
|
5539
|
+
const block = match[1];
|
|
5540
|
+
const blockLines = block.split("\n");
|
|
5541
|
+
let blockHeading;
|
|
5542
|
+
const beforeText = raw.slice(0, match.index).split("\n").reverse();
|
|
5543
|
+
for (const prev of beforeText) {
|
|
5544
|
+
const t2 = prev.trim();
|
|
5545
|
+
if (t2 === "") continue;
|
|
5546
|
+
const head = /^#{1,6}\s+(.+)$/.exec(t2);
|
|
5547
|
+
if (head) blockHeading = head[1].replace(/[#*`]/g, "").trim();
|
|
5548
|
+
break;
|
|
5549
|
+
}
|
|
5550
|
+
const merged = [];
|
|
5551
|
+
let pending = "";
|
|
5552
|
+
for (const rawLine of blockLines) {
|
|
5553
|
+
if (rawLine.trimEnd().endsWith("\\")) {
|
|
5554
|
+
pending += rawLine.trimEnd().slice(0, -1) + " ";
|
|
5555
|
+
continue;
|
|
5556
|
+
}
|
|
5557
|
+
merged.push(pending + rawLine);
|
|
5558
|
+
pending = "";
|
|
5559
|
+
}
|
|
5560
|
+
if (pending) merged.push(pending);
|
|
5561
|
+
for (const rawLine of merged) {
|
|
5562
|
+
const cmd = sanitizeBashLine(rawLine);
|
|
5563
|
+
if (!cmd) continue;
|
|
5564
|
+
const { command: cleanCmd, inlineComment } = splitInlineComment(cmd);
|
|
5565
|
+
const title = synthesizeTitle(cleanCmd);
|
|
5566
|
+
out.push(makeCommand({
|
|
5567
|
+
title,
|
|
5568
|
+
command: cleanCmd,
|
|
5569
|
+
cwd: "",
|
|
5570
|
+
source,
|
|
5571
|
+
sourceFile: fileName,
|
|
5572
|
+
description: inlineComment ?? blockHeading,
|
|
5573
|
+
category: classifyByCommand(cleanCmd)
|
|
5574
|
+
}));
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
};
|
|
5579
|
+
function makeCommand(input) {
|
|
5580
|
+
const id = (0, import_node_crypto.createHash)("sha1").update(`${input.source}|${input.sourceFile}|${input.command}|${input.cwd}`).digest("hex").slice(0, 12);
|
|
5581
|
+
return {
|
|
5582
|
+
id,
|
|
5583
|
+
title: input.title,
|
|
5584
|
+
command: input.command,
|
|
5585
|
+
cwd: input.cwd,
|
|
5586
|
+
source: input.source,
|
|
5587
|
+
sourceFile: input.sourceFile,
|
|
5588
|
+
description: input.description,
|
|
5589
|
+
category: input.category ?? classifyByCommand(input.command) ?? "other"
|
|
5590
|
+
};
|
|
5591
|
+
}
|
|
5592
|
+
function sanitizeBashLine(line) {
|
|
5593
|
+
let l = line.trim();
|
|
5594
|
+
if (!l) return null;
|
|
5595
|
+
if (l.startsWith("#")) return null;
|
|
5596
|
+
l = l.replace(/^[$>]\s*/, "");
|
|
5597
|
+
if (!l) return null;
|
|
5598
|
+
if (/^[A-Z_]+=/.test(l) && !/\s/.test(l)) return null;
|
|
5599
|
+
if (l === "EOF" || l === "EOT") return null;
|
|
5600
|
+
if (l.startsWith("//") || l.startsWith("//#")) return null;
|
|
5601
|
+
if (l.length > 400) return null;
|
|
5602
|
+
if (/<.+>/.test(l) && /your[-_]/.test(l.toLowerCase())) return null;
|
|
5603
|
+
return l;
|
|
5604
|
+
}
|
|
5605
|
+
function synthesizeTitle(cmd) {
|
|
5606
|
+
let work = cmd;
|
|
5607
|
+
while (/^[A-Z_][A-Z0-9_]*=/.test(work)) {
|
|
5608
|
+
const m = /^[A-Z_][A-Z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+/.exec(work);
|
|
5609
|
+
if (!m) break;
|
|
5610
|
+
work = work.slice(m[0].length);
|
|
5611
|
+
}
|
|
5612
|
+
const cdMatch = /^cd\s+\S+\s*&&\s*(.+)$/.exec(work);
|
|
5613
|
+
if (cdMatch) work = cdMatch[1];
|
|
5614
|
+
const tokens = work.split(/\s+/).filter(Boolean);
|
|
5615
|
+
const head = tokens.slice(0, 3).join(" ");
|
|
5616
|
+
return head.length > 60 ? head.slice(0, 60) + "\u2026" : head;
|
|
5617
|
+
}
|
|
5618
|
+
function splitInlineComment(line) {
|
|
5619
|
+
let inSingle = false;
|
|
5620
|
+
let inDouble = false;
|
|
5621
|
+
for (let i = 0; i < line.length; i++) {
|
|
5622
|
+
const ch = line[i];
|
|
5623
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
5624
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
5625
|
+
else if (ch === "#" && !inSingle && !inDouble && (i === 0 || /\s/.test(line[i - 1]))) {
|
|
5626
|
+
const cmd = line.slice(0, i).trim();
|
|
5627
|
+
const comment = line.slice(i + 1).trim();
|
|
5628
|
+
return { command: cmd, inlineComment: comment.length > 0 ? comment : void 0 };
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
return { command: line };
|
|
5632
|
+
}
|
|
5633
|
+
function classifyByName(name) {
|
|
5634
|
+
const lower = name.toLowerCase();
|
|
5635
|
+
if (/(^|[:_-])(build|compile|bundle|prebuild)([:_-]|$)/.test(lower)) return "build";
|
|
5636
|
+
if (/(^|[:_-])(test|spec|jest|vitest|e2e)([:_-]|$)/.test(lower)) return "test";
|
|
5637
|
+
if (/(^|[:_-])(dev|start|serve|watch|run)([:_-]|$)/.test(lower)) return "dev";
|
|
5638
|
+
if (/(^|[:_-])(lint|format|fmt|check|typecheck)([:_-]|$)/.test(lower)) return "lint";
|
|
5639
|
+
if (/(^|[:_-])(install|setup|init|bootstrap)([:_-]|$)/.test(lower)) return "install";
|
|
5640
|
+
if (/(^|[:_-])(deploy|publish|release|ship)([:_-]|$)/.test(lower)) return "deploy";
|
|
5641
|
+
return void 0;
|
|
5642
|
+
}
|
|
5643
|
+
function classifyByCommand(cmd) {
|
|
5644
|
+
const lower = cmd.toLowerCase();
|
|
5645
|
+
if (/\b(build|compile|bundle|prebuild|tsup|webpack|esbuild|vite build|next build)\b/.test(lower)) return "build";
|
|
5646
|
+
if (/\b(test|jest|vitest|mocha|pytest|cargo test|go test)\b/.test(lower)) return "test";
|
|
5647
|
+
if (/\b(dev|start|serve|watch|nodemon|tsx watch|next dev|expo start)\b/.test(lower)) return "dev";
|
|
5648
|
+
if (/\b(lint|eslint|tsc|tslint|fmt|format|prettier|clippy)\b/.test(lower)) return "lint";
|
|
5649
|
+
if (/\b(install|setup|bootstrap)\b/.test(lower) && !/\binstall\s+/.test(lower)) return "install";
|
|
5650
|
+
if (/^npm install\b|^pnpm install\b|^yarn install\b|^yarn\s*$|^pnpm\s*$/.test(lower)) return "install";
|
|
5651
|
+
if (/\b(deploy|publish|release)\b/.test(lower)) return "deploy";
|
|
5652
|
+
return "other";
|
|
5653
|
+
}
|
|
5654
|
+
function categoryWeight(c) {
|
|
5655
|
+
return {
|
|
5656
|
+
dev: 0,
|
|
5657
|
+
build: 1,
|
|
5658
|
+
test: 2,
|
|
5659
|
+
lint: 3,
|
|
5660
|
+
install: 4,
|
|
5661
|
+
deploy: 5,
|
|
5662
|
+
other: 6
|
|
5663
|
+
}[c];
|
|
5664
|
+
}
|
|
5665
|
+
function sourceWeight(s) {
|
|
5666
|
+
return {
|
|
5667
|
+
"package.json": 0,
|
|
5668
|
+
makefile: 1,
|
|
5669
|
+
justfile: 2,
|
|
5670
|
+
taskfile: 3,
|
|
5671
|
+
cargo: 4,
|
|
5672
|
+
compose: 5,
|
|
5673
|
+
readme: 6,
|
|
5674
|
+
"claude.md": 7
|
|
5675
|
+
}[s];
|
|
5676
|
+
}
|
|
5677
|
+
|
|
5678
|
+
// src/git/GitExecutor.ts
|
|
5196
5679
|
var import_node_child_process9 = require("child_process");
|
|
5680
|
+
var import_node_util2 = require("util");
|
|
5681
|
+
var import_uuid7 = require("uuid");
|
|
5682
|
+
var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process9.exec);
|
|
5683
|
+
var STATUS_TIMEOUT_MS = 15e3;
|
|
5684
|
+
var COMMIT_TIMEOUT_MS = 6e4;
|
|
5685
|
+
var PUSH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5686
|
+
var GitExecutor = class {
|
|
5687
|
+
eventCallbacks = [];
|
|
5688
|
+
onEvent(callback) {
|
|
5689
|
+
this.eventCallbacks.push(callback);
|
|
5690
|
+
return () => {
|
|
5691
|
+
const idx = this.eventCallbacks.indexOf(callback);
|
|
5692
|
+
if (idx !== -1) this.eventCallbacks.splice(idx, 1);
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5695
|
+
emit(event) {
|
|
5696
|
+
for (const cb of this.eventCallbacks) {
|
|
5697
|
+
try {
|
|
5698
|
+
cb(event);
|
|
5699
|
+
} catch (err) {
|
|
5700
|
+
console.error("[GitExecutor] Event callback error:", err);
|
|
5701
|
+
}
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
async detectStatus(projectPath) {
|
|
5705
|
+
const opts = { cwd: projectPath, timeout: STATUS_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 };
|
|
5706
|
+
try {
|
|
5707
|
+
await execAsync2("git rev-parse --is-inside-work-tree", opts);
|
|
5708
|
+
} catch {
|
|
5709
|
+
return { isRepo: false, hasRemote: false, changes: [] };
|
|
5710
|
+
}
|
|
5711
|
+
let root;
|
|
5712
|
+
try {
|
|
5713
|
+
const { stdout } = await execAsync2("git rev-parse --show-toplevel", opts);
|
|
5714
|
+
root = stdout.trim();
|
|
5715
|
+
} catch {
|
|
5716
|
+
}
|
|
5717
|
+
let branch;
|
|
5718
|
+
try {
|
|
5719
|
+
const { stdout } = await execAsync2("git symbolic-ref --short HEAD", opts);
|
|
5720
|
+
branch = stdout.trim();
|
|
5721
|
+
} catch {
|
|
5722
|
+
branch = void 0;
|
|
5723
|
+
}
|
|
5724
|
+
let upstream;
|
|
5725
|
+
try {
|
|
5726
|
+
const { stdout } = await execAsync2("git rev-parse --abbrev-ref --symbolic-full-name @{u}", opts);
|
|
5727
|
+
upstream = stdout.trim();
|
|
5728
|
+
} catch {
|
|
5729
|
+
}
|
|
5730
|
+
let hasRemote = false;
|
|
5731
|
+
try {
|
|
5732
|
+
const { stdout } = await execAsync2("git remote", opts);
|
|
5733
|
+
hasRemote = stdout.trim().length > 0;
|
|
5734
|
+
} catch {
|
|
5735
|
+
}
|
|
5736
|
+
let ahead;
|
|
5737
|
+
let behind;
|
|
5738
|
+
if (upstream) {
|
|
5739
|
+
try {
|
|
5740
|
+
const { stdout } = await execAsync2("git rev-list --left-right --count HEAD...@{u}", opts);
|
|
5741
|
+
const [a, b] = stdout.trim().split(/\s+/);
|
|
5742
|
+
ahead = Number(a);
|
|
5743
|
+
behind = Number(b);
|
|
5744
|
+
} catch {
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
const changes = await this.parsePorcelain(projectPath);
|
|
5748
|
+
return {
|
|
5749
|
+
isRepo: true,
|
|
5750
|
+
root,
|
|
5751
|
+
branch,
|
|
5752
|
+
upstream,
|
|
5753
|
+
hasRemote,
|
|
5754
|
+
changes,
|
|
5755
|
+
ahead,
|
|
5756
|
+
behind
|
|
5757
|
+
};
|
|
5758
|
+
}
|
|
5759
|
+
async parsePorcelain(projectPath) {
|
|
5760
|
+
let stdout;
|
|
5761
|
+
try {
|
|
5762
|
+
const r = await execAsync2("git status --porcelain=v1 -z", {
|
|
5763
|
+
cwd: projectPath,
|
|
5764
|
+
timeout: STATUS_TIMEOUT_MS,
|
|
5765
|
+
maxBuffer: 8 * 1024 * 1024
|
|
5766
|
+
});
|
|
5767
|
+
stdout = r.stdout;
|
|
5768
|
+
} catch {
|
|
5769
|
+
return [];
|
|
5770
|
+
}
|
|
5771
|
+
const changes = [];
|
|
5772
|
+
const records = stdout.split("\0");
|
|
5773
|
+
for (let i = 0; i < records.length; i++) {
|
|
5774
|
+
const rec = records[i];
|
|
5775
|
+
if (!rec) continue;
|
|
5776
|
+
if (rec.length < 3) continue;
|
|
5777
|
+
const x = rec.charAt(0);
|
|
5778
|
+
const y = rec.charAt(1);
|
|
5779
|
+
const path2 = rec.slice(3);
|
|
5780
|
+
const isRename = x === "R" || x === "C";
|
|
5781
|
+
if (isRename) {
|
|
5782
|
+
i += 1;
|
|
5783
|
+
}
|
|
5784
|
+
const untracked = x === "?" && y === "?";
|
|
5785
|
+
const staged = !untracked && x !== " " && x !== "?";
|
|
5786
|
+
changes.push({
|
|
5787
|
+
path: path2,
|
|
5788
|
+
staged,
|
|
5789
|
+
untracked,
|
|
5790
|
+
code: `${x}${y}`
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5793
|
+
return changes;
|
|
5794
|
+
}
|
|
5795
|
+
/**
|
|
5796
|
+
* 执行 commit(可选连带 push)。
|
|
5797
|
+
* - 若提供 files:先 git add 这些路径
|
|
5798
|
+
* - 若未提供 files:默认 git add -A(提交所有变更)
|
|
5799
|
+
*/
|
|
5800
|
+
async commit(sessionId, projectPath, message, files, alsoPush) {
|
|
5801
|
+
const opId = (0, import_uuid7.v4)();
|
|
5802
|
+
this.runSequence(sessionId, opId, "commit", projectPath, [
|
|
5803
|
+
files && files.length > 0 ? ["git", "add", "--", ...files] : ["git", "add", "-A"],
|
|
5804
|
+
["git", "commit", "-m", message]
|
|
5805
|
+
], COMMIT_TIMEOUT_MS).then(async (ok) => {
|
|
5806
|
+
if (ok && alsoPush) {
|
|
5807
|
+
await this.runSequence(sessionId, opId, "push", projectPath, [
|
|
5808
|
+
["git", "push"]
|
|
5809
|
+
], PUSH_TIMEOUT_MS);
|
|
5810
|
+
}
|
|
5811
|
+
}).catch((err) => {
|
|
5812
|
+
console.error("[GitExecutor] commit error:", err);
|
|
5813
|
+
});
|
|
5814
|
+
return opId;
|
|
5815
|
+
}
|
|
5816
|
+
async push(sessionId, projectPath) {
|
|
5817
|
+
const opId = (0, import_uuid7.v4)();
|
|
5818
|
+
this.runSequence(sessionId, opId, "push", projectPath, [
|
|
5819
|
+
["git", "push"]
|
|
5820
|
+
], PUSH_TIMEOUT_MS).catch((err) => {
|
|
5821
|
+
console.error("[GitExecutor] push error:", err);
|
|
5822
|
+
});
|
|
5823
|
+
return opId;
|
|
5824
|
+
}
|
|
5825
|
+
/**
|
|
5826
|
+
* 顺序执行一组命令,任一失败则停止。返回是否全部成功。
|
|
5827
|
+
* 每条命令的输出和最后一条命令的退出事件统一打到同一 phase。
|
|
5828
|
+
*/
|
|
5829
|
+
async runSequence(sessionId, opId, phase, projectPath, commands, timeoutMs) {
|
|
5830
|
+
let lastCode = 0;
|
|
5831
|
+
let lastSignal = null;
|
|
5832
|
+
for (const cmd of commands) {
|
|
5833
|
+
const { code, signal } = await this.runOne(sessionId, opId, phase, projectPath, cmd, timeoutMs);
|
|
5834
|
+
lastCode = code;
|
|
5835
|
+
lastSignal = signal;
|
|
5836
|
+
if (code !== 0) break;
|
|
5837
|
+
}
|
|
5838
|
+
this.emit({ type: "git_exit", sessionId, opId, phase, code: lastCode, signal: lastSignal });
|
|
5839
|
+
return lastCode === 0;
|
|
5840
|
+
}
|
|
5841
|
+
runOne(sessionId, opId, phase, projectPath, cmd, timeoutMs) {
|
|
5842
|
+
return new Promise((resolve) => {
|
|
5843
|
+
const display = cmd.map((p) => /\s/.test(p) ? `"${p}"` : p).join(" ");
|
|
5844
|
+
this.emit({
|
|
5845
|
+
type: "git_output",
|
|
5846
|
+
sessionId,
|
|
5847
|
+
opId,
|
|
5848
|
+
phase,
|
|
5849
|
+
stream: "stdout",
|
|
5850
|
+
data: `$ ${display}
|
|
5851
|
+
`
|
|
5852
|
+
});
|
|
5853
|
+
let proc;
|
|
5854
|
+
try {
|
|
5855
|
+
proc = (0, import_node_child_process9.spawn)(cmd[0], cmd.slice(1), {
|
|
5856
|
+
cwd: projectPath,
|
|
5857
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5858
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
5859
|
+
});
|
|
5860
|
+
} catch (err) {
|
|
5861
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5862
|
+
this.emit({ type: "git_output", sessionId, opId, phase, stream: "stderr", data: `[spawn error] ${msg}
|
|
5863
|
+
` });
|
|
5864
|
+
resolve({ code: 1, signal: null });
|
|
5865
|
+
return;
|
|
5866
|
+
}
|
|
5867
|
+
proc.stdout?.on("data", (chunk) => {
|
|
5868
|
+
this.emit({ type: "git_output", sessionId, opId, phase, stream: "stdout", data: chunk.toString() });
|
|
5869
|
+
});
|
|
5870
|
+
proc.stderr?.on("data", (chunk) => {
|
|
5871
|
+
this.emit({ type: "git_output", sessionId, opId, phase, stream: "stderr", data: chunk.toString() });
|
|
5872
|
+
});
|
|
5873
|
+
proc.on("error", (err) => {
|
|
5874
|
+
this.emit({ type: "git_output", sessionId, opId, phase, stream: "stderr", data: `[error] ${err.message}
|
|
5875
|
+
` });
|
|
5876
|
+
});
|
|
5877
|
+
const timer = setTimeout(() => {
|
|
5878
|
+
try {
|
|
5879
|
+
proc.kill("SIGTERM");
|
|
5880
|
+
} catch {
|
|
5881
|
+
}
|
|
5882
|
+
}, timeoutMs);
|
|
5883
|
+
proc.on("exit", (code, signal) => {
|
|
5884
|
+
clearTimeout(timer);
|
|
5885
|
+
resolve({ code, signal });
|
|
5886
|
+
});
|
|
5887
|
+
});
|
|
5888
|
+
}
|
|
5889
|
+
};
|
|
5890
|
+
|
|
5891
|
+
// src/scheduling/ScheduledSessionManager.ts
|
|
5892
|
+
var import_promises6 = require("fs/promises");
|
|
5893
|
+
var import_node_os8 = require("os");
|
|
5894
|
+
var import_node_path8 = require("path");
|
|
5895
|
+
var import_uuid8 = require("uuid");
|
|
5896
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
5897
|
+
var ScheduledSessionManager = class {
|
|
5898
|
+
tasks = /* @__PURE__ */ new Map();
|
|
5899
|
+
storeFile;
|
|
5900
|
+
onFire;
|
|
5901
|
+
onChange;
|
|
5902
|
+
onFired;
|
|
5903
|
+
persistTimer = null;
|
|
5904
|
+
constructor(opts) {
|
|
5905
|
+
this.storeFile = opts.storeFile ?? (0, import_node_path8.join)((0, import_node_os8.homedir)(), ".sessix", "scheduled-sessions.json");
|
|
5906
|
+
this.onFire = opts.onFire;
|
|
5907
|
+
this.onChange = opts.onChange;
|
|
5908
|
+
this.onFired = opts.onFired;
|
|
5909
|
+
}
|
|
5910
|
+
/** 启动时从磁盘恢复任务表,已过期的立刻触发 */
|
|
5911
|
+
async load() {
|
|
5912
|
+
let raw;
|
|
5913
|
+
try {
|
|
5914
|
+
raw = await (0, import_promises6.readFile)(this.storeFile, "utf8");
|
|
5915
|
+
} catch {
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5918
|
+
let parsed;
|
|
5919
|
+
try {
|
|
5920
|
+
parsed = JSON.parse(raw);
|
|
5921
|
+
} catch {
|
|
5922
|
+
return;
|
|
5923
|
+
}
|
|
5924
|
+
if (!Array.isArray(parsed)) return;
|
|
5925
|
+
for (const item of parsed) {
|
|
5926
|
+
if (!isValidTask(item)) continue;
|
|
5927
|
+
this.scheduleTimer(item);
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
/** 注册一个定时任务(payload 由调用方校验) */
|
|
5931
|
+
schedule(scheduledAt, payload) {
|
|
5932
|
+
const task = {
|
|
5933
|
+
id: (0, import_uuid8.v4)(),
|
|
5934
|
+
scheduledAt,
|
|
5935
|
+
createdAt: Date.now(),
|
|
5936
|
+
payload
|
|
5937
|
+
};
|
|
5938
|
+
this.scheduleTimer(task);
|
|
5939
|
+
this.persist();
|
|
5940
|
+
this.notifyChange();
|
|
5941
|
+
return task;
|
|
5942
|
+
}
|
|
5943
|
+
/** 取消任务,返回是否成功 */
|
|
5944
|
+
cancel(id) {
|
|
5945
|
+
const entry = this.tasks.get(id);
|
|
5946
|
+
if (!entry) return false;
|
|
5947
|
+
clearTimeout(entry.timer);
|
|
5948
|
+
this.tasks.delete(id);
|
|
5949
|
+
this.persist();
|
|
5950
|
+
this.notifyChange();
|
|
5951
|
+
return true;
|
|
5952
|
+
}
|
|
5953
|
+
/** 列出所有未触发的任务(按时间升序) */
|
|
5954
|
+
list() {
|
|
5955
|
+
return [...this.tasks.values()].map((e) => e.task).sort((a, b) => a.scheduledAt - b.scheduledAt);
|
|
5956
|
+
}
|
|
5957
|
+
/** 优雅关闭(清空定时器,不删除磁盘任务) */
|
|
5958
|
+
destroy() {
|
|
5959
|
+
for (const { timer } of this.tasks.values()) clearTimeout(timer);
|
|
5960
|
+
this.tasks.clear();
|
|
5961
|
+
if (this.persistTimer) {
|
|
5962
|
+
clearTimeout(this.persistTimer);
|
|
5963
|
+
this.persistTimer = null;
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
// ============================================
|
|
5967
|
+
// 内部
|
|
5968
|
+
// ============================================
|
|
5969
|
+
scheduleTimer(task) {
|
|
5970
|
+
const delay = Math.max(0, task.scheduledAt - Date.now());
|
|
5971
|
+
const armDelay = Math.min(delay, MAX_TIMEOUT_MS);
|
|
5972
|
+
const timer = setTimeout(() => {
|
|
5973
|
+
const remaining = task.scheduledAt - Date.now();
|
|
5974
|
+
if (remaining > 1e3) {
|
|
5975
|
+
this.scheduleTimer(task);
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
this.fire(task).catch((err) => {
|
|
5979
|
+
console.error("[ScheduledSessionManager] fire error:", err);
|
|
5980
|
+
});
|
|
5981
|
+
}, armDelay);
|
|
5982
|
+
this.tasks.set(task.id, { task, timer });
|
|
5983
|
+
}
|
|
5984
|
+
async fire(task) {
|
|
5985
|
+
const entry = this.tasks.get(task.id);
|
|
5986
|
+
if (!entry) return;
|
|
5987
|
+
clearTimeout(entry.timer);
|
|
5988
|
+
this.tasks.delete(task.id);
|
|
5989
|
+
this.persist();
|
|
5990
|
+
this.notifyChange();
|
|
5991
|
+
try {
|
|
5992
|
+
const result = await this.onFire(task);
|
|
5993
|
+
this.onFired?.({ id: task.id, sessionId: result.sessionId });
|
|
5994
|
+
} catch (err) {
|
|
5995
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5996
|
+
console.error(`[ScheduledSessionManager] fire failed for task ${task.id}: ${message}`);
|
|
5997
|
+
this.onFired?.({ id: task.id, error: message });
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
notifyChange() {
|
|
6001
|
+
if (!this.onChange) return;
|
|
6002
|
+
this.onChange(this.list());
|
|
6003
|
+
}
|
|
6004
|
+
/** 防抖持久化(500ms) */
|
|
6005
|
+
persist() {
|
|
6006
|
+
if (this.persistTimer) clearTimeout(this.persistTimer);
|
|
6007
|
+
this.persistTimer = setTimeout(() => {
|
|
6008
|
+
this.persistTimer = null;
|
|
6009
|
+
const tasks = [...this.tasks.values()].map((e) => e.task);
|
|
6010
|
+
(0, import_promises6.mkdir)((0, import_node_path8.join)(this.storeFile, ".."), { recursive: true }).then(() => (0, import_promises6.writeFile)(this.storeFile, JSON.stringify(tasks, null, 2), "utf8")).catch((err) => {
|
|
6011
|
+
console.error("[ScheduledSessionManager] persist error:", err);
|
|
6012
|
+
});
|
|
6013
|
+
}, 500);
|
|
6014
|
+
}
|
|
6015
|
+
};
|
|
6016
|
+
function isValidTask(value) {
|
|
6017
|
+
if (!value || typeof value !== "object") return false;
|
|
6018
|
+
const v = value;
|
|
6019
|
+
if (typeof v.id !== "string" || typeof v.scheduledAt !== "number" || typeof v.createdAt !== "number") {
|
|
6020
|
+
return false;
|
|
6021
|
+
}
|
|
6022
|
+
const payload = v.payload;
|
|
6023
|
+
if (!payload || typeof payload !== "object") return false;
|
|
6024
|
+
const p = payload;
|
|
6025
|
+
if (p.kind === "create") {
|
|
6026
|
+
return typeof p.projectPath === "string" && typeof p.message === "string";
|
|
6027
|
+
}
|
|
6028
|
+
if (p.kind === "send") {
|
|
6029
|
+
return typeof p.sessionId === "string" && typeof p.message === "string";
|
|
6030
|
+
}
|
|
6031
|
+
return false;
|
|
6032
|
+
}
|
|
6033
|
+
|
|
6034
|
+
// src/utils/cliCapabilities.ts
|
|
6035
|
+
var import_node_child_process10 = require("child_process");
|
|
5197
6036
|
var DEFAULT_CAPABILITIES = {
|
|
5198
6037
|
effortLevels: ["low", "medium", "high", "xhigh", "max"]
|
|
5199
6038
|
};
|
|
@@ -5221,7 +6060,7 @@ async function parseCliCapabilities() {
|
|
|
5221
6060
|
}
|
|
5222
6061
|
function runCli(path2, args) {
|
|
5223
6062
|
return new Promise((resolve) => {
|
|
5224
|
-
(0,
|
|
6063
|
+
(0, import_node_child_process10.execFile)(path2, args, { timeout: 5e3 }, (err, stdout) => {
|
|
5225
6064
|
if (err) {
|
|
5226
6065
|
console.warn(`[CliCapabilities] Failed to run ${path2} ${args.join(" ")}:`, err.message);
|
|
5227
6066
|
resolve(null);
|
|
@@ -5235,11 +6074,11 @@ function runCli(path2, args) {
|
|
|
5235
6074
|
// src/server.ts
|
|
5236
6075
|
var WS_PORT = 3745;
|
|
5237
6076
|
var HTTP_PORT = 3746;
|
|
5238
|
-
var
|
|
6077
|
+
var execAsync3 = (0, import_node_util3.promisify)(import_node_child_process11.exec);
|
|
5239
6078
|
async function killPortProcess(port) {
|
|
5240
6079
|
try {
|
|
5241
6080
|
if (isWindows) {
|
|
5242
|
-
const { stdout } = await
|
|
6081
|
+
const { stdout } = await execAsync3(
|
|
5243
6082
|
`netstat -ano | findstr :${port} | findstr LISTENING`
|
|
5244
6083
|
);
|
|
5245
6084
|
const pids = /* @__PURE__ */ new Set();
|
|
@@ -5249,14 +6088,14 @@ async function killPortProcess(port) {
|
|
|
5249
6088
|
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
5250
6089
|
}
|
|
5251
6090
|
for (const pid of pids) {
|
|
5252
|
-
await
|
|
6091
|
+
await execAsync3(`taskkill /PID ${pid} /F`).catch(() => {
|
|
5253
6092
|
});
|
|
5254
6093
|
}
|
|
5255
6094
|
} else {
|
|
5256
|
-
const { stdout } = await
|
|
6095
|
+
const { stdout } = await execAsync3(`lsof -ti :${port}`);
|
|
5257
6096
|
const pids = stdout.trim().split("\n").filter((p) => p && /^\d+$/.test(p));
|
|
5258
6097
|
if (pids.length > 0) {
|
|
5259
|
-
await
|
|
6098
|
+
await execAsync3(`kill -9 ${pids.join(" ")}`);
|
|
5260
6099
|
}
|
|
5261
6100
|
}
|
|
5262
6101
|
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
@@ -5277,8 +6116,8 @@ async function createWithRetry(label, port, factory) {
|
|
|
5277
6116
|
}
|
|
5278
6117
|
}
|
|
5279
6118
|
async function start(opts = {}) {
|
|
5280
|
-
const configDir = (0,
|
|
5281
|
-
const tokenFile = (0,
|
|
6119
|
+
const configDir = (0, import_node_path9.join)((0, import_node_os9.homedir)(), ".sessix");
|
|
6120
|
+
const tokenFile = (0, import_node_path9.join)(configDir, "token");
|
|
5282
6121
|
let token;
|
|
5283
6122
|
if (opts.token !== void 0) {
|
|
5284
6123
|
token = opts.token;
|
|
@@ -5288,11 +6127,11 @@ async function start(opts = {}) {
|
|
|
5288
6127
|
token = envToken;
|
|
5289
6128
|
} else {
|
|
5290
6129
|
try {
|
|
5291
|
-
token = (await (0,
|
|
6130
|
+
token = (await (0, import_promises7.readFile)(tokenFile, "utf8")).trim();
|
|
5292
6131
|
} catch {
|
|
5293
|
-
token = (0,
|
|
5294
|
-
await (0,
|
|
5295
|
-
await (0,
|
|
6132
|
+
token = (0, import_uuid9.v4)();
|
|
6133
|
+
await (0, import_promises7.mkdir)(configDir, { recursive: true });
|
|
6134
|
+
await (0, import_promises7.writeFile)(tokenFile, token, "utf8");
|
|
5296
6135
|
}
|
|
5297
6136
|
}
|
|
5298
6137
|
}
|
|
@@ -5300,6 +6139,9 @@ async function start(opts = {}) {
|
|
|
5300
6139
|
const sessionManager = new SessionManager(providerFactory);
|
|
5301
6140
|
const terminalExecutor = new TerminalExecutor();
|
|
5302
6141
|
const xcodeBuildExecutor = new XcodeBuildExecutor();
|
|
6142
|
+
const commandDiscovery = new CommandDiscovery();
|
|
6143
|
+
const gitExecutor = new GitExecutor();
|
|
6144
|
+
let scheduledManager = null;
|
|
5303
6145
|
const approvalProxy = await createWithRetry(
|
|
5304
6146
|
"ApprovalProxy",
|
|
5305
6147
|
HTTP_PORT,
|
|
@@ -5338,7 +6180,7 @@ async function start(opts = {}) {
|
|
|
5338
6180
|
let mdnsService = null;
|
|
5339
6181
|
const pairingManager = new PairingManager({
|
|
5340
6182
|
token,
|
|
5341
|
-
serverName: (0,
|
|
6183
|
+
serverName: (0, import_node_os9.hostname)(),
|
|
5342
6184
|
version: "0.2.0",
|
|
5343
6185
|
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
5344
6186
|
});
|
|
@@ -5367,6 +6209,49 @@ async function start(opts = {}) {
|
|
|
5367
6209
|
const broadcastUnreadSessions = () => {
|
|
5368
6210
|
wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
|
|
5369
6211
|
};
|
|
6212
|
+
scheduledManager = new ScheduledSessionManager({
|
|
6213
|
+
onFire: async (task) => {
|
|
6214
|
+
const p = task.payload;
|
|
6215
|
+
if (p.kind === "create") {
|
|
6216
|
+
await (0, import_promises7.mkdir)(p.projectPath, { recursive: true });
|
|
6217
|
+
const session = await sessionManager.createSession(
|
|
6218
|
+
p.projectPath,
|
|
6219
|
+
p.message,
|
|
6220
|
+
p.resumeSessionId,
|
|
6221
|
+
p.newSessionId,
|
|
6222
|
+
p.model,
|
|
6223
|
+
p.permissionMode,
|
|
6224
|
+
p.effort,
|
|
6225
|
+
void 0,
|
|
6226
|
+
p.agentType
|
|
6227
|
+
);
|
|
6228
|
+
wsBridge.broadcast({ type: "session_list", sessions: sessionManager.getActiveSessions() });
|
|
6229
|
+
return { sessionId: session.id };
|
|
6230
|
+
}
|
|
6231
|
+
const active = sessionManager.getActiveSessions().find((s) => s.id === p.sessionId);
|
|
6232
|
+
if (active) {
|
|
6233
|
+
await sessionManager.sendMessage(p.sessionId, p.message, p.permissionMode);
|
|
6234
|
+
} else {
|
|
6235
|
+
await sessionManager.createSession(
|
|
6236
|
+
p.projectPath,
|
|
6237
|
+
p.message,
|
|
6238
|
+
p.sessionId,
|
|
6239
|
+
void 0,
|
|
6240
|
+
void 0,
|
|
6241
|
+
p.permissionMode
|
|
6242
|
+
);
|
|
6243
|
+
}
|
|
6244
|
+
wsBridge.broadcast({ type: "session_list", sessions: sessionManager.getActiveSessions() });
|
|
6245
|
+
return { sessionId: p.sessionId };
|
|
6246
|
+
},
|
|
6247
|
+
onChange: (tasks) => {
|
|
6248
|
+
wsBridge.broadcast({ type: "scheduled_session_list", tasks });
|
|
6249
|
+
},
|
|
6250
|
+
onFired: (event) => {
|
|
6251
|
+
wsBridge.broadcast({ type: "scheduled_session_fired", ...event });
|
|
6252
|
+
}
|
|
6253
|
+
});
|
|
6254
|
+
await scheduledManager.load();
|
|
5370
6255
|
wsBridge.onConnection(async (ws) => {
|
|
5371
6256
|
const result = await getProjects();
|
|
5372
6257
|
if (result.ok) {
|
|
@@ -5388,12 +6273,15 @@ async function start(opts = {}) {
|
|
|
5388
6273
|
if (cliCapabilities) {
|
|
5389
6274
|
wsBridge.send(ws, { type: "cli_info", effortLevels: cliCapabilities.effortLevels, version: cliCapabilities.version });
|
|
5390
6275
|
}
|
|
6276
|
+
if (scheduledManager) {
|
|
6277
|
+
wsBridge.send(ws, { type: "scheduled_session_list", tasks: scheduledManager.list() });
|
|
6278
|
+
}
|
|
5391
6279
|
});
|
|
5392
6280
|
wsBridge.onClientEvent(async (event, ws) => {
|
|
5393
6281
|
try {
|
|
5394
6282
|
switch (event.type) {
|
|
5395
6283
|
case "create_session": {
|
|
5396
|
-
await (0,
|
|
6284
|
+
await (0, import_promises7.mkdir)(event.projectPath, { recursive: true });
|
|
5397
6285
|
const resumeId = event.resumeSessionId ?? event.newSessionId;
|
|
5398
6286
|
if (resumeId) sessionFileWatcher.unwatch(resumeId);
|
|
5399
6287
|
await sessionManager.createSession(
|
|
@@ -5581,7 +6469,7 @@ async function start(opts = {}) {
|
|
|
5581
6469
|
if (!isStreaming) {
|
|
5582
6470
|
const filePath = getSessionFilePath(event.projectPath, event.sessionId);
|
|
5583
6471
|
try {
|
|
5584
|
-
const fileStat = await (0,
|
|
6472
|
+
const fileStat = await (0, import_promises8.stat)(filePath);
|
|
5585
6473
|
sessionFileWatcher.watch(event.sessionId, filePath, fileStat.size);
|
|
5586
6474
|
} catch {
|
|
5587
6475
|
}
|
|
@@ -5744,6 +6632,66 @@ async function start(opts = {}) {
|
|
|
5744
6632
|
xcodeBuildExecutor.killInstall(event.installId);
|
|
5745
6633
|
break;
|
|
5746
6634
|
}
|
|
6635
|
+
case "schedule_session": {
|
|
6636
|
+
if (!scheduledManager) break;
|
|
6637
|
+
const scheduledAt = Number(event.scheduledAt);
|
|
6638
|
+
if (!Number.isFinite(scheduledAt)) {
|
|
6639
|
+
wsBridge.send(ws, { type: "error", code: "INVALID_MESSAGE", message: "Invalid scheduledAt" });
|
|
6640
|
+
break;
|
|
6641
|
+
}
|
|
6642
|
+
scheduledManager.schedule(scheduledAt, event.payload);
|
|
6643
|
+
break;
|
|
6644
|
+
}
|
|
6645
|
+
case "cancel_scheduled_session": {
|
|
6646
|
+
scheduledManager?.cancel(event.id);
|
|
6647
|
+
break;
|
|
6648
|
+
}
|
|
6649
|
+
case "list_scheduled_sessions": {
|
|
6650
|
+
if (scheduledManager) {
|
|
6651
|
+
wsBridge.send(ws, { type: "scheduled_session_list", tasks: scheduledManager.list() });
|
|
6652
|
+
}
|
|
6653
|
+
break;
|
|
6654
|
+
}
|
|
6655
|
+
case "git_status": {
|
|
6656
|
+
const status = await gitExecutor.detectStatus(event.projectPath);
|
|
6657
|
+
wsBridge.send(ws, { type: "git_status_result", sessionId: event.sessionId, status });
|
|
6658
|
+
break;
|
|
6659
|
+
}
|
|
6660
|
+
case "git_commit": {
|
|
6661
|
+
await gitExecutor.commit(
|
|
6662
|
+
event.sessionId,
|
|
6663
|
+
event.projectPath,
|
|
6664
|
+
event.message,
|
|
6665
|
+
event.files,
|
|
6666
|
+
event.alsoPush
|
|
6667
|
+
);
|
|
6668
|
+
break;
|
|
6669
|
+
}
|
|
6670
|
+
case "git_push": {
|
|
6671
|
+
await gitExecutor.push(event.sessionId, event.projectPath);
|
|
6672
|
+
break;
|
|
6673
|
+
}
|
|
6674
|
+
case "list_project_commands": {
|
|
6675
|
+
try {
|
|
6676
|
+
const commands = await commandDiscovery.scan(event.projectPath, event.refresh ?? false);
|
|
6677
|
+
wsBridge.send(ws, {
|
|
6678
|
+
type: "commands_result",
|
|
6679
|
+
sessionId: event.sessionId,
|
|
6680
|
+
projectPath: event.projectPath,
|
|
6681
|
+
commands
|
|
6682
|
+
});
|
|
6683
|
+
} catch (err) {
|
|
6684
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6685
|
+
wsBridge.send(ws, {
|
|
6686
|
+
type: "commands_result",
|
|
6687
|
+
sessionId: event.sessionId,
|
|
6688
|
+
projectPath: event.projectPath,
|
|
6689
|
+
commands: [],
|
|
6690
|
+
error: message
|
|
6691
|
+
});
|
|
6692
|
+
}
|
|
6693
|
+
break;
|
|
6694
|
+
}
|
|
5747
6695
|
default: {
|
|
5748
6696
|
wsBridge.send(ws, {
|
|
5749
6697
|
type: "error",
|
|
@@ -5782,6 +6730,9 @@ async function start(opts = {}) {
|
|
|
5782
6730
|
xcodeBuildExecutor.onEvent((event) => {
|
|
5783
6731
|
wsBridge.broadcast(event);
|
|
5784
6732
|
});
|
|
6733
|
+
gitExecutor.onEvent((event) => {
|
|
6734
|
+
wsBridge.broadcast(event);
|
|
6735
|
+
});
|
|
5785
6736
|
wsBridge.onDisconnect(() => {
|
|
5786
6737
|
if (wsBridge.getConnectionCount() === 0 && approvalProxy.getPendingCount() > 0) {
|
|
5787
6738
|
approvalProxy.approveAll(t("server.phoneDisconnected"));
|
|
@@ -5871,6 +6822,42 @@ async function start(opts = {}) {
|
|
|
5871
6822
|
console.error(`[Server] ${t("server.hookInstallFailed")}`, err);
|
|
5872
6823
|
console.log(`[Server] ${t("server.hookContinue")}`);
|
|
5873
6824
|
}
|
|
6825
|
+
const idleTimeoutMs = Number(process.env.SESSIX_IDLE_TIMEOUT_MS ?? 30 * 60 * 1e3);
|
|
6826
|
+
const idleSweepIntervalMs = Number(process.env.SESSIX_IDLE_SWEEP_INTERVAL_MS ?? 5 * 60 * 1e3);
|
|
6827
|
+
const maxActiveProcesses = Number(process.env.SESSIX_MAX_ACTIVE_PROCESSES ?? 15);
|
|
6828
|
+
let idleSweepTimer = null;
|
|
6829
|
+
if (idleSweepIntervalMs > 0 && (idleTimeoutMs > 0 || maxActiveProcesses > 0)) {
|
|
6830
|
+
idleSweepTimer = setInterval(async () => {
|
|
6831
|
+
try {
|
|
6832
|
+
let totalSwept = 0;
|
|
6833
|
+
const broadcastShrink = (sessionId) => {
|
|
6834
|
+
sessionManager.shrinkSessionBuffer(sessionId, 100);
|
|
6835
|
+
};
|
|
6836
|
+
for (const agentType of ["claude-code", "codex"]) {
|
|
6837
|
+
const provider = providerFactory.getProvider(agentType);
|
|
6838
|
+
if (idleTimeoutMs > 0 && typeof provider.sweepIdleProcesses === "function") {
|
|
6839
|
+
const swept = await provider.sweepIdleProcesses(idleTimeoutMs);
|
|
6840
|
+
swept.forEach(broadcastShrink);
|
|
6841
|
+
totalSwept += swept.length;
|
|
6842
|
+
}
|
|
6843
|
+
if (maxActiveProcesses > 0 && typeof provider.sweepLruProcesses === "function") {
|
|
6844
|
+
const swept = await provider.sweepLruProcesses(maxActiveProcesses);
|
|
6845
|
+
swept.forEach(broadcastShrink);
|
|
6846
|
+
totalSwept += swept.length;
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
if (totalSwept > 0) {
|
|
6850
|
+
console.log(`[Server] Idle GC: swept ${totalSwept} idle session(s)`);
|
|
6851
|
+
wsBridge.broadcast({
|
|
6852
|
+
type: "session_list",
|
|
6853
|
+
sessions: sessionManager.getActiveSessions()
|
|
6854
|
+
});
|
|
6855
|
+
}
|
|
6856
|
+
} catch (err) {
|
|
6857
|
+
console.error("[Server] Idle GC failed:", err);
|
|
6858
|
+
}
|
|
6859
|
+
}, idleSweepIntervalMs);
|
|
6860
|
+
}
|
|
5874
6861
|
const stop = async () => {
|
|
5875
6862
|
console.log(`[Server] ${t("server.shuttingDown")}`);
|
|
5876
6863
|
const errors = [];
|
|
@@ -5882,6 +6869,7 @@ async function start(opts = {}) {
|
|
|
5882
6869
|
errors.push(err);
|
|
5883
6870
|
}
|
|
5884
6871
|
};
|
|
6872
|
+
if (idleSweepTimer) clearInterval(idleSweepTimer);
|
|
5885
6873
|
await attempt(() => authManager.destroy(), "AuthManager");
|
|
5886
6874
|
await attempt(() => stopMdns(), "mDNS");
|
|
5887
6875
|
await attempt(() => pairingManager.destroy(), "PairingManager");
|
|
@@ -5892,6 +6880,7 @@ async function start(opts = {}) {
|
|
|
5892
6880
|
await attempt(() => xcodeBuildExecutor.destroy(), "XcodeBuildExecutor");
|
|
5893
6881
|
await attempt(() => notificationService.destroy(), "NotificationService");
|
|
5894
6882
|
await attempt(() => sessionFileWatcher.destroy(), "SessionFileWatcher");
|
|
6883
|
+
await attempt(() => scheduledManager?.destroy(), "ScheduledSessionManager");
|
|
5895
6884
|
if (errors.length > 0) {
|
|
5896
6885
|
console.error(`[Server] ${t("server.shutdownWithErrors", { count: errors.length })}`);
|
|
5897
6886
|
throw errors[0];
|
|
@@ -5918,9 +6907,9 @@ async function start(opts = {}) {
|
|
|
5918
6907
|
openPairing: (duration) => pairingManager.open(duration),
|
|
5919
6908
|
closePairing: () => pairingManager.close(),
|
|
5920
6909
|
regenerateToken: async () => {
|
|
5921
|
-
const newToken = (0,
|
|
5922
|
-
await (0,
|
|
5923
|
-
await (0,
|
|
6910
|
+
const newToken = (0, import_uuid9.v4)();
|
|
6911
|
+
await (0, import_promises7.mkdir)(configDir, { recursive: true });
|
|
6912
|
+
await (0, import_promises7.writeFile)(tokenFile, newToken, "utf8");
|
|
5924
6913
|
instance.token = newToken;
|
|
5925
6914
|
wsBridge.updateToken(newToken);
|
|
5926
6915
|
approvalProxy.updateToken(newToken);
|
|
@@ -5937,7 +6926,7 @@ async function start(opts = {}) {
|
|
|
5937
6926
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
5938
6927
|
function getPackageVersion() {
|
|
5939
6928
|
try {
|
|
5940
|
-
const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0,
|
|
6929
|
+
const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path10.join)(__dirname, "..", "package.json"), "utf8"));
|
|
5941
6930
|
return pkg.version ?? "0.0.0";
|
|
5942
6931
|
} catch {
|
|
5943
6932
|
return "0.0.0";
|
|
@@ -5975,7 +6964,7 @@ async function autoUpdateIfNeeded() {
|
|
|
5975
6964
|
console.log(` \u{1F4E6} ${t("startup.autoUpdating", { current: PKG_VERSION, latest })}`);
|
|
5976
6965
|
console.log();
|
|
5977
6966
|
try {
|
|
5978
|
-
(0,
|
|
6967
|
+
(0, import_node_child_process12.execFileSync)("npx", [`sessix-server@${latest}`], {
|
|
5979
6968
|
stdio: "inherit",
|
|
5980
6969
|
env: { ...process.env, __SESSIX_UPDATED: "1" }
|
|
5981
6970
|
});
|
|
@@ -6081,7 +7070,7 @@ ${t("startup.pairingReopened")}`);
|
|
|
6081
7070
|
}
|
|
6082
7071
|
}
|
|
6083
7072
|
function getLocalIp() {
|
|
6084
|
-
const interfaces = (0,
|
|
7073
|
+
const interfaces = (0, import_node_os10.networkInterfaces)();
|
|
6085
7074
|
for (const iface of Object.values(interfaces)) {
|
|
6086
7075
|
for (const addr of iface ?? []) {
|
|
6087
7076
|
if (addr.family === "IPv4" && !addr.internal) {
|