@michengai/dsh-archive-manager 1.1.6 → 1.1.7

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/workspace.js CHANGED
@@ -306,20 +306,9 @@ async function hostRepairFormat(format, children = []) {
306
306
  return bound.createRestore(header, { recovery: "recoverable", validation: "transformed" });
307
307
  } };
308
308
  }
309
- let activeSessionError;
310
- async function activeSessionErrorType() {
311
- if (activeSessionError !== void 0) return activeSessionError ?? void 0;
312
- const workspace = await import("@deepseek-ai/dsh-workspace");
313
- activeSessionError = typeof workspace.WorkspaceActiveSessionError === "function" ? workspace.WorkspaceActiveSessionError : null;
314
- return activeSessionError ?? void 0;
315
- }
316
309
  function projectionSnapshot(cache, header) {
317
310
  return cache.cachedSnapshot(header);
318
311
  }
319
- function hostEvent(ctx, name) {
320
- const caller = ctx[name];
321
- return typeof caller === "function" ? caller : void 0;
322
- }
323
312
  const fileUploadResolverRestart = Symbol.for("dsh-archive-manager.file-upload-resolver-restart");
324
313
  const staleFileUploadResolver = "file-upload: Agent resolver is already registered";
325
314
  function tolerateStaleFileUploadResolver(ctx) {
@@ -353,12 +342,9 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
353
342
  deletedSessionTombstoneLimit = 4096;
354
343
  /** 被删生命周期的日志身份(createdAt/cwd):冷复用探针区分“同 id 新会话”与 stale list() 的依据。 */
355
344
  deletedIdentities = /* @__PURE__ */ new Map();
356
- archivedSessionPathIndex = /* @__PURE__ */ new Map();
357
- archivedSessionPathIndexKey;
358
345
  constructor(ctx) {
359
346
  super(ctx);
360
347
  tolerateStaleFileUploadResolver(ctx);
361
- protectArchivedSessionPath(this);
362
348
  this.typertRemote = bindTypertRemote(this, this.name);
363
349
  for (const method of ARCHIVE_REMOTE_METHODS) markRemoteMethod(this, method);
364
350
  registerHostRemote(this.ctx);
@@ -567,26 +553,6 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
567
553
  return next;
568
554
  });
569
555
  }
570
- /** 归档集合未变时复用反查表,避免每次 sessionPath 都扫全部工作区记账。 */
571
- archivedWorkspacePath(sessionId) {
572
- const state = this.requireState();
573
- if (!state.archivedSessionIds.includes(sessionId)) return void 0;
574
- if (this.archivedSessionPathIndexKey !== state.archivedSessionIds) {
575
- const index = /* @__PURE__ */ new Map();
576
- const archived = new Set(state.archivedSessionIds);
577
- const table = this.requireTable();
578
- for (const workspaceId of state.workspaceIds) {
579
- const record2 = table.get(workspaceId);
580
- if (record2 === void 0) continue;
581
- for (const id of record2.sessionIds) {
582
- if (archived.has(id)) index.set(id, record2.path);
583
- }
584
- }
585
- this.archivedSessionPathIndex = index;
586
- this.archivedSessionPathIndexKey = state.archivedSessionIds;
587
- }
588
- return this.archivedSessionPathIndex.get(sessionId);
589
- }
590
556
  /**
591
557
  * 只有 persistence.stat 明确说文件不在时才从持久集合摘掉。
592
558
  * list/header 读不出不等于文件没了;没有 stat 时宁可不落盘删除。
@@ -754,98 +720,10 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
754
720
  await handle.close();
755
721
  }
756
722
  }
757
- /**
758
- * 把一个已知会话加入注册表全局归档集合。已归档的 id 不写入;
759
- * 未知 id 直接跳过,不抛 `UNKNOWN_SESSION`,避免把幽灵 id 写进集合。
760
- * @param sessionId - 要归档的会话。
761
- */
762
- async archiveSession(sessionId, options) {
763
- return this.enqueueOperation(async () => {
764
- if (this.requireState().archivedSessionIds.includes(sessionId)) return;
765
- if (!await this.sessionKnown(sessionId)) return;
766
- const stopActivity = options?.stopActivity === true;
767
- const ActiveSession = await activeSessionErrorType();
768
- if (!stopActivity && ActiveSession !== void 0) {
769
- const activity = await this.sessionActivity(sessionId);
770
- if (activity.length > 0) throw new ActiveSession(sessionId, activity);
771
- }
772
- const state = this.requireState();
773
- const pinnedSessionIds = Array.isArray(state.pinnedSessionIds) ? state.pinnedSessionIds.filter((id) => id !== sessionId) : void 0;
774
- await this.setState({
775
- ...state,
776
- archivedSessionIds: [...state.archivedSessionIds, sessionId],
777
- ...pinnedSessionIds === void 0 ? {} : { pinnedSessionIds }
778
- });
779
- if (stopActivity && ActiveSession !== void 0) await this.stopSessionActivity(sessionId);
780
- });
781
- }
782
- /** 宿主用 waterfall 报告仍在运行的回合、子代理、任务和提醒。 */
783
- async sessionActivity(sessionId) {
784
- const waterfall = hostEvent(this.ctx, "waterfall");
785
- if (waterfall === void 0) return [];
786
- const activity = await waterfall.call(this.ctx, "workspace/session-activity", { sessionId }, () => Promise.resolve([]));
787
- return Array.isArray(activity) ? activity : [];
788
- }
789
- /** 归档已经写入后再请求停止;提供方失败只记日志,不撤回归档。 */
790
- async stopSessionActivity(sessionId) {
791
- const parallel = hostEvent(this.ctx, "parallel");
792
- if (parallel === void 0) return;
793
- try {
794
- await parallel.call(this.ctx, "workspace/session-stop", { sessionId });
795
- } catch (error) {
796
- const failures = error instanceof AggregateError ? error.errors : [error];
797
- for (const failure of failures) {
798
- this.ctx.logger.warn(`archive-manager: stopping session "${sessionId}" for archive failed: ${String(failure)}`);
799
- }
800
- }
801
- }
802
- /**
803
- * 把一个会话移出注册表全局归档集合,恢复其正常可见性(其记账位从未
804
- * 移动,会话在原工作区位置重新出现)。与官方一致:不做会话存在性检查,
805
- * 因为从集合摘掉 id 不会引入未知引用。孤儿归档标记也会被清掉;
806
- * 未归档的 id(含不存在的 id)不写入即返回。
807
- * @param sessionId - 要取消归档的会话。
808
- * @returns 更新后的完整归档集合。
809
- */
723
+ /** 官方负责移出归档集合。这里只补远程接口需要的当前集合,不补工作区记账。 */
810
724
  async unarchiveSession(sessionId) {
811
- return this.enqueueOperation(async () => {
812
- const state = this.requireState();
813
- if (!state.archivedSessionIds.includes(sessionId))
814
- return { archivedSessionIds: [...state.archivedSessionIds] };
815
- const next = {
816
- ...state,
817
- archivedSessionIds: state.archivedSessionIds.filter(
818
- (id) => id !== sessionId
819
- )
820
- };
821
- await this.setState(next);
822
- await this.restoreWorkspaceMembership(sessionId);
823
- return { archivedSessionIds: [...next.archivedSessionIds] };
824
- });
825
- }
826
- /**
827
- * 恢复后重新编入路径索引;若会话已不在任何工作区记账但 cwd 仍对应某工作区,
828
- * 则挂回去,避免「恢复并打开」时官方成员过滤把会话从原工作区抹掉。
829
- */
830
- async restoreWorkspaceMembership(sessionId) {
831
- let header;
832
- try {
833
- header = await this.readSessionHeader(sessionId);
834
- await this.indexHeader(header);
835
- } catch {
836
- return;
837
- }
838
- const path = this.sessionPaths.get(sessionId);
839
- if (path === void 0) return;
840
- const table = this.requireTable();
841
- for (const workspaceId of this.requireState().workspaceIds) {
842
- const record2 = table.get(workspaceId);
843
- if (record2 === void 0 || record2.path !== path) continue;
844
- if (record2.sessionIds.includes(sessionId)) return;
845
- const entity = this.entities.get(workspaceId);
846
- if (entity !== void 0) await entity.attachSession(sessionId);
847
- return;
848
- }
725
+ await super.unarchiveSession(sessionId);
726
+ return { archivedSessionIds: [...this.requireState().archivedSessionIds] };
849
727
  }
850
728
  /**
851
729
  * 按作用域永久删除归档会话。跨会话文件删除无法组成事务,因此继续处理
@@ -1062,13 +940,6 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
1062
940
  if (this.deletedSessionIds.has(header.id)) return;
1063
941
  return super.indexHeader(header);
1064
942
  }
1065
- /** 持久化列表返回快照,这里只取出头部。 */
1066
- async listStoredHeaders() {
1067
- return (await this.ctx.sessionPersistence.list()).map((item) => item.header);
1068
- }
1069
- async indexHeaders(items) {
1070
- for (const item of items) await this.indexHeader(item);
1071
- }
1072
943
  /** 为未处于实时状态的持久化会话发布相同的移除事件。 */
1073
944
  async publishColdSessionRemoval(sessionId, sessions) {
1074
945
  const persistence = this.ctx.get("sessionPersistence");
@@ -1256,40 +1127,6 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
1256
1127
  }
1257
1128
  };
1258
1129
  const workspaceOverlay = Symbol.for("dsh-archive-manager.workspace-overlay");
1259
- function protectArchivedSessionPath(registry) {
1260
- const host = registry.host;
1261
- if (typeof host.candidateMatches === "function") {
1262
- const original = host.candidateMatches;
1263
- host.candidateMatches = (id, path) => {
1264
- if (original.call(host, id, path)) return true;
1265
- if (registry.invalidSessionPaths.has(id)) return false;
1266
- try {
1267
- return registry.archivedWorkspacePath(id) === path;
1268
- } catch {
1269
- return false;
1270
- }
1271
- };
1272
- return () => {
1273
- host.candidateMatches = original;
1274
- };
1275
- }
1276
- if (typeof host.sessionPath !== "function") return () => {
1277
- };
1278
- const indexedPath = host.sessionPath;
1279
- host.sessionPath = (id) => {
1280
- const path = indexedPath.call(host, id);
1281
- if (path !== void 0) return path;
1282
- if (registry.invalidSessionPaths.has(id)) return void 0;
1283
- try {
1284
- return registry.archivedWorkspacePath(id);
1285
- } catch {
1286
- return void 0;
1287
- }
1288
- };
1289
- return () => {
1290
- host.sessionPath = indexedPath;
1291
- };
1292
- }
1293
1130
  const remoteMethodDescriptor = "@deepseek-ai/dsh-typert-protocol/remote-methods";
1294
1131
  function markOverlayMethods(registry, methods) {
1295
1132
  const raw = Reflect.get(registry, symbols.original) ?? registry;
@@ -1332,9 +1169,7 @@ function installArchiveWorkspace(registry) {
1332
1169
  deletedSessionIds: /* @__PURE__ */ new Set(),
1333
1170
  deletedSessionOrder: [],
1334
1171
  deletedSessionTombstoneLimit: 4096,
1335
- deletedIdentities: /* @__PURE__ */ new Map(),
1336
- archivedSessionPathIndex: /* @__PURE__ */ new Map(),
1337
- archivedSessionPathIndexKey: void 0
1172
+ deletedIdentities: /* @__PURE__ */ new Map()
1338
1173
  };
1339
1174
  const addedFields = [];
1340
1175
  for (const [key, value] of Object.entries(fields)) {
@@ -1343,8 +1178,9 @@ function installArchiveWorkspace(registry) {
1343
1178
  addedFields.push(key);
1344
1179
  }
1345
1180
  }
1346
- const restorePath = protectArchivedSessionPath(overlay);
1347
1181
  const restoreRemote = bindOverlayRemote(registry);
1182
+ const originalUnarchive = target.unarchiveSession;
1183
+ const unarchiveDescriptor = Object.getOwnPropertyDescriptor(target, "unarchiveSession");
1348
1184
  const restoredMethods = [];
1349
1185
  const copiedMethods = [];
1350
1186
  for (const name of Object.getOwnPropertyNames(ArchiveWorkspaceRegistry.prototype)) {
@@ -1352,18 +1188,28 @@ function installArchiveWorkspace(registry) {
1352
1188
  const method = Reflect.get(ArchiveWorkspaceRegistry.prototype, name);
1353
1189
  if (typeof method !== "function") continue;
1354
1190
  restoredMethods.push({ name, hadOwn: Object.hasOwn(target, name), previous: target[name] });
1355
- target[name] = method;
1191
+ if (name === "unarchiveSession") {
1192
+ Object.defineProperty(target, name, {
1193
+ configurable: unarchiveDescriptor?.configurable ?? true,
1194
+ enumerable: unarchiveDescriptor?.enumerable ?? true,
1195
+ writable: true,
1196
+ value: async function(sessionId) {
1197
+ await originalUnarchive.call(this, sessionId);
1198
+ return { archivedSessionIds: [...this.requireState().archivedSessionIds] };
1199
+ }
1200
+ });
1201
+ } else target[name] = method;
1356
1202
  copiedMethods.push(name);
1357
1203
  }
1358
1204
  const restoreMarkers = markOverlayMethods(registry, ARCHIVE_REMOTE_METHODS.filter((method) => copiedMethods.includes(method)));
1359
1205
  target[workspaceOverlay] = true;
1360
1206
  return () => {
1361
1207
  if (target[workspaceOverlay] !== true) return;
1362
- restorePath();
1363
1208
  restoreMarkers();
1364
1209
  restoreRemote();
1365
1210
  for (const item of restoredMethods.reverse()) {
1366
- if (item.hadOwn) target[item.name] = item.previous;
1211
+ if (item.name === "unarchiveSession" && unarchiveDescriptor !== void 0) Object.defineProperty(target, item.name, unarchiveDescriptor);
1212
+ else if (item.hadOwn) target[item.name] = item.previous;
1367
1213
  else delete target[item.name];
1368
1214
  }
1369
1215
  for (const key of addedFields) delete target[key];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "归档、恢复、收藏和永久删除会话,并诊断损坏的会话日志。 · Archive, restore, favorite, and permanently delete sessions, and diagnose damaged session logs.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",