@i14ks/ccv 0.12.3 → 0.14.0
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/bundle/ccv.mjs +316 -78
- package/package.json +1 -1
- package/web/assets/{codemirror-D11GWMsB.js → codemirror-DfnUEAQ1.js} +1 -1
- package/web/assets/{index-BIhkivIg.js → index-1NZdgXKt.js} +1 -1
- package/web/assets/{index-Iwl1w0_V.js → index-6WrTm4Cl.js} +1 -1
- package/web/assets/{index-CZF-u-hv.js → index-Aa3M3Mcp.js} +1 -1
- package/web/assets/{index-Dn5EQ5YI.js → index-B4QS6XYB.js} +1 -1
- package/web/assets/{index-DO1FY4r1.js → index-BBJ7uyxo.js} +1 -1
- package/web/assets/{index-Bg0VD211.js → index-BFyhFly9.js} +1 -1
- package/web/assets/{index-J2jvoYvL.js → index-BSHub4Y3.js} +1 -1
- package/web/assets/{index-D57ZXpBD.js → index-BUVVNNln.js} +1 -1
- package/web/assets/{index-BDuAzkOQ.js → index-BrhFysJH.js} +1 -1
- package/web/assets/{index-09MC_4AF.js → index-C33PhVuR.js} +1 -1
- package/web/assets/{index-ZstREAcb.js → index-CE-6gz9W.js} +1 -1
- package/web/assets/{index-BSEf83Uc.js → index-CReKaug6.js} +1 -1
- package/web/assets/{index-4S0cl_O2.js → index-C_0hLH-6.js} +1 -1
- package/web/assets/{index-Bw0U50QY.js → index-Cu9yVjDd.js} +1 -1
- package/web/assets/{index-Czu0PbLS.js → index-DLoVg97c.js} +1 -1
- package/web/assets/{index-CmCj0IE6.js → index-DNvUBsh8.js} +1 -1
- package/web/assets/{index-DdCJlB8h.js → index-DdCEnSCp.js} +1 -1
- package/web/assets/{index-DABElhgt.js → index-DdWyOJPD.js} +1 -1
- package/web/assets/{index-xSo7_4Td.js → index-UUIF3jgc.js} +1 -1
- package/web/assets/index-i3oOZBX4.js +133 -0
- package/web/assets/index-ihyDKGfF.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-9Toz507b.js +0 -132
- package/web/assets/index-D5y0Nk74.css +0 -1
package/bundle/ccv.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// dist/ccv.js
|
|
4
4
|
import { spawn as spawn2 } from "node:child_process";
|
|
5
|
-
import { readFile as
|
|
6
|
-
import
|
|
5
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
6
|
+
import path20 from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
|
|
@@ -145,9 +145,9 @@ function envDefaults() {
|
|
|
145
145
|
|
|
146
146
|
// ../host/dist/server.js
|
|
147
147
|
import { createServer } from "node:http";
|
|
148
|
-
import { createReadStream, existsSync as
|
|
149
|
-
import
|
|
150
|
-
import { homedir as
|
|
148
|
+
import { createReadStream, existsSync as existsSync4, statSync as statSync3 } from "node:fs";
|
|
149
|
+
import path19 from "node:path";
|
|
150
|
+
import { homedir as homedir6 } from "node:os";
|
|
151
151
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
152
152
|
import { WebSocketServer } from "ws";
|
|
153
153
|
import { renameSession as renameSession2 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -164,6 +164,8 @@ function workspaceIdFromPath(absolutePath) {
|
|
|
164
164
|
// ../host/dist/session.js
|
|
165
165
|
import { EventEmitter } from "node:events";
|
|
166
166
|
import { randomUUID } from "node:crypto";
|
|
167
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
168
|
+
import { homedir as homedir3 } from "node:os";
|
|
167
169
|
import path5 from "node:path";
|
|
168
170
|
import { query, renameSession } from "@anthropic-ai/claude-agent-sdk";
|
|
169
171
|
|
|
@@ -1109,6 +1111,7 @@ function previewAnnotations(questions, answers) {
|
|
|
1109
1111
|
return annotations;
|
|
1110
1112
|
}
|
|
1111
1113
|
var USAGE_MIN_INTERVAL_MS = 2e4;
|
|
1114
|
+
var USAGE_TURN_INTERVAL_MS = 12e4;
|
|
1112
1115
|
var CONTEXT_MIN_INTERVAL_MS = 5e3;
|
|
1113
1116
|
var ClaudeSession = class extends EventEmitter {
|
|
1114
1117
|
id;
|
|
@@ -1864,6 +1867,7 @@ var ClaudeSession = class extends EventEmitter {
|
|
|
1864
1867
|
const raw = await q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET();
|
|
1865
1868
|
this.#usage = toUsageState(raw);
|
|
1866
1869
|
this.emit("usage", this.#usage);
|
|
1870
|
+
writeFinisherSnapshot(this.#usage, this.#claudeSessionId, this.cwd);
|
|
1867
1871
|
} catch {
|
|
1868
1872
|
} finally {
|
|
1869
1873
|
this.#usageAt = Date.now();
|
|
@@ -2172,6 +2176,8 @@ var ClaudeSession = class extends EventEmitter {
|
|
|
2172
2176
|
break;
|
|
2173
2177
|
case "user":
|
|
2174
2178
|
this.#handleToolResults(message);
|
|
2179
|
+
if (Date.now() - this.#usageAt >= USAGE_TURN_INTERVAL_MS)
|
|
2180
|
+
void this.refreshUsage();
|
|
2175
2181
|
break;
|
|
2176
2182
|
case "stream_event":
|
|
2177
2183
|
this.#handleStreamEvent(message);
|
|
@@ -2421,6 +2427,27 @@ function toUsageState(raw) {
|
|
|
2421
2427
|
fetchedAt: Date.now()
|
|
2422
2428
|
};
|
|
2423
2429
|
}
|
|
2430
|
+
function writeFinisherSnapshot(usage, sessionId, cwd) {
|
|
2431
|
+
if (!usage.available)
|
|
2432
|
+
return;
|
|
2433
|
+
const dir = path5.join(homedir3(), ".claude", "finisher");
|
|
2434
|
+
if (!existsSync(dir))
|
|
2435
|
+
return;
|
|
2436
|
+
const snapshot = {
|
|
2437
|
+
updated_at: Date.now(),
|
|
2438
|
+
session_id: sessionId,
|
|
2439
|
+
cwd,
|
|
2440
|
+
rate_limits: {
|
|
2441
|
+
five_hour: usage.fiveHour?.utilization ?? null,
|
|
2442
|
+
seven_day: usage.sevenDay?.utilization ?? null,
|
|
2443
|
+
spend_limit: null
|
|
2444
|
+
}
|
|
2445
|
+
};
|
|
2446
|
+
try {
|
|
2447
|
+
writeFileSync(path5.join(dir, "usage-cache.json"), JSON.stringify(snapshot, null, 2));
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2424
2451
|
function toUsageWindow(raw) {
|
|
2425
2452
|
if (!raw || typeof raw.utilization !== "number")
|
|
2426
2453
|
return null;
|
|
@@ -3021,9 +3048,9 @@ function parsePatch(raw) {
|
|
|
3021
3048
|
body = "";
|
|
3022
3049
|
}
|
|
3023
3050
|
total += body.length;
|
|
3024
|
-
const
|
|
3051
|
+
const path21 = status === "deleted" ? oldPath : newPath || oldPath;
|
|
3025
3052
|
files.push({
|
|
3026
|
-
path:
|
|
3053
|
+
path: path21,
|
|
3027
3054
|
oldPath: status === "renamed" ? oldPath : "",
|
|
3028
3055
|
status,
|
|
3029
3056
|
added: count(body, "+"),
|
|
@@ -3035,10 +3062,10 @@ function parsePatch(raw) {
|
|
|
3035
3062
|
return { files, truncated };
|
|
3036
3063
|
}
|
|
3037
3064
|
function stripPrefix(value) {
|
|
3038
|
-
const
|
|
3039
|
-
if (
|
|
3065
|
+
const path21 = unquote(value.trim());
|
|
3066
|
+
if (path21 === "/dev/null")
|
|
3040
3067
|
return "";
|
|
3041
|
-
return
|
|
3068
|
+
return path21.replace(/^[ab]\//, "");
|
|
3042
3069
|
}
|
|
3043
3070
|
function count(body, mark) {
|
|
3044
3071
|
let n = 0;
|
|
@@ -3280,7 +3307,7 @@ function describe3(error) {
|
|
|
3280
3307
|
}
|
|
3281
3308
|
|
|
3282
3309
|
// ../host/dist/finder.js
|
|
3283
|
-
import { existsSync, statSync } from "node:fs";
|
|
3310
|
+
import { existsSync as existsSync2, statSync } from "node:fs";
|
|
3284
3311
|
import { readdir as readdir2 } from "node:fs/promises";
|
|
3285
3312
|
import os2 from "node:os";
|
|
3286
3313
|
import path8 from "node:path";
|
|
@@ -3468,7 +3495,7 @@ var ProjectFinder = class {
|
|
|
3468
3495
|
}
|
|
3469
3496
|
};
|
|
3470
3497
|
function usable(dir) {
|
|
3471
|
-
return !contains(os2.tmpdir(), dir) &&
|
|
3498
|
+
return !contains(os2.tmpdir(), dir) && existsSync2(dir);
|
|
3472
3499
|
}
|
|
3473
3500
|
function tokens(query2) {
|
|
3474
3501
|
return query2.toLowerCase().split(/[^a-z0-9а-яё]+/i).filter(Boolean);
|
|
@@ -3565,7 +3592,7 @@ function fromText(text) {
|
|
|
3565
3592
|
}
|
|
3566
3593
|
|
|
3567
3594
|
// ../host/dist/workspaces.js
|
|
3568
|
-
import { existsSync as
|
|
3595
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
|
|
3569
3596
|
import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
|
|
3570
3597
|
import path9 from "node:path";
|
|
3571
3598
|
import { listSessions as listSessions2 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -3589,7 +3616,7 @@ var WorkspaceRegistry = class {
|
|
|
3589
3616
|
if (isEntry(item))
|
|
3590
3617
|
return item;
|
|
3591
3618
|
return null;
|
|
3592
|
-
}).filter((e) => e !== null &&
|
|
3619
|
+
}).filter((e) => e !== null && existsSync3(e.path));
|
|
3593
3620
|
} catch {
|
|
3594
3621
|
this.#entries = [];
|
|
3595
3622
|
}
|
|
@@ -3612,7 +3639,7 @@ var WorkspaceRegistry = class {
|
|
|
3612
3639
|
}
|
|
3613
3640
|
async open(rawPath) {
|
|
3614
3641
|
const absolute = path9.resolve(rawPath);
|
|
3615
|
-
if (!
|
|
3642
|
+
if (!existsSync3(absolute)) {
|
|
3616
3643
|
throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${absolute}`);
|
|
3617
3644
|
}
|
|
3618
3645
|
const existing = this.#entries.find((e) => e.path === absolute);
|
|
@@ -3679,14 +3706,14 @@ var WorkspaceRegistry = class {
|
|
|
3679
3706
|
const name = nameRaw.trim();
|
|
3680
3707
|
requireName(name);
|
|
3681
3708
|
const parent = path9.resolve(parentRaw.trim());
|
|
3682
|
-
if (!
|
|
3709
|
+
if (!existsSync3(parent)) {
|
|
3683
3710
|
throw new Error(`\u041A\u0430\u0442\u0430\u043B\u043E\u0433 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: ${parent}`);
|
|
3684
3711
|
}
|
|
3685
3712
|
if (!statSync2(parent).isDirectory()) {
|
|
3686
3713
|
throw new Error(`\u042D\u0442\u043E \u043D\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433: ${parent}`);
|
|
3687
3714
|
}
|
|
3688
3715
|
const target = path9.join(parent, name);
|
|
3689
|
-
if (
|
|
3716
|
+
if (existsSync3(target)) {
|
|
3690
3717
|
throw new Error(`\u0423\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442: ${target} \u2014 \u043E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0435\u0433\u043E \u043A\u0430\u043A \u043F\u0440\u043E\u0435\u043A\u0442`);
|
|
3691
3718
|
}
|
|
3692
3719
|
await mkdir3(target);
|
|
@@ -3768,7 +3795,7 @@ function toInfo(entry) {
|
|
|
3768
3795
|
id: workspaceIdFromPath(absolute),
|
|
3769
3796
|
path: absolute,
|
|
3770
3797
|
name: path9.basename(absolute) || absolute,
|
|
3771
|
-
hasClaudeMd:
|
|
3798
|
+
hasClaudeMd: existsSync3(path9.join(absolute, "CLAUDE.md")),
|
|
3772
3799
|
...archivedAt ? { archivedAt } : {}
|
|
3773
3800
|
};
|
|
3774
3801
|
}
|
|
@@ -3984,9 +4011,79 @@ function isRecord2(value) {
|
|
|
3984
4011
|
return typeof value === "object" && value !== null && typeof value.workspaceId === "string" && typeof value.at === "number";
|
|
3985
4012
|
}
|
|
3986
4013
|
|
|
3987
|
-
// ../host/dist/
|
|
4014
|
+
// ../host/dist/order.js
|
|
3988
4015
|
import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
3989
4016
|
import path12 from "node:path";
|
|
4017
|
+
var ListOrderStore = class {
|
|
4018
|
+
#order = emptyOrder();
|
|
4019
|
+
#storePath;
|
|
4020
|
+
constructor(dataDir) {
|
|
4021
|
+
this.#storePath = path12.join(dataDir, "list-order.json");
|
|
4022
|
+
}
|
|
4023
|
+
async load() {
|
|
4024
|
+
try {
|
|
4025
|
+
const raw = await readFile9(this.#storePath, "utf8");
|
|
4026
|
+
this.#order = sanitize(JSON.parse(raw));
|
|
4027
|
+
} catch {
|
|
4028
|
+
this.#order = emptyOrder();
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
get current() {
|
|
4032
|
+
return {
|
|
4033
|
+
workspaces: [...this.#order.workspaces],
|
|
4034
|
+
sessions: Object.fromEntries(Object.entries(this.#order.sessions).map(([k, v]) => [k, [...v]])),
|
|
4035
|
+
pinned: [...this.#order.pinned],
|
|
4036
|
+
live: [...this.#order.live]
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
/**
|
|
4040
|
+
* Записать порядок одного списка. `ids === null` — сбросить его: список
|
|
4041
|
+
* вернётся к обычному порядку.
|
|
4042
|
+
*/
|
|
4043
|
+
async set(scope, ids, workspaceId) {
|
|
4044
|
+
const clean = ids === null ? null : dedupe(ids);
|
|
4045
|
+
if (scope === "sessions") {
|
|
4046
|
+
if (!workspaceId)
|
|
4047
|
+
throw new Error("\u0414\u043B\u044F \u043F\u043E\u0440\u044F\u0434\u043A\u0430 \u0441\u0435\u0441\u0441\u0438\u0439 \u043D\u0443\u0436\u0435\u043D workspaceId");
|
|
4048
|
+
if (clean === null || clean.length === 0)
|
|
4049
|
+
delete this.#order.sessions[workspaceId];
|
|
4050
|
+
else
|
|
4051
|
+
this.#order.sessions[workspaceId] = clean;
|
|
4052
|
+
} else {
|
|
4053
|
+
this.#order[scope] = clean ?? [];
|
|
4054
|
+
}
|
|
4055
|
+
await mkdir6(path12.dirname(this.#storePath), { recursive: true });
|
|
4056
|
+
await writeFile7(this.#storePath, JSON.stringify(this.#order, null, 2), "utf8");
|
|
4057
|
+
}
|
|
4058
|
+
};
|
|
4059
|
+
function emptyOrder() {
|
|
4060
|
+
return { workspaces: [], sessions: {}, pinned: [], live: [] };
|
|
4061
|
+
}
|
|
4062
|
+
function dedupe(ids) {
|
|
4063
|
+
return [...new Set(ids.filter((id) => typeof id === "string" && id !== ""))];
|
|
4064
|
+
}
|
|
4065
|
+
function sanitize(value) {
|
|
4066
|
+
const result = emptyOrder();
|
|
4067
|
+
if (typeof value !== "object" || value === null)
|
|
4068
|
+
return result;
|
|
4069
|
+
const record = value;
|
|
4070
|
+
const list = (v) => Array.isArray(v) ? dedupe(v) : [];
|
|
4071
|
+
result.workspaces = list(record.workspaces);
|
|
4072
|
+
result.pinned = list(record.pinned);
|
|
4073
|
+
result.live = list(record.live);
|
|
4074
|
+
if (typeof record.sessions === "object" && record.sessions !== null) {
|
|
4075
|
+
for (const [workspaceId, ids] of Object.entries(record.sessions)) {
|
|
4076
|
+
const clean = list(ids);
|
|
4077
|
+
if (clean.length > 0)
|
|
4078
|
+
result.sessions[workspaceId] = clean;
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
return result;
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
// ../host/dist/folders.js
|
|
4085
|
+
import { mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
4086
|
+
import path13 from "node:path";
|
|
3990
4087
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3991
4088
|
var Folders = class {
|
|
3992
4089
|
#workspaceFolders = /* @__PURE__ */ new Map();
|
|
@@ -3997,11 +4094,11 @@ var Folders = class {
|
|
|
3997
4094
|
#pinnedAssignments = /* @__PURE__ */ new Map();
|
|
3998
4095
|
#storePath;
|
|
3999
4096
|
constructor(dataDir) {
|
|
4000
|
-
this.#storePath =
|
|
4097
|
+
this.#storePath = path13.join(dataDir, "folders.json");
|
|
4001
4098
|
}
|
|
4002
4099
|
async load() {
|
|
4003
4100
|
try {
|
|
4004
|
-
const raw = await
|
|
4101
|
+
const raw = await readFile10(this.#storePath, "utf8");
|
|
4005
4102
|
const parsed = JSON.parse(raw);
|
|
4006
4103
|
this.#workspaceFolders = new Map(Object.entries(parsed.workspaceFolders ?? {}).filter(([, r]) => isWorkspaceFolder(r)));
|
|
4007
4104
|
this.#sessionFolders = new Map(Object.entries(parsed.sessionFolders ?? {}).filter(([, r]) => isSessionFolder(r)));
|
|
@@ -4184,8 +4281,8 @@ var Folders = class {
|
|
|
4184
4281
|
sessionAssignments: Object.fromEntries(this.#sessionAssignments),
|
|
4185
4282
|
pinnedAssignments: Object.fromEntries(this.#pinnedAssignments)
|
|
4186
4283
|
};
|
|
4187
|
-
await
|
|
4188
|
-
await
|
|
4284
|
+
await mkdir7(path13.dirname(this.#storePath), { recursive: true });
|
|
4285
|
+
await writeFile8(this.#storePath, JSON.stringify(file, null, 2), "utf8");
|
|
4189
4286
|
}
|
|
4190
4287
|
};
|
|
4191
4288
|
function isWorkspaceFolder(value) {
|
|
@@ -4199,11 +4296,13 @@ function isPinnedFolder(value) {
|
|
|
4199
4296
|
}
|
|
4200
4297
|
|
|
4201
4298
|
// ../host/dist/app-settings.js
|
|
4202
|
-
import { mkdir as
|
|
4203
|
-
import { homedir as
|
|
4204
|
-
import
|
|
4299
|
+
import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
4300
|
+
import { homedir as homedir4 } from "node:os";
|
|
4301
|
+
import path14 from "node:path";
|
|
4205
4302
|
var DEFAULT_AUTO_ARCHIVE_AFTER_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
4206
4303
|
var MIN_AUTO_ARCHIVE_AFTER_MS = 60 * 60 * 1e3;
|
|
4304
|
+
var DEFAULT_AUTO_STOP_AFTER_MS = 10 * 60 * 1e3;
|
|
4305
|
+
var MIN_AUTO_STOP_AFTER_MS = 60 * 1e3;
|
|
4207
4306
|
var MIN_AUTO_SAVE_AFTER_PAUSE_MS = 500;
|
|
4208
4307
|
var FOLDER_ACCESS = ["all", "projects", "project"];
|
|
4209
4308
|
var PERMISSION_MODES = [
|
|
@@ -4216,6 +4315,7 @@ var PERMISSION_MODES = [
|
|
|
4216
4315
|
];
|
|
4217
4316
|
var DEFAULT_APP_SETTINGS = {
|
|
4218
4317
|
autoArchiveAfterMs: DEFAULT_AUTO_ARCHIVE_AFTER_MS,
|
|
4318
|
+
autoStopAfterMs: DEFAULT_AUTO_STOP_AFTER_MS,
|
|
4219
4319
|
// Строгий режим по умолчанию: молча писать в чужой проект приложение начнёт
|
|
4220
4320
|
// только после того, как об этом попросили явно.
|
|
4221
4321
|
folderAccess: "project",
|
|
@@ -4230,18 +4330,20 @@ var DEFAULT_APP_SETTINGS = {
|
|
|
4230
4330
|
// Терминал по умолчанию закрыт — его включают, а не выключают: приложение,
|
|
4231
4331
|
// открытое в браузере, не обязано с первого запуска уметь запускать на этой
|
|
4232
4332
|
// машине что угодно.
|
|
4233
|
-
terminalEnabled: false
|
|
4333
|
+
terminalEnabled: false,
|
|
4334
|
+
// Выключено по умолчанию: списки идут в том порядке, в каком их расставили.
|
|
4335
|
+
liveSessionsFirst: false
|
|
4234
4336
|
};
|
|
4235
4337
|
var AppSettingsStore = class {
|
|
4236
4338
|
#settings = { ...DEFAULT_APP_SETTINGS };
|
|
4237
4339
|
#storePath;
|
|
4238
4340
|
constructor(dataDir) {
|
|
4239
|
-
this.#storePath =
|
|
4341
|
+
this.#storePath = path14.join(dataDir, "app-settings.json");
|
|
4240
4342
|
}
|
|
4241
4343
|
async load() {
|
|
4242
4344
|
try {
|
|
4243
|
-
const raw = await
|
|
4244
|
-
this.#settings =
|
|
4345
|
+
const raw = await readFile11(this.#storePath, "utf8");
|
|
4346
|
+
this.#settings = sanitize2(JSON.parse(raw), DEFAULT_APP_SETTINGS);
|
|
4245
4347
|
} catch {
|
|
4246
4348
|
this.#settings = { ...DEFAULT_APP_SETTINGS };
|
|
4247
4349
|
}
|
|
@@ -4258,14 +4360,14 @@ var AppSettingsStore = class {
|
|
|
4258
4360
|
* всё равно получит в ответ то, что реально записано, и покажет именно это.
|
|
4259
4361
|
*/
|
|
4260
4362
|
async update(patch) {
|
|
4261
|
-
const next =
|
|
4363
|
+
const next = sanitize2(patch, this.#settings);
|
|
4262
4364
|
this.#settings = next;
|
|
4263
|
-
await
|
|
4264
|
-
await
|
|
4365
|
+
await mkdir8(path14.dirname(this.#storePath), { recursive: true });
|
|
4366
|
+
await writeFile9(this.#storePath, JSON.stringify(next, null, 2), "utf8");
|
|
4265
4367
|
return this.current;
|
|
4266
4368
|
}
|
|
4267
4369
|
};
|
|
4268
|
-
function
|
|
4370
|
+
function sanitize2(value, fallback) {
|
|
4269
4371
|
const result = { ...fallback };
|
|
4270
4372
|
if (typeof value !== "object" || value === null)
|
|
4271
4373
|
return result;
|
|
@@ -4274,6 +4376,10 @@ function sanitize(value, fallback) {
|
|
|
4274
4376
|
if (typeof after === "number" && Number.isFinite(after)) {
|
|
4275
4377
|
result.autoArchiveAfterMs = after <= 0 ? 0 : Math.max(Math.round(after), MIN_AUTO_ARCHIVE_AFTER_MS);
|
|
4276
4378
|
}
|
|
4379
|
+
const stopAfter = record.autoStopAfterMs;
|
|
4380
|
+
if (typeof stopAfter === "number" && Number.isFinite(stopAfter)) {
|
|
4381
|
+
result.autoStopAfterMs = stopAfter <= 0 ? 0 : Math.max(Math.round(stopAfter), MIN_AUTO_STOP_AFTER_MS);
|
|
4382
|
+
}
|
|
4277
4383
|
const access3 = record.folderAccess;
|
|
4278
4384
|
if (typeof access3 === "string" && FOLDER_ACCESS.includes(access3)) {
|
|
4279
4385
|
result.folderAccess = access3;
|
|
@@ -4296,10 +4402,13 @@ function sanitize(value, fallback) {
|
|
|
4296
4402
|
if (typeof record.terminalEnabled === "boolean") {
|
|
4297
4403
|
result.terminalEnabled = record.terminalEnabled;
|
|
4298
4404
|
}
|
|
4405
|
+
if (typeof record.liveSessionsFirst === "boolean") {
|
|
4406
|
+
result.liveSessionsFirst = record.liveSessionsFirst;
|
|
4407
|
+
}
|
|
4299
4408
|
return result;
|
|
4300
4409
|
}
|
|
4301
4410
|
function claudeHome() {
|
|
4302
|
-
return
|
|
4411
|
+
return path14.join(homedir4(), ".claude");
|
|
4303
4412
|
}
|
|
4304
4413
|
function writableRoots(mode, cwd, workspacePaths) {
|
|
4305
4414
|
if (mode === "all")
|
|
@@ -4309,7 +4418,7 @@ function writableRoots(mode, cwd, workspacePaths) {
|
|
|
4309
4418
|
return [...roots];
|
|
4310
4419
|
for (const workspace of [cwd, ...workspacePaths]) {
|
|
4311
4420
|
roots.add(workspace);
|
|
4312
|
-
const parent =
|
|
4421
|
+
const parent = path14.dirname(workspace);
|
|
4313
4422
|
if (parent !== workspace)
|
|
4314
4423
|
roots.add(parent);
|
|
4315
4424
|
}
|
|
@@ -4317,9 +4426,9 @@ function writableRoots(mode, cwd, workspacePaths) {
|
|
|
4317
4426
|
}
|
|
4318
4427
|
|
|
4319
4428
|
// ../host/dist/plugins.js
|
|
4320
|
-
import { readFile as
|
|
4321
|
-
import { homedir as
|
|
4322
|
-
import
|
|
4429
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
4430
|
+
import { homedir as homedir5 } from "node:os";
|
|
4431
|
+
import path15 from "node:path";
|
|
4323
4432
|
var TIMEOUT_MS2 = 12e4;
|
|
4324
4433
|
var SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._@/-]*$/;
|
|
4325
4434
|
var PluginCli = class {
|
|
@@ -4418,10 +4527,10 @@ async function readCatalog(markets, installed) {
|
|
|
4418
4527
|
return lists.flat().sort((a, b) => a.name.localeCompare(b.name));
|
|
4419
4528
|
}
|
|
4420
4529
|
async function readMarketplace(market, installed) {
|
|
4421
|
-
const root = market.installLocation ||
|
|
4530
|
+
const root = market.installLocation || path15.join(homedir5(), ".claude", "plugins", "marketplaces", market.name);
|
|
4422
4531
|
let manifest;
|
|
4423
4532
|
try {
|
|
4424
|
-
const text = await
|
|
4533
|
+
const text = await readFile12(path15.join(root, ".claude-plugin", "marketplace.json"), "utf8");
|
|
4425
4534
|
manifest = JSON.parse(text);
|
|
4426
4535
|
} catch {
|
|
4427
4536
|
return [];
|
|
@@ -4662,8 +4771,8 @@ function describe4(error) {
|
|
|
4662
4771
|
|
|
4663
4772
|
// ../host/dist/relay-link.js
|
|
4664
4773
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
4665
|
-
import { readFile as
|
|
4666
|
-
import
|
|
4774
|
+
import { readFile as readFile13, writeFile as writeFile10, rm as rm3, mkdir as mkdir9 } from "node:fs/promises";
|
|
4775
|
+
import path16 from "node:path";
|
|
4667
4776
|
import { hostname } from "node:os";
|
|
4668
4777
|
import WebSocket from "ws";
|
|
4669
4778
|
|
|
@@ -4718,7 +4827,7 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4718
4827
|
#log;
|
|
4719
4828
|
constructor(options2) {
|
|
4720
4829
|
super();
|
|
4721
|
-
this.#file =
|
|
4830
|
+
this.#file = path16.join(options2.dataDir, "relay.json");
|
|
4722
4831
|
this.#defaultUrl = options2.defaultUrl.replace(/\/+$/, "");
|
|
4723
4832
|
this.#log = options2.log ?? ((msg) => console.log(`[relay] ${msg}`));
|
|
4724
4833
|
}
|
|
@@ -4799,7 +4908,7 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4799
4908
|
}
|
|
4800
4909
|
async #read() {
|
|
4801
4910
|
try {
|
|
4802
|
-
const raw = await
|
|
4911
|
+
const raw = await readFile13(this.#file, "utf8");
|
|
4803
4912
|
const parsed = JSON.parse(raw);
|
|
4804
4913
|
if (typeof parsed !== "object" || parsed === null)
|
|
4805
4914
|
return null;
|
|
@@ -4812,8 +4921,8 @@ var RelayLink = class extends EventEmitter3 {
|
|
|
4812
4921
|
}
|
|
4813
4922
|
}
|
|
4814
4923
|
async #write(credentials) {
|
|
4815
|
-
await
|
|
4816
|
-
await
|
|
4924
|
+
await mkdir9(path16.dirname(this.#file), { recursive: true });
|
|
4925
|
+
await writeFile10(this.#file, `${JSON.stringify(credentials, null, 2)}
|
|
4817
4926
|
`, {
|
|
4818
4927
|
encoding: "utf8",
|
|
4819
4928
|
mode: 384
|
|
@@ -5070,7 +5179,7 @@ function humanize(error) {
|
|
|
5070
5179
|
// ../host/dist/terminal.js
|
|
5071
5180
|
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
5072
5181
|
import { access as access2, constants as constants2 } from "node:fs/promises";
|
|
5073
|
-
import
|
|
5182
|
+
import path17 from "node:path";
|
|
5074
5183
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
5075
5184
|
var SCROLLBACK_BYTES = 256 * 1024;
|
|
5076
5185
|
var FLUSH_MS = 12;
|
|
@@ -5240,22 +5349,22 @@ async function defaultShell() {
|
|
|
5240
5349
|
if (pwsh)
|
|
5241
5350
|
return { file: pwsh, args: [], title: "pwsh" };
|
|
5242
5351
|
const system = process.env.SystemRoot ?? "C:\\Windows";
|
|
5243
|
-
const powershell =
|
|
5352
|
+
const powershell = path17.join(system, "System32/WindowsPowerShell/v1.0/powershell.exe");
|
|
5244
5353
|
if (await exists2(powershell))
|
|
5245
5354
|
return { file: powershell, args: [], title: "powershell" };
|
|
5246
5355
|
return { file: process.env.COMSPEC ?? "cmd.exe", args: [], title: "cmd" };
|
|
5247
5356
|
}
|
|
5248
5357
|
const shell = process.env.SHELL;
|
|
5249
5358
|
if (shell && await exists2(shell))
|
|
5250
|
-
return { file: shell, args: [], title:
|
|
5359
|
+
return { file: shell, args: [], title: path17.basename(shell) };
|
|
5251
5360
|
if (await exists2("/bin/bash"))
|
|
5252
5361
|
return { file: "/bin/bash", args: [], title: "bash" };
|
|
5253
5362
|
return { file: "/bin/sh", args: [], title: "sh" };
|
|
5254
5363
|
}
|
|
5255
5364
|
async function findOnPath(name) {
|
|
5256
|
-
const dirs = (process.env.PATH ?? "").split(
|
|
5365
|
+
const dirs = (process.env.PATH ?? "").split(path17.delimiter).filter(Boolean);
|
|
5257
5366
|
for (const dir of dirs) {
|
|
5258
|
-
const candidate =
|
|
5367
|
+
const candidate = path17.join(dir, name);
|
|
5259
5368
|
if (await exists2(candidate))
|
|
5260
5369
|
return candidate;
|
|
5261
5370
|
}
|
|
@@ -5266,10 +5375,10 @@ function exists2(file) {
|
|
|
5266
5375
|
}
|
|
5267
5376
|
|
|
5268
5377
|
// ../host/dist/mcp-config.js
|
|
5269
|
-
import { readFile as
|
|
5270
|
-
import
|
|
5378
|
+
import { readFile as readFile14, writeFile as writeFile11, mkdir as mkdir10 } from "node:fs/promises";
|
|
5379
|
+
import path18 from "node:path";
|
|
5271
5380
|
var MCP_FILE = ".mcp.json";
|
|
5272
|
-
var LOCAL_SETTINGS2 =
|
|
5381
|
+
var LOCAL_SETTINGS2 = path18.join(".claude", "settings.local.json");
|
|
5273
5382
|
var queues = /* @__PURE__ */ new Map();
|
|
5274
5383
|
function serialize(cwd, work) {
|
|
5275
5384
|
const previous = queues.get(cwd) ?? Promise.resolve();
|
|
@@ -5278,7 +5387,7 @@ function serialize(cwd, work) {
|
|
|
5278
5387
|
return next;
|
|
5279
5388
|
}
|
|
5280
5389
|
async function readProjectServers(cwd) {
|
|
5281
|
-
const file = await readJson(
|
|
5390
|
+
const file = await readJson(path18.join(cwd, MCP_FILE));
|
|
5282
5391
|
const servers = file?.mcpServers;
|
|
5283
5392
|
if (!servers || typeof servers !== "object")
|
|
5284
5393
|
return {};
|
|
@@ -5286,23 +5395,23 @@ async function readProjectServers(cwd) {
|
|
|
5286
5395
|
}
|
|
5287
5396
|
function addProjectServer(cwd, name, config) {
|
|
5288
5397
|
return serialize(cwd, async () => {
|
|
5289
|
-
const file = await readJson(
|
|
5398
|
+
const file = await readJson(path18.join(cwd, MCP_FILE)) ?? {};
|
|
5290
5399
|
const servers = file.mcpServers ?? {};
|
|
5291
5400
|
servers[name] = config;
|
|
5292
5401
|
file.mcpServers = servers;
|
|
5293
|
-
await writeJson(
|
|
5402
|
+
await writeJson(path18.join(cwd, MCP_FILE), file);
|
|
5294
5403
|
await approve(cwd, name);
|
|
5295
5404
|
});
|
|
5296
5405
|
}
|
|
5297
5406
|
function removeProjectServer(cwd, name) {
|
|
5298
5407
|
return serialize(cwd, async () => {
|
|
5299
|
-
const file = await readJson(
|
|
5408
|
+
const file = await readJson(path18.join(cwd, MCP_FILE));
|
|
5300
5409
|
const servers = file?.mcpServers;
|
|
5301
5410
|
if (!servers || !(name in servers))
|
|
5302
5411
|
return false;
|
|
5303
5412
|
delete servers[name];
|
|
5304
|
-
await writeJson(
|
|
5305
|
-
const settingsPath =
|
|
5413
|
+
await writeJson(path18.join(cwd, MCP_FILE), file);
|
|
5414
|
+
const settingsPath = path18.join(cwd, LOCAL_SETTINGS2);
|
|
5306
5415
|
const settings = await readJson(settingsPath);
|
|
5307
5416
|
const enabled = settings?.enabledMcpjsonServers;
|
|
5308
5417
|
if (settings && Array.isArray(enabled) && enabled.includes(name)) {
|
|
@@ -5313,7 +5422,7 @@ function removeProjectServer(cwd, name) {
|
|
|
5313
5422
|
});
|
|
5314
5423
|
}
|
|
5315
5424
|
async function approve(cwd, name) {
|
|
5316
|
-
const file =
|
|
5425
|
+
const file = path18.join(cwd, LOCAL_SETTINGS2);
|
|
5317
5426
|
const settings = await readJson(file) ?? {};
|
|
5318
5427
|
const enabled = Array.isArray(settings.enabledMcpjsonServers) ? settings.enabledMcpjsonServers : [];
|
|
5319
5428
|
if (!enabled.includes(name))
|
|
@@ -5352,21 +5461,21 @@ function describeConfig(config) {
|
|
|
5352
5461
|
}
|
|
5353
5462
|
async function readJson(file) {
|
|
5354
5463
|
try {
|
|
5355
|
-
const raw = await
|
|
5464
|
+
const raw = await readFile14(file, "utf8");
|
|
5356
5465
|
const parsed = JSON.parse(raw);
|
|
5357
5466
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
5358
5467
|
} catch (error) {
|
|
5359
5468
|
if (error.code === "ENOENT")
|
|
5360
5469
|
return null;
|
|
5361
5470
|
if (error instanceof SyntaxError) {
|
|
5362
|
-
throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${
|
|
5471
|
+
throw new Error(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C ${path18.basename(file)}: ${error.message}`);
|
|
5363
5472
|
}
|
|
5364
5473
|
throw error;
|
|
5365
5474
|
}
|
|
5366
5475
|
}
|
|
5367
5476
|
async function writeJson(file, data) {
|
|
5368
|
-
await
|
|
5369
|
-
await
|
|
5477
|
+
await mkdir10(path18.dirname(file), { recursive: true });
|
|
5478
|
+
await writeFile11(file, `${JSON.stringify(data, null, 2)}
|
|
5370
5479
|
`, "utf8");
|
|
5371
5480
|
}
|
|
5372
5481
|
|
|
@@ -5375,11 +5484,18 @@ var SIDE_TITLE = "\xB7 \u0432\u043E\u043F\u0440\u043E\u0441 \u043D\u0430 \u043F\
|
|
|
5375
5484
|
var FINDER_TITLE = "\xB7 \u043F\u043E\u0438\u0441\u043A \u043F\u0440\u043E\u0435\u043A\u0442\u0430";
|
|
5376
5485
|
var HISTORY_PAGE = 250;
|
|
5377
5486
|
var HISTORY_PAGE_MAX = 500;
|
|
5487
|
+
var AUTO_STOP_SWEEP_MS = 30 * 1e3;
|
|
5488
|
+
var BUSY_STATES = /* @__PURE__ */ new Set([
|
|
5489
|
+
"running",
|
|
5490
|
+
"waiting_permission",
|
|
5491
|
+
"reconnecting"
|
|
5492
|
+
]);
|
|
5378
5493
|
var HostServer = class {
|
|
5379
5494
|
#options;
|
|
5380
5495
|
#workspaces;
|
|
5381
5496
|
#archive;
|
|
5382
5497
|
#pins;
|
|
5498
|
+
#order;
|
|
5383
5499
|
#folders;
|
|
5384
5500
|
#settings;
|
|
5385
5501
|
#finder;
|
|
@@ -5398,6 +5514,21 @@ var HostServer = class {
|
|
|
5398
5514
|
#clients = /* @__PURE__ */ new Set();
|
|
5399
5515
|
/** Удалённые вкладки по их id в relay — чтобы адресовать ответ именно той. */
|
|
5400
5516
|
#remotes = /* @__PURE__ */ new Map();
|
|
5517
|
+
/**
|
|
5518
|
+
* Сессии с недописанным черновиком — по окнам (кадр `session.drafts`).
|
|
5519
|
+
*
|
|
5520
|
+
* По окнам, а не одним множеством: черновик держит сессию, пока окно с ним
|
|
5521
|
+
* подключено, и уйти вместе с окном он должен сам, не дожидаясь, пока
|
|
5522
|
+
* кто-то пришлёт пустой список.
|
|
5523
|
+
*/
|
|
5524
|
+
#drafts = /* @__PURE__ */ new Map();
|
|
5525
|
+
/**
|
|
5526
|
+
* Последнее движение черновика по сессии — появился, исчез, окно с ним
|
|
5527
|
+
* отключилось. Отсчёт простоя идёт от позднего из этого и событий самой
|
|
5528
|
+
* сессии: стёртый черновик — тоже действие человека в ней.
|
|
5529
|
+
*/
|
|
5530
|
+
#draftTouchedAt = /* @__PURE__ */ new Map();
|
|
5531
|
+
#autoStopTimer = null;
|
|
5401
5532
|
#hostId = randomUUID4();
|
|
5402
5533
|
#plugins = new PluginCli();
|
|
5403
5534
|
#auth = new AuthCli();
|
|
@@ -5418,6 +5549,7 @@ var HostServer = class {
|
|
|
5418
5549
|
this.#workspaces = new WorkspaceRegistry(options2.dataDir);
|
|
5419
5550
|
this.#archive = new SessionArchive(options2.dataDir);
|
|
5420
5551
|
this.#pins = new SessionPins(options2.dataDir);
|
|
5552
|
+
this.#order = new ListOrderStore(options2.dataDir);
|
|
5421
5553
|
this.#folders = new Folders(options2.dataDir);
|
|
5422
5554
|
this.#settings = new AppSettingsStore(options2.dataDir);
|
|
5423
5555
|
this.#finder = new ProjectFinder(this.#workspaces);
|
|
@@ -5436,6 +5568,7 @@ var HostServer = class {
|
|
|
5436
5568
|
await this.#workspaces.load();
|
|
5437
5569
|
await this.#archive.load();
|
|
5438
5570
|
await this.#pins.load();
|
|
5571
|
+
await this.#order.load();
|
|
5439
5572
|
await this.#folders.load();
|
|
5440
5573
|
await this.#settings.load();
|
|
5441
5574
|
await this.#github.load();
|
|
@@ -5460,8 +5593,13 @@ var HostServer = class {
|
|
|
5460
5593
|
await this.#relay.start().catch((error) => {
|
|
5461
5594
|
console.error("[relay]", error instanceof Error ? error.message : String(error));
|
|
5462
5595
|
});
|
|
5596
|
+
this.#autoStopTimer = setInterval(() => void this.#stopIdleSessions(), AUTO_STOP_SWEEP_MS);
|
|
5597
|
+
this.#autoStopTimer.unref();
|
|
5463
5598
|
}
|
|
5464
5599
|
async shutdown() {
|
|
5600
|
+
if (this.#autoStopTimer)
|
|
5601
|
+
clearInterval(this.#autoStopTimer);
|
|
5602
|
+
this.#autoStopTimer = null;
|
|
5465
5603
|
this.#auth.cancel();
|
|
5466
5604
|
this.#terminals.closeAll();
|
|
5467
5605
|
await this.#relay.close();
|
|
@@ -5481,7 +5619,7 @@ var HostServer = class {
|
|
|
5481
5619
|
}
|
|
5482
5620
|
};
|
|
5483
5621
|
this.#clients.add(client);
|
|
5484
|
-
socket.on("close", () => this.#
|
|
5622
|
+
socket.on("close", () => this.#forgetClient(client));
|
|
5485
5623
|
socket.on("message", (raw) => {
|
|
5486
5624
|
let parsed;
|
|
5487
5625
|
try {
|
|
@@ -5512,7 +5650,7 @@ var HostServer = class {
|
|
|
5512
5650
|
if (!client)
|
|
5513
5651
|
return;
|
|
5514
5652
|
this.#remotes.delete(clientId);
|
|
5515
|
-
this.#
|
|
5653
|
+
this.#forgetClient(client);
|
|
5516
5654
|
});
|
|
5517
5655
|
this.#relay.on("frame", (clientId, body) => {
|
|
5518
5656
|
const client = this.#remotes.get(clientId);
|
|
@@ -5619,6 +5757,8 @@ var HostServer = class {
|
|
|
5619
5757
|
});
|
|
5620
5758
|
this.#send(socket, { type: "workspace.list", ...this.#workspaceLists() });
|
|
5621
5759
|
this.#sendFolders(socket);
|
|
5760
|
+
this.#send(socket, { type: "order.state", order: this.#order.current });
|
|
5761
|
+
this.#sendLive(socket);
|
|
5622
5762
|
this.#auth.status().then((status) => this.#send(socket, { type: "auth.status", status })).catch(() => void 0);
|
|
5623
5763
|
this.#send(socket, { type: "relay.state", state: this.#relay.state });
|
|
5624
5764
|
return;
|
|
@@ -5693,6 +5833,22 @@ var HostServer = class {
|
|
|
5693
5833
|
case "session.close":
|
|
5694
5834
|
await this.#dropSession(frame.sessionId, "closed by client");
|
|
5695
5835
|
return;
|
|
5836
|
+
case "session.drafts": {
|
|
5837
|
+
const next = new Set(frame.sessionIds.filter((id) => typeof id === "string"));
|
|
5838
|
+
const previous = this.#drafts.get(socket) ?? /* @__PURE__ */ new Set();
|
|
5839
|
+
const now = Date.now();
|
|
5840
|
+
for (const id of next)
|
|
5841
|
+
if (!previous.has(id))
|
|
5842
|
+
this.#draftTouchedAt.set(id, now);
|
|
5843
|
+
for (const id of previous)
|
|
5844
|
+
if (!next.has(id))
|
|
5845
|
+
this.#draftTouchedAt.set(id, now);
|
|
5846
|
+
if (next.size > 0)
|
|
5847
|
+
this.#drafts.set(socket, next);
|
|
5848
|
+
else
|
|
5849
|
+
this.#drafts.delete(socket);
|
|
5850
|
+
return;
|
|
5851
|
+
}
|
|
5696
5852
|
case "session.reconnect":
|
|
5697
5853
|
this.#requireSession(frame.sessionId).reconnect("\u043F\u043E \u043F\u0440\u043E\u0441\u044C\u0431\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F");
|
|
5698
5854
|
return;
|
|
@@ -5734,6 +5890,13 @@ var HostServer = class {
|
|
|
5734
5890
|
case "session.pins":
|
|
5735
5891
|
await this.#sendPinned();
|
|
5736
5892
|
return;
|
|
5893
|
+
case "order.get":
|
|
5894
|
+
this.#send(socket, { type: "order.state", order: this.#order.current });
|
|
5895
|
+
return;
|
|
5896
|
+
case "order.set":
|
|
5897
|
+
await this.#order.set(frame.scope, frame.ids, frame.workspaceId);
|
|
5898
|
+
this.#broadcast({ type: "order.state", order: this.#order.current });
|
|
5899
|
+
return;
|
|
5737
5900
|
case "folder.create": {
|
|
5738
5901
|
if (frame.scope === "session") {
|
|
5739
5902
|
if (!frame.workspaceId)
|
|
@@ -6375,8 +6538,82 @@ var HostServer = class {
|
|
|
6375
6538
|
await session.close(reason);
|
|
6376
6539
|
this.#sessions.delete(sessionId);
|
|
6377
6540
|
this.#broadcast({ type: "session.closed", sessionId, reason });
|
|
6541
|
+
this.#sendLive();
|
|
6378
6542
|
return true;
|
|
6379
6543
|
}
|
|
6544
|
+
/**
|
|
6545
|
+
* Окно ушло — вместе с ним уходят и его черновики.
|
|
6546
|
+
*
|
|
6547
|
+
* Сам текст остаётся в localStorage окна и вернётся, когда сессию откроют
|
|
6548
|
+
* снова; держать ради него процесс CLI, пока окна нет, незачем. Момент
|
|
6549
|
+
* ухода считается действием: сессия, которую только что бросили с
|
|
6550
|
+
* черновиком, получает полный срок, а не остаток.
|
|
6551
|
+
*/
|
|
6552
|
+
#forgetClient(client) {
|
|
6553
|
+
this.#clients.delete(client);
|
|
6554
|
+
const drafts = this.#drafts.get(client);
|
|
6555
|
+
if (!drafts)
|
|
6556
|
+
return;
|
|
6557
|
+
this.#drafts.delete(client);
|
|
6558
|
+
const now = Date.now();
|
|
6559
|
+
for (const id of drafts)
|
|
6560
|
+
this.#draftTouchedAt.set(id, now);
|
|
6561
|
+
}
|
|
6562
|
+
/**
|
|
6563
|
+
* Остановить живые сессии, простаивающие дольше `autoStopAfterMs`.
|
|
6564
|
+
*
|
|
6565
|
+
* Только пользовательские: боковой вопрос уходит вместе с родителем, у
|
|
6566
|
+
* сессии поиска проекта своя жизнь. Простой — это когда сессия ничего не
|
|
6567
|
+
* делает, в ней ничего не происходит и ни одно окно не держит для неё
|
|
6568
|
+
* черновик (см. `AppSettings.autoStopAfterMs`).
|
|
6569
|
+
*/
|
|
6570
|
+
async #stopIdleSessions() {
|
|
6571
|
+
const after = this.#settings.current.autoStopAfterMs;
|
|
6572
|
+
if (after <= 0)
|
|
6573
|
+
return;
|
|
6574
|
+
const now = Date.now();
|
|
6575
|
+
const drafted = /* @__PURE__ */ new Set();
|
|
6576
|
+
for (const ids of this.#drafts.values())
|
|
6577
|
+
for (const id of ids)
|
|
6578
|
+
drafted.add(id);
|
|
6579
|
+
for (const session of [...this.#sessions.values()]) {
|
|
6580
|
+
if (!session.workspaceId || session === this.#finderSession)
|
|
6581
|
+
continue;
|
|
6582
|
+
const summary = session.summary();
|
|
6583
|
+
if (summary.parentSessionId)
|
|
6584
|
+
continue;
|
|
6585
|
+
if (drafted.has(session.id))
|
|
6586
|
+
continue;
|
|
6587
|
+
if (BUSY_STATES.has(summary.state) || summary.backgroundWork)
|
|
6588
|
+
continue;
|
|
6589
|
+
const side = this.#sides.get(session.id);
|
|
6590
|
+
if (side && BUSY_STATES.has(side.state))
|
|
6591
|
+
continue;
|
|
6592
|
+
const lastActivity = Math.max(summary.updatedAt, side?.summary().updatedAt ?? 0, this.#draftTouchedAt.get(session.id) ?? 0);
|
|
6593
|
+
if (now - lastActivity < after)
|
|
6594
|
+
continue;
|
|
6595
|
+
try {
|
|
6596
|
+
await this.#dropSession(session.id, "idle");
|
|
6597
|
+
} catch (error) {
|
|
6598
|
+
console.error("[auto-stop]", error instanceof Error ? error.message : String(error));
|
|
6599
|
+
}
|
|
6600
|
+
this.#draftTouchedAt.delete(session.id);
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
/**
|
|
6604
|
+
* Живые сессии всех проектов — вкладке запущенных.
|
|
6605
|
+
*
|
|
6606
|
+
* Без `socket` — всем окнам: состав живых поменялся. Боковые сессии и сессия
|
|
6607
|
+
* поиска проекта в список не идут, их пользователь не заводил.
|
|
6608
|
+
*/
|
|
6609
|
+
#sendLive(socket) {
|
|
6610
|
+
const sessions = [...this.#sessions.values()].filter((s) => s !== this.#finderSession).map((s) => s.summary()).filter((s) => !s.parentSessionId);
|
|
6611
|
+
const frame = { type: "session.live", sessions };
|
|
6612
|
+
if (socket)
|
|
6613
|
+
this.#send(socket, frame);
|
|
6614
|
+
else
|
|
6615
|
+
this.#broadcast(frame);
|
|
6616
|
+
}
|
|
6380
6617
|
/**
|
|
6381
6618
|
* Разослать список сессий проекта всем окнам.
|
|
6382
6619
|
*
|
|
@@ -6438,6 +6675,7 @@ var HostServer = class {
|
|
|
6438
6675
|
this.#wire(session);
|
|
6439
6676
|
this.#sessions.set(session.id, session);
|
|
6440
6677
|
session.start();
|
|
6678
|
+
this.#sendLive();
|
|
6441
6679
|
this.#send(socket, { type: "session.summary", session: session.summary() });
|
|
6442
6680
|
}
|
|
6443
6681
|
/**
|
|
@@ -6551,7 +6789,7 @@ var HostServer = class {
|
|
|
6551
6789
|
*/
|
|
6552
6790
|
#roots() {
|
|
6553
6791
|
const roots = this.#finder.roots();
|
|
6554
|
-
return roots.length > 0 ? roots : [
|
|
6792
|
+
return roots.length > 0 ? roots : [homedir6()];
|
|
6555
6793
|
}
|
|
6556
6794
|
/**
|
|
6557
6795
|
* Где сессии этого каталога разрешено писать без вопроса.
|
|
@@ -6884,15 +7122,15 @@ var HostServer = class {
|
|
|
6884
7122
|
}
|
|
6885
7123
|
#serveStatic(req, res) {
|
|
6886
7124
|
const webRoot = this.#options.webRoot;
|
|
6887
|
-
if (!webRoot || !
|
|
7125
|
+
if (!webRoot || !existsSync4(webRoot)) {
|
|
6888
7126
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
|
|
6889
7127
|
res.end("CCV host is running. UI is served by the Vite dev server.\n");
|
|
6890
7128
|
return;
|
|
6891
7129
|
}
|
|
6892
7130
|
const url2 = new URL(req.url ?? "/", "http://localhost");
|
|
6893
|
-
const requested =
|
|
6894
|
-
const target = requested.startsWith(webRoot) &&
|
|
6895
|
-
if (!
|
|
7131
|
+
const requested = path19.join(webRoot, path19.normalize(url2.pathname));
|
|
7132
|
+
const target = requested.startsWith(webRoot) && existsSync4(requested) && statSync3(requested).isFile() ? requested : path19.join(webRoot, "index.html");
|
|
7133
|
+
if (!existsSync4(target)) {
|
|
6896
7134
|
res.writeHead(404).end("not found");
|
|
6897
7135
|
return;
|
|
6898
7136
|
}
|
|
@@ -6917,12 +7155,12 @@ function contentType(file) {
|
|
|
6917
7155
|
".svg": "image/svg+xml",
|
|
6918
7156
|
".woff2": "font/woff2"
|
|
6919
7157
|
};
|
|
6920
|
-
return types[
|
|
7158
|
+
return types[path19.extname(file)] ?? "application/octet-stream";
|
|
6921
7159
|
}
|
|
6922
7160
|
|
|
6923
7161
|
// dist/ccv.js
|
|
6924
|
-
var HERE =
|
|
6925
|
-
var PACKAGE_ROOT =
|
|
7162
|
+
var HERE = path20.dirname(fileURLToPath(import.meta.url));
|
|
7163
|
+
var PACKAGE_ROOT = path20.resolve(HERE, "..");
|
|
6926
7164
|
var USAGE = `ccv \u2014 Claude Code Visual: \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0445\u043E\u0441\u0442 Claude Code \u0441 \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043E\u043C \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435.
|
|
6927
7165
|
|
|
6928
7166
|
\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435:
|
|
@@ -6989,7 +7227,7 @@ var server = new HostServer({
|
|
|
6989
7227
|
dataDir: defaults.dataDir,
|
|
6990
7228
|
// UI лежит в пакете рядом с бандлом — это и есть разница между установленной
|
|
6991
7229
|
// утилитой и запуском из репозитория, где сборка живёт в `apps/web/dist`.
|
|
6992
|
-
webRoot: process.env.CCV_WEB_ROOT ??
|
|
7230
|
+
webRoot: process.env.CCV_WEB_ROOT ?? path20.join(PACKAGE_ROOT, "web"),
|
|
6993
7231
|
version,
|
|
6994
7232
|
relayUrl: defaults.relayUrl
|
|
6995
7233
|
});
|
|
@@ -7018,7 +7256,7 @@ for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
|
7018
7256
|
}
|
|
7019
7257
|
async function packageVersion() {
|
|
7020
7258
|
try {
|
|
7021
|
-
const text = await
|
|
7259
|
+
const text = await readFile15(path20.join(PACKAGE_ROOT, "package.json"), "utf8");
|
|
7022
7260
|
return JSON.parse(text).version ?? "0.0.0";
|
|
7023
7261
|
} catch {
|
|
7024
7262
|
return "0.0.0";
|