dsh-sessions-manager 3.5.2 → 3.5.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/README.en.md +2 -2
- package/README.md +2 -2
- package/lib/client.js +9 -2
- package/lib/client.js.map +2 -2
- package/lib/index.js +131 -118
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
- package/src/client/index.jsx +16 -3
- package/src/compat/persistence.js +2 -0
- package/src/index.js +20 -1
package/lib/index.js
CHANGED
|
@@ -666,6 +666,121 @@ function createTitleIndexStore({ dir, file }) {
|
|
|
666
666
|
};
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
+
// src/handle-era-paths.js
|
|
670
|
+
import { readdir, stat } from "node:fs/promises";
|
|
671
|
+
import { basename, join as join4 } from "node:path";
|
|
672
|
+
|
|
673
|
+
// src/path-guard.js
|
|
674
|
+
function splitSegments(target) {
|
|
675
|
+
return String(target).replace(/[\\/]+$/, "").split(/[\\/]/).filter((seg) => seg.length > 0);
|
|
676
|
+
}
|
|
677
|
+
function stripDriveLetter(segments) {
|
|
678
|
+
return segments.length > 0 && /^[A-Za-z]:$/.test(segments[0]) ? segments.slice(1) : segments;
|
|
679
|
+
}
|
|
680
|
+
function pathOwnsSession(target, sid) {
|
|
681
|
+
if (typeof target !== "string" || target.length === 0) return false;
|
|
682
|
+
if (typeof sid !== "string" || sid.length === 0) return false;
|
|
683
|
+
const segments = stripDriveLetter(splitSegments(target));
|
|
684
|
+
if (segments.length === 0) return false;
|
|
685
|
+
const file = segments[segments.length - 1];
|
|
686
|
+
if (segments.length >= 2 && segments[segments.length - 2] === sid) return true;
|
|
687
|
+
if (file.includes(sid)) {
|
|
688
|
+
const ID_CHAR = /[A-Za-z0-9_-]/;
|
|
689
|
+
let from = 0;
|
|
690
|
+
while (true) {
|
|
691
|
+
const at = file.indexOf(sid, from);
|
|
692
|
+
if (at < 0) return false;
|
|
693
|
+
const before = at > 0 ? file[at - 1] : "";
|
|
694
|
+
const after = at + sid.length < file.length ? file[at + sid.length] : "";
|
|
695
|
+
if (!(before && ID_CHAR.test(before)) && !(after && ID_CHAR.test(after))) return true;
|
|
696
|
+
from = at + 1;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/handle-era-paths.js
|
|
703
|
+
var GENERATION_LOG_RE = /^session\.v\d+\.jsonl(\.zst(d)?)?$/;
|
|
704
|
+
function isSafeChar(ch) {
|
|
705
|
+
return ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch);
|
|
706
|
+
}
|
|
707
|
+
function projectKeyFor(cwd) {
|
|
708
|
+
const s = String(cwd);
|
|
709
|
+
if (s.length === 0) throw new Error("cannot encode an empty project path");
|
|
710
|
+
let readable = "";
|
|
711
|
+
let separatorRun = false;
|
|
712
|
+
for (let i = 0; i < s.length; i++) {
|
|
713
|
+
const ch = s[i];
|
|
714
|
+
if (ch === "/" || ch === "\\" || ch === ":") {
|
|
715
|
+
if (!separatorRun) readable += "-";
|
|
716
|
+
separatorRun = true;
|
|
717
|
+
} else if (isSafeChar(ch)) {
|
|
718
|
+
readable += ch;
|
|
719
|
+
separatorRun = false;
|
|
720
|
+
} else {
|
|
721
|
+
readable += "~" + s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
722
|
+
separatorRun = false;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return "--" + (readable.replace(/^-+/, "") || "root").slice(0, 251) + "--";
|
|
726
|
+
}
|
|
727
|
+
function encodeSegmentFor(raw) {
|
|
728
|
+
const s = String(raw);
|
|
729
|
+
if (s.length === 0) throw new Error("cannot encode an empty path segment");
|
|
730
|
+
if (s === ".") return "~002E";
|
|
731
|
+
if (s === "..") return "~002E~002E";
|
|
732
|
+
let out = "";
|
|
733
|
+
for (let i = 0; i < s.length; i++) {
|
|
734
|
+
const ch = s[i];
|
|
735
|
+
out += isSafeChar(ch) ? ch : "~" + s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
736
|
+
}
|
|
737
|
+
return out;
|
|
738
|
+
}
|
|
739
|
+
function resolveSessionRoot(sp) {
|
|
740
|
+
const root = sp && typeof sp === "object" ? sp.root : void 0;
|
|
741
|
+
return typeof root === "string" && root.length > 0 ? root : null;
|
|
742
|
+
}
|
|
743
|
+
function deriveSessionDir(root, cwd, id) {
|
|
744
|
+
const project = cwd === void 0 || cwd === null || cwd === "" ? join4(root, "_no-cwd") : join4(root, projectKeyFor(cwd));
|
|
745
|
+
return join4(project, encodeSegmentFor(id));
|
|
746
|
+
}
|
|
747
|
+
async function locateSessionArtifacts(sp, header) {
|
|
748
|
+
const root = resolveSessionRoot(sp);
|
|
749
|
+
if (!root || !header || header.id == null) return null;
|
|
750
|
+
const sid = String(header.id);
|
|
751
|
+
let sessionDir;
|
|
752
|
+
try {
|
|
753
|
+
sessionDir = deriveSessionDir(root, header.cwd, sid);
|
|
754
|
+
} catch (e) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
if (basename(sessionDir) !== encodeSegmentFor(sid)) return null;
|
|
758
|
+
let entries;
|
|
759
|
+
try {
|
|
760
|
+
const st = await stat(sessionDir);
|
|
761
|
+
if (!st.isDirectory()) return null;
|
|
762
|
+
entries = await readdir(sessionDir);
|
|
763
|
+
} catch (e) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
const generationFiles = entries.filter((name2) => GENERATION_LOG_RE.test(name2));
|
|
767
|
+
if (generationFiles.length === 0) return null;
|
|
768
|
+
generationFiles.sort((a, b) => {
|
|
769
|
+
const va = Number((a.match(/^session\.v(\d+)\./) || [])[1] || 0);
|
|
770
|
+
const vb = Number((b.match(/^session\.v(\d+)\./) || [])[1] || 0);
|
|
771
|
+
return vb - va;
|
|
772
|
+
});
|
|
773
|
+
const logPath = join4(sessionDir, generationFiles[0]);
|
|
774
|
+
if (!pathOwnsSession(logPath, sid)) return null;
|
|
775
|
+
return {
|
|
776
|
+
root,
|
|
777
|
+
projectDir: join4(root, header.cwd === void 0 || header.cwd === null || header.cwd === "" ? "_no-cwd" : projectKeyFor(header.cwd)),
|
|
778
|
+
sessionDir,
|
|
779
|
+
logPath,
|
|
780
|
+
generationFiles
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
669
784
|
// src/compat/persistence.js
|
|
670
785
|
var DEFAULT_CHUNK = 400;
|
|
671
786
|
var MAX_CHUNKS = 2e4;
|
|
@@ -859,124 +974,9 @@ function requireCapability(capabilities, name2) {
|
|
|
859
974
|
throw error;
|
|
860
975
|
}
|
|
861
976
|
|
|
862
|
-
// src/path-guard.js
|
|
863
|
-
function splitSegments(target) {
|
|
864
|
-
return String(target).replace(/[\\/]+$/, "").split(/[\\/]/).filter((seg) => seg.length > 0);
|
|
865
|
-
}
|
|
866
|
-
function stripDriveLetter(segments) {
|
|
867
|
-
return segments.length > 0 && /^[A-Za-z]:$/.test(segments[0]) ? segments.slice(1) : segments;
|
|
868
|
-
}
|
|
869
|
-
function pathOwnsSession(target, sid) {
|
|
870
|
-
if (typeof target !== "string" || target.length === 0) return false;
|
|
871
|
-
if (typeof sid !== "string" || sid.length === 0) return false;
|
|
872
|
-
const segments = stripDriveLetter(splitSegments(target));
|
|
873
|
-
if (segments.length === 0) return false;
|
|
874
|
-
const file = segments[segments.length - 1];
|
|
875
|
-
if (segments.length >= 2 && segments[segments.length - 2] === sid) return true;
|
|
876
|
-
if (file.includes(sid)) {
|
|
877
|
-
const ID_CHAR = /[A-Za-z0-9_-]/;
|
|
878
|
-
let from = 0;
|
|
879
|
-
while (true) {
|
|
880
|
-
const at = file.indexOf(sid, from);
|
|
881
|
-
if (at < 0) return false;
|
|
882
|
-
const before = at > 0 ? file[at - 1] : "";
|
|
883
|
-
const after = at + sid.length < file.length ? file[at + sid.length] : "";
|
|
884
|
-
if (!(before && ID_CHAR.test(before)) && !(after && ID_CHAR.test(after))) return true;
|
|
885
|
-
from = at + 1;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
return false;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
977
|
// src/handle-era-ops.js
|
|
892
978
|
import { mkdir as mkdir4, readFile as readFile2, rename as rename4, rm, unlink, writeFile as writeFile4 } from "node:fs/promises";
|
|
893
979
|
import { join as join5 } from "node:path";
|
|
894
|
-
|
|
895
|
-
// src/handle-era-paths.js
|
|
896
|
-
import { readdir, stat } from "node:fs/promises";
|
|
897
|
-
import { basename, join as join4 } from "node:path";
|
|
898
|
-
var GENERATION_LOG_RE = /^session\.v\d+\.jsonl(\.zst(d)?)?$/;
|
|
899
|
-
function isSafeChar(ch) {
|
|
900
|
-
return ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch);
|
|
901
|
-
}
|
|
902
|
-
function projectKeyFor(cwd) {
|
|
903
|
-
const s = String(cwd);
|
|
904
|
-
if (s.length === 0) throw new Error("cannot encode an empty project path");
|
|
905
|
-
let readable = "";
|
|
906
|
-
let separatorRun = false;
|
|
907
|
-
for (let i = 0; i < s.length; i++) {
|
|
908
|
-
const ch = s[i];
|
|
909
|
-
if (ch === "/" || ch === "\\" || ch === ":") {
|
|
910
|
-
if (!separatorRun) readable += "-";
|
|
911
|
-
separatorRun = true;
|
|
912
|
-
} else if (isSafeChar(ch)) {
|
|
913
|
-
readable += ch;
|
|
914
|
-
separatorRun = false;
|
|
915
|
-
} else {
|
|
916
|
-
readable += "~" + s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
917
|
-
separatorRun = false;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
return "--" + (readable.replace(/^-+/, "") || "root").slice(0, 251) + "--";
|
|
921
|
-
}
|
|
922
|
-
function encodeSegmentFor(raw) {
|
|
923
|
-
const s = String(raw);
|
|
924
|
-
if (s.length === 0) throw new Error("cannot encode an empty path segment");
|
|
925
|
-
if (s === ".") return "~002E";
|
|
926
|
-
if (s === "..") return "~002E~002E";
|
|
927
|
-
let out = "";
|
|
928
|
-
for (let i = 0; i < s.length; i++) {
|
|
929
|
-
const ch = s[i];
|
|
930
|
-
out += isSafeChar(ch) ? ch : "~" + s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
931
|
-
}
|
|
932
|
-
return out;
|
|
933
|
-
}
|
|
934
|
-
function resolveSessionRoot(sp) {
|
|
935
|
-
const root = sp && typeof sp === "object" ? sp.root : void 0;
|
|
936
|
-
return typeof root === "string" && root.length > 0 ? root : null;
|
|
937
|
-
}
|
|
938
|
-
function deriveSessionDir(root, cwd, id) {
|
|
939
|
-
const project = cwd === void 0 || cwd === null || cwd === "" ? join4(root, "_no-cwd") : join4(root, projectKeyFor(cwd));
|
|
940
|
-
return join4(project, encodeSegmentFor(id));
|
|
941
|
-
}
|
|
942
|
-
async function locateSessionArtifacts2(sp, header) {
|
|
943
|
-
const root = resolveSessionRoot(sp);
|
|
944
|
-
if (!root || !header || header.id == null) return null;
|
|
945
|
-
const sid = String(header.id);
|
|
946
|
-
let sessionDir;
|
|
947
|
-
try {
|
|
948
|
-
sessionDir = deriveSessionDir(root, header.cwd, sid);
|
|
949
|
-
} catch (e) {
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
if (basename(sessionDir) !== encodeSegmentFor(sid)) return null;
|
|
953
|
-
let entries;
|
|
954
|
-
try {
|
|
955
|
-
const st = await stat(sessionDir);
|
|
956
|
-
if (!st.isDirectory()) return null;
|
|
957
|
-
entries = await readdir(sessionDir);
|
|
958
|
-
} catch (e) {
|
|
959
|
-
return null;
|
|
960
|
-
}
|
|
961
|
-
const generationFiles = entries.filter((name2) => GENERATION_LOG_RE.test(name2));
|
|
962
|
-
if (generationFiles.length === 0) return null;
|
|
963
|
-
generationFiles.sort((a, b) => {
|
|
964
|
-
const va = Number((a.match(/^session\.v(\d+)\./) || [])[1] || 0);
|
|
965
|
-
const vb = Number((b.match(/^session\.v(\d+)\./) || [])[1] || 0);
|
|
966
|
-
return vb - va;
|
|
967
|
-
});
|
|
968
|
-
const logPath = join4(sessionDir, generationFiles[0]);
|
|
969
|
-
if (!pathOwnsSession(logPath, sid)) return null;
|
|
970
|
-
return {
|
|
971
|
-
root,
|
|
972
|
-
projectDir: join4(root, header.cwd === void 0 || header.cwd === null || header.cwd === "" ? "_no-cwd" : projectKeyFor(header.cwd)),
|
|
973
|
-
sessionDir,
|
|
974
|
-
logPath,
|
|
975
|
-
generationFiles
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
// src/handle-era-ops.js
|
|
980
980
|
var MOVE_BATCH = 400;
|
|
981
981
|
function conflictError(message) {
|
|
982
982
|
const error = new Error(message);
|
|
@@ -1010,7 +1010,7 @@ async function ensureNoActiveWriter(sp, sid) {
|
|
|
1010
1010
|
await closeQuietly2(handle);
|
|
1011
1011
|
}
|
|
1012
1012
|
async function purgeSessionArtifacts(sp, sid, header) {
|
|
1013
|
-
const artifacts = await
|
|
1013
|
+
const artifacts = await locateSessionArtifacts(sp, header);
|
|
1014
1014
|
if (!artifacts) {
|
|
1015
1015
|
const error = new Error("\u65E0\u6CD5\u5B9A\u4F4D\u8BE5\u4F1A\u8BDD\u7684\u7269\u7406\u65E5\u5FD7\u76EE\u5F55\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664");
|
|
1016
1016
|
error.status = 409;
|
|
@@ -1060,7 +1060,7 @@ async function relocateRewrittenBackup({ sid, canonical, backupPath, artifacts }
|
|
|
1060
1060
|
return targetDir;
|
|
1061
1061
|
}
|
|
1062
1062
|
async function moveSessionToCwd({ sp, sid, header, canonical, events = [], inheritedEventCount = 0 }) {
|
|
1063
|
-
const artifacts = await
|
|
1063
|
+
const artifacts = await locateSessionArtifacts(sp, header);
|
|
1064
1064
|
if (!artifacts) {
|
|
1065
1065
|
const error = new Error("\u65E0\u6CD5\u5B9A\u4F4D\u8BE5\u4F1A\u8BDD\u7684\u7269\u7406\u65E5\u5FD7\uFF0C\u5DF2\u505C\u6B62\u79FB\u52A8");
|
|
1066
1066
|
error.status = 409;
|
|
@@ -1645,13 +1645,26 @@ function apply(ctx) {
|
|
|
1645
1645
|
throw error;
|
|
1646
1646
|
}
|
|
1647
1647
|
let target = null;
|
|
1648
|
+
let locatedHeader = null;
|
|
1648
1649
|
try {
|
|
1649
1650
|
const entries = await persistence.listEntries();
|
|
1650
1651
|
const current = entries.find((entry2) => entry2.id === sid);
|
|
1651
|
-
|
|
1652
|
+
locatedHeader = current ? current.header : null;
|
|
1653
|
+
const located = current ? await persistence.locateVerified(current.header) : null;
|
|
1652
1654
|
if (located && typeof located.path === "string") target = located.path;
|
|
1653
1655
|
} catch (e) {
|
|
1654
1656
|
}
|
|
1657
|
+
if (!locatedHeader && typeof persistence.statSession === "function") {
|
|
1658
|
+
try {
|
|
1659
|
+
const snap = await persistence.statSession(sid);
|
|
1660
|
+
if (snap && snap.header) {
|
|
1661
|
+
locatedHeader = snap.header;
|
|
1662
|
+
const located = await persistence.locateVerified(snap.header);
|
|
1663
|
+
if (located && typeof located.path === "string") target = located.path;
|
|
1664
|
+
}
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1655
1668
|
if (!target && typeof entry.originalPath === "string") target = entry.originalPath;
|
|
1656
1669
|
if (!target) {
|
|
1657
1670
|
const error = new Error("\u65E0\u6CD5\u786E\u8BA4\u8BE5\u4F1A\u8BDD\u7684\u7269\u7406\u65E5\u5FD7\u4F4D\u7F6E\uFF0C\u5DF2\u505C\u6B62\u6C38\u4E45\u5220\u9664");
|