dsh-session-manager 0.5.4 → 0.6.2
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/CHANGELOG.md +23 -0
- package/README.local-workflow.md +52 -0
- package/README.md +11 -1
- package/README.zh.md +11 -3
- package/lib/annotation-store.js +1 -1
- package/lib/client.js +1481 -148
- package/lib/compat/dsh-adapter.js +55 -0
- package/lib/index.js +83 -2
- package/package.json +1 -1
|
@@ -64,6 +64,60 @@ function versionFromArgv() {
|
|
|
64
64
|
return undefined;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Compare two semver strings (e.g. "0.5.4" vs "0.5.5", "0.5.5-beta.10" vs "0.5.5-beta.2").
|
|
69
|
+
* Returns 1 if v1 > v2, -1 if v1 < v2, and 0 if equal.
|
|
70
|
+
*/
|
|
71
|
+
export function compareSemver(v1, v2) {
|
|
72
|
+
if (typeof v1 !== "string" || typeof v2 !== "string") return 0;
|
|
73
|
+
const parse = (v) => {
|
|
74
|
+
// Strip build metadata (+...)
|
|
75
|
+
const noBuild = v.trim().replace(/^[vV]/, "").split("+")[0];
|
|
76
|
+
const hyphenIdx = noBuild.indexOf("-");
|
|
77
|
+
const core = hyphenIdx === -1 ? noBuild : noBuild.slice(0, hyphenIdx);
|
|
78
|
+
const prerelease = hyphenIdx === -1 ? undefined : noBuild.slice(hyphenIdx + 1);
|
|
79
|
+
const parts = (core || "").split(".").map(n => parseInt(n, 10) || 0);
|
|
80
|
+
while (parts.length < 3) parts.push(0);
|
|
81
|
+
return { parts, prerelease: prerelease !== undefined ? prerelease.trim() : undefined };
|
|
82
|
+
};
|
|
83
|
+
const p1 = parse(v1);
|
|
84
|
+
const p2 = parse(v2);
|
|
85
|
+
for (let i = 0; i < 3; i++) {
|
|
86
|
+
if (p1.parts[i] > p2.parts[i]) return 1;
|
|
87
|
+
if (p1.parts[i] < p2.parts[i]) return -1;
|
|
88
|
+
}
|
|
89
|
+
// When major, minor, patch match: version without prerelease is higher
|
|
90
|
+
if (p1.prerelease && !p2.prerelease) return -1;
|
|
91
|
+
if (!p1.prerelease && p2.prerelease) return 1;
|
|
92
|
+
if (p1.prerelease && p2.prerelease) {
|
|
93
|
+
if (p1.prerelease === p2.prerelease) return 0;
|
|
94
|
+
const id1 = p1.prerelease.split(".");
|
|
95
|
+
const id2 = p2.prerelease.split(".");
|
|
96
|
+
const maxLen = Math.max(id1.length, id2.length);
|
|
97
|
+
for (let i = 0; i < maxLen; i++) {
|
|
98
|
+
if (i >= id1.length) return -1; // fewer identifiers is lower precedence
|
|
99
|
+
if (i >= id2.length) return 1;
|
|
100
|
+
const part1 = id1[i];
|
|
101
|
+
const part2 = id2[i];
|
|
102
|
+
if (part1 === part2) continue;
|
|
103
|
+
const num1 = /^\d+$/.test(part1) ? parseInt(part1, 10) : null;
|
|
104
|
+
const num2 = /^\d+$/.test(part2) ? parseInt(part2, 10) : null;
|
|
105
|
+
if (num1 !== null && num2 !== null) {
|
|
106
|
+
if (num1 > num2) return 1;
|
|
107
|
+
if (num1 < num2) return -1;
|
|
108
|
+
} else if (num1 !== null && num2 === null) {
|
|
109
|
+
return -1; // numeric has lower precedence than non-numeric
|
|
110
|
+
} else if (num1 === null && num2 !== null) {
|
|
111
|
+
return 1;
|
|
112
|
+
} else {
|
|
113
|
+
if (part1 > part2) return 1;
|
|
114
|
+
if (part1 < part2) return -1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
67
121
|
/** Normalize a persistence list item from both snapshot (header field) and raw header shapes. **/
|
|
68
122
|
export function normalizeSessionHeader(value) {
|
|
69
123
|
if (value === undefined || value === null) return undefined;
|
|
@@ -95,6 +149,7 @@ export function createDshAdapter(ctx) {
|
|
|
95
149
|
get agentPresets() { return getService(ctx, "agentPresets"); },
|
|
96
150
|
get projectionCache() { return getService(ctx, "sessionProjectionCache"); },
|
|
97
151
|
get homePath() { return getService(ctx, "dshHomePath"); },
|
|
152
|
+
get pluginManager() { return getService(ctx, "pluginManager"); },
|
|
98
153
|
|
|
99
154
|
getSession(id) {
|
|
100
155
|
try { return adapter.sessions && adapter.sessions.get && adapter.sessions.get(id); } catch { return undefined; }
|
package/lib/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import { lstat, mkdir, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
|
26
26
|
import { basename, dirname, join } from "node:path";
|
|
27
27
|
import { randomBytes } from "node:crypto";
|
|
28
28
|
import { createAnnotationStore } from "./annotation-store.js";
|
|
29
|
-
import { createDshAdapter, normalizeSessionHeader } from "./compat/dsh-adapter.js";
|
|
29
|
+
import { createDshAdapter, normalizeSessionHeader, compareSemver } from "./compat/dsh-adapter.js";
|
|
30
30
|
import { ARTIFACT_NAMES, assertSessionId, assertSessionDirectory, encodeSessionSegment, parseSessionGenerationFromFilename, readSessionHeader, readSessionFile, writeTempFile, replaceSessionFile } from "./session-files.js";
|
|
31
31
|
|
|
32
32
|
export const name = "dsh-session-manager";
|
|
@@ -53,6 +53,7 @@ function safeErrorMessage(error) {
|
|
|
53
53
|
}
|
|
54
54
|
try { return JSON.stringify(error); } catch { return String(error); }
|
|
55
55
|
}
|
|
56
|
+
|
|
56
57
|
const API_PREFIX = "/session-manager/api";
|
|
57
58
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
59
|
function liveWriterOf(persistence, sessionId) {
|
|
@@ -1239,6 +1240,86 @@ ${rest}`, "utf8");
|
|
|
1239
1240
|
const result = await scanPreset({ id: url.searchParams.get("workspaceId") || "", sessionId });
|
|
1240
1241
|
return send(res, 200, { ok: true, result });
|
|
1241
1242
|
}
|
|
1243
|
+
if (req.method === "GET" && path === "/update/check") {
|
|
1244
|
+
const currentVersion = adapter.pluginVersion() || "0.6.2";
|
|
1245
|
+
let latestVersion = currentVersion;
|
|
1246
|
+
let hasUpdate = false;
|
|
1247
|
+
try {
|
|
1248
|
+
const requestedRegistry = url.searchParams.get("registry")?.trim();
|
|
1249
|
+
const registry = (requestedRegistry && /^https?:\/\//i.test(requestedRegistry))
|
|
1250
|
+
? requestedRegistry
|
|
1251
|
+
: (process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org");
|
|
1252
|
+
const base = registry.endsWith("/") ? registry : registry + "/";
|
|
1253
|
+
const pkgUrl = new URL("dsh-session-manager/latest", base).toString();
|
|
1254
|
+
const controller = new AbortController();
|
|
1255
|
+
const timer = setTimeout(() => controller.abort(), 6000);
|
|
1256
|
+
try {
|
|
1257
|
+
const response = await fetch(pkgUrl, {
|
|
1258
|
+
signal: controller.signal,
|
|
1259
|
+
headers: { "accept": "application/json" }
|
|
1260
|
+
});
|
|
1261
|
+
if (response.ok) {
|
|
1262
|
+
const data = await response.json();
|
|
1263
|
+
if (data && typeof data.version === "string") {
|
|
1264
|
+
latestVersion = data.version;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} finally {
|
|
1268
|
+
clearTimeout(timer);
|
|
1269
|
+
}
|
|
1270
|
+
hasUpdate = compareSemver(latestVersion, currentVersion) > 0;
|
|
1271
|
+
return send(res, 200, {
|
|
1272
|
+
ok: true,
|
|
1273
|
+
result: { currentVersion, latestVersion, hasUpdate }
|
|
1274
|
+
});
|
|
1275
|
+
} catch (err) {
|
|
1276
|
+
return send(res, 200, {
|
|
1277
|
+
ok: false,
|
|
1278
|
+
code: "update-check-failed",
|
|
1279
|
+
error: "检查更新失败: " + safeErrorMessage(err),
|
|
1280
|
+
result: { currentVersion, latestVersion: currentVersion, hasUpdate: false }
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (req.method === "POST" && path === "/update/install") {
|
|
1285
|
+
const rawVersion = typeof body?.version === "string" ? body.version.trim() : "";
|
|
1286
|
+
if (rawVersion && !/^[a-zA-Z0-9_.-]+$/.test(rawVersion)) {
|
|
1287
|
+
return send(res, 400, { ok: false, code: "bad-request", error: "Invalid version format" });
|
|
1288
|
+
}
|
|
1289
|
+
const targetVersion = rawVersion || "latest";
|
|
1290
|
+
const spec = "dsh-session-manager@" + targetVersion;
|
|
1291
|
+
const registry = typeof body?.registry === "string" && /^https?:\/\//i.test(body.registry.trim()) ? body.registry.trim() : "";
|
|
1292
|
+
const pm = adapter.pluginManager;
|
|
1293
|
+
if (pm && typeof pm.installBundle === "function") {
|
|
1294
|
+
try {
|
|
1295
|
+
const prevRegistry = process.env.NPM_CONFIG_REGISTRY;
|
|
1296
|
+
if (registry) {
|
|
1297
|
+
process.env.NPM_CONFIG_REGISTRY = registry;
|
|
1298
|
+
}
|
|
1299
|
+
let outcome;
|
|
1300
|
+
try {
|
|
1301
|
+
outcome = await (registry ? pm.installBundle(spec, { registry }) : pm.installBundle(spec));
|
|
1302
|
+
} finally {
|
|
1303
|
+
if (registry) {
|
|
1304
|
+
if (prevRegistry !== undefined) {
|
|
1305
|
+
process.env.NPM_CONFIG_REGISTRY = prevRegistry;
|
|
1306
|
+
} else {
|
|
1307
|
+
delete process.env.NPM_CONFIG_REGISTRY;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
const application = outcome?.application || "restart-required";
|
|
1312
|
+
return send(res, 200, { ok: true, result: { application, spec } });
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
return send(res, 500, { ok: false, code: "install-failed", error: safeErrorMessage(err) });
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return send(res, 501, {
|
|
1318
|
+
ok: false,
|
|
1319
|
+
code: "plugin-manager-unavailable",
|
|
1320
|
+
error: "当前宿主环境未提供 PluginManager 安装服务,请通过 DSH 插件市场或命令行更新"
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1242
1323
|
return send(res, 404, { ok: false, error: "not found: " + req.method + " " + path });
|
|
1243
1324
|
} catch (error) {
|
|
1244
1325
|
ctx.logger.warn("session-manager: api error: " + safeErrorMessage(error) + "; json=" + safeJson(error));
|
|
@@ -1251,4 +1332,4 @@ ${rest}`, "utf8");
|
|
|
1251
1332
|
}), "session-manager: http api");
|
|
1252
1333
|
}
|
|
1253
1334
|
|
|
1254
|
-
export { listSessionHeaders, scanSessionHeaders, encodeSessionSegment };
|
|
1335
|
+
export { listSessionHeaders, scanSessionHeaders, encodeSessionSegment, compareSemver };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "DeepSeek Harness session manager: delete, archive, move across workspaces, migrate presets, favorites, review-later, search, filter, sort, priority, tags, notes, and batch actions. | DSH 会话管理:删除、归档、跨工作区移动、预设迁移、收藏、待回看、搜索、筛选、排序、优先级、标签、备注及批量操作。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|