@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/CHANGELOG.md +12 -0
- package/CHANGELOG.zh-CN.md +12 -0
- package/README.md +7 -1
- package/README.ru.md +7 -1
- package/README.zh-CN.md +7 -1
- package/lib/archive-organizer.js +3 -6
- package/lib/client.js +4 -3
- package/lib/host-install.js +3 -3
- package/lib/projcache.js +125 -163
- package/lib/session-repair.js +41 -5
- package/lib/workspace.js +19 -173
- package/package.json +1 -1
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
|
-
|
|
812
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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",
|