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/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 locateSessionArtifacts2(sp, header);
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 locateSessionArtifacts2(sp, header);
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
- const located = current && persistence.locate(current.header);
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");