dsh-session-manager 0.7.1 → 0.7.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/CHANGELOG.md +19 -0
- package/README.md +22 -20
- package/README.zh.md +20 -18
- package/lib/client.js +429 -95
- package/lib/compat/dsh-adapter.js +177 -7
- package/lib/index.js +946 -39
- package/lib/session-files.js +73 -6
- package/package.json +1 -1
package/lib/session-files.js
CHANGED
|
@@ -21,7 +21,7 @@ const MAX_HEADER_BYTES = 1024 * 1024;
|
|
|
21
21
|
|
|
22
22
|
export function assertSessionId(id) {
|
|
23
23
|
if (typeof id !== "string" || id.trim() === "" || id === "." || id === ".."
|
|
24
|
-
|| /[\\/\x00-\x1f
|
|
24
|
+
|| /[\\/\x00-\x1f<>"|?*]/.test(id) || /[. ]$/.test(id)) {
|
|
25
25
|
throw Object.assign(new Error("sessionId 必须是合法的会话标识,不能包含路径或特殊目录名"), { code: "bad-request" });
|
|
26
26
|
}
|
|
27
27
|
return id;
|
|
@@ -46,12 +46,56 @@ export function parseSessionGenerationFromFilename(filename) {
|
|
|
46
46
|
return match[1] !== undefined ? Number(match[1]) : 0;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Encode a session ID into its on-disk session directory name,
|
|
51
|
+
* matching DSH kernel's `encodeSegment` contract in
|
|
52
|
+
* packages/session/session-persistence-jsonl/src/format.ts:
|
|
53
|
+
* - "." -> "~002E"
|
|
54
|
+
* - ".." -> "~002E~002E"
|
|
55
|
+
* - Safe chars [A-Za-z0-9._-] (except "~") remain unchanged
|
|
56
|
+
* - Other characters (including ":", "~", non-ASCII) are escaped as ~XXXX (4-digit uppercase hex)
|
|
57
|
+
*/
|
|
58
|
+
export function encodeFsSessionSegment(raw) {
|
|
59
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
60
|
+
throw new Error("cannot encode an empty path segment");
|
|
61
|
+
}
|
|
62
|
+
if (raw === ".") return "~002E";
|
|
63
|
+
if (raw === "..") return "~002E~002E";
|
|
64
|
+
let out = "";
|
|
65
|
+
for (let i = 0; i < raw.length; i++) {
|
|
66
|
+
const code = raw.charCodeAt(i);
|
|
67
|
+
const ch = String.fromCharCode(code);
|
|
68
|
+
if (ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch)) {
|
|
69
|
+
out += ch;
|
|
70
|
+
} else {
|
|
71
|
+
out += "~" + code.toString(16).toUpperCase().padStart(4, "0");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Decode an on-disk session directory name back to raw session ID.
|
|
79
|
+
*/
|
|
80
|
+
export function decodeFsSessionSegment(encoded) {
|
|
81
|
+
if (typeof encoded !== "string" || encoded.length === 0) return encoded;
|
|
82
|
+
if (encoded === "~002E") return ".";
|
|
83
|
+
if (encoded === "~002E~002E") return "..";
|
|
84
|
+
return encoded.replace(/~([0-9A-Fa-f]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Encode a project directory path (cwd) into its on-disk project directory key,
|
|
89
|
+
* matching DSH kernel's `projectKey` contract:
|
|
90
|
+
* Wrapping "--...--" markers with path separators collapsed into "-".
|
|
91
|
+
*/
|
|
92
|
+
export function encodeProjectSegment(path) {
|
|
93
|
+
if (typeof path !== "string") return "--root--";
|
|
50
94
|
let readable = "";
|
|
51
95
|
let separatorRun = false;
|
|
52
|
-
for (let i = 0; i <
|
|
53
|
-
const ch =
|
|
54
|
-
const code =
|
|
96
|
+
for (let i = 0; i < path.length; i++) {
|
|
97
|
+
const ch = path[i];
|
|
98
|
+
const code = path.charCodeAt(i);
|
|
55
99
|
if (ch === "/" || ch === "\\" || ch === ":") {
|
|
56
100
|
if (!separatorRun) readable += "-";
|
|
57
101
|
separatorRun = true;
|
|
@@ -66,6 +110,28 @@ export function encodeSessionSegment(id) {
|
|
|
66
110
|
return "--" + (readable.replace(/^-+/, "") || "root").slice(0, 251) + "--";
|
|
67
111
|
}
|
|
68
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Legacy alias for `encodeProjectSegment`.
|
|
115
|
+
* @deprecated Use `encodeProjectSegment` for project cwd keys, or `encodeFsSessionSegment` for session directories.
|
|
116
|
+
*/
|
|
117
|
+
export const encodeSessionSegment = encodeProjectSegment;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Return strictly verified candidate directory names for a given session ID.
|
|
121
|
+
* - Primary: DSH's true `encodeFsSessionSegment(id)` (e.g. "im~003Afeishu...")
|
|
122
|
+
* - Raw `id`: matches when raw ID equals encoded ID (common UUIDs), or if raw dir was used
|
|
123
|
+
* - Legacy projectKey format: transitional compatibility for `--id--` created by older plugin versions
|
|
124
|
+
*/
|
|
125
|
+
export function candidateSessionDirs(id) {
|
|
126
|
+
assertSessionId(id);
|
|
127
|
+
const primary = encodeFsSessionSegment(id);
|
|
128
|
+
const candidates = [primary];
|
|
129
|
+
if (id !== primary) candidates.push(id);
|
|
130
|
+
const legacyProject = encodeProjectSegment(id);
|
|
131
|
+
if (legacyProject !== primary && legacyProject !== id) candidates.push(legacyProject);
|
|
132
|
+
return candidates;
|
|
133
|
+
}
|
|
134
|
+
|
|
69
135
|
/** Only an actual <sessions>/<project>/<session> directory may be mutated.
|
|
70
136
|
* The root itself may be configured through a symlink, but neither child may
|
|
71
137
|
* be a symlink/junction. Check again immediately before destructive cleanup.
|
|
@@ -79,8 +145,9 @@ export async function assertSessionDirectory(root, directory, id, { allowMissing
|
|
|
79
145
|
const target = resolve(directory);
|
|
80
146
|
const rel = relative(base, target);
|
|
81
147
|
const parts = rel.split(sep);
|
|
148
|
+
const validDirs = candidateSessionDirs(id);
|
|
82
149
|
if (isAbsolute(rel) || parts.length !== 2 || parts.some(p => p === ".." || p === "." || p === "")
|
|
83
|
-
|| !
|
|
150
|
+
|| !validDirs.includes(parts[1])) {
|
|
84
151
|
throw new Error("会话目录超出允许范围,已停止文件操作");
|
|
85
152
|
}
|
|
86
153
|
const canonicalRoot = await fs.realpath(base);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-manager",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "DeepSeek Harness session and workspace manager —— Workspaces: archive and visibility. Sessions: move, migrate presets, delete, favorite, review later, prioritize, tag, and annotate. Batch operations. | DSH 会话与工作区管理——工作区:归档与显示。会话:跨工作区移动、预设迁移、删除;收藏、待回看、优先级、标签、备注。批量操作。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|