@michengai/dsh-archive-manager 0.1.42 → 0.1.44

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
@@ -30,6 +30,14 @@ function jsonlSessionDirectory(persistence, header, location) {
30
30
  if (location.path !== join(directory, basename(location.path))) return;
31
31
  return directory;
32
32
  }
33
+ function strictCodec(typeSymbol, schema) {
34
+ return {
35
+ mode: "strict",
36
+ typeSymbol,
37
+ create: () => schema,
38
+ schema
39
+ };
40
+ }
33
41
  function markRemoteMethod(instance, method) {
34
42
  const context = {
35
43
  private: false,
@@ -41,17 +49,6 @@ function markRemoteMethod(instance, method) {
41
49
  };
42
50
  Remote(method)(void 0, context);
43
51
  }
44
- function unknownSessionMessage(sessionId) {
45
- return `unknown session "${sessionId}" (UNKNOWN_SESSION)`;
46
- }
47
- var ArchiveUnknownSessionError = class extends Error {
48
- sessionId;
49
- constructor(sessionId) {
50
- super(unknownSessionMessage(sessionId));
51
- this.sessionId = sessionId;
52
- this.name = "ArchiveUnknownSessionError";
53
- }
54
- };
55
52
  function headerIdentity(header) {
56
53
  return {
57
54
  createdAt: header.createdAt,
@@ -67,15 +64,6 @@ const sessionIdSchema = {
67
64
  return value;
68
65
  }
69
66
  };
70
- const workspaceIdSchema = {
71
- parse(value) {
72
- if (typeof value !== "string" || value.length === 0)
73
- throw new TypeError(
74
- `workspaceId must be a non-empty string, got ${String(value)}`
75
- );
76
- return value;
77
- }
78
- };
79
67
  const archivedSetSchema = {
80
68
  parse(value) {
81
69
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -107,34 +95,6 @@ const archivedBatchTargetSchema = {
107
95
  );
108
96
  }
109
97
  };
110
- const unarchivedBatchSchema = {
111
- parse(value) {
112
- if (typeof value !== "object" || value === null || Array.isArray(value))
113
- throw new TypeError("result must be an object");
114
- if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string"))
115
- throw new TypeError("archivedSessionIds must be a string array");
116
- if (!Array.isArray(value.unarchivedSessionIds) || value.unarchivedSessionIds.some((id) => typeof id !== "string"))
117
- throw new TypeError("unarchivedSessionIds must be a string array");
118
- return value;
119
- }
120
- };
121
- const archiveSessionIdsSchema = {
122
- parse(value) {
123
- if (!Array.isArray(value)) throw new TypeError("sessionIds must be an array");
124
- return [...new Set(value.map((id) => sessionIdSchema.parse(id)))];
125
- }
126
- };
127
- const archivedWorkspaceBatchSchema = {
128
- parse(value) {
129
- if (typeof value !== "object" || value === null || Array.isArray(value))
130
- throw new TypeError("result must be an object");
131
- if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string"))
132
- throw new TypeError("archivedSessionIds must be a string array");
133
- if (!Array.isArray(value.archivedSessionIdsAdded) || value.archivedSessionIdsAdded.some((id) => typeof id !== "string"))
134
- throw new TypeError("archivedSessionIdsAdded must be a string array");
135
- return value;
136
- }
137
- };
138
98
  const deletedBatchSchema = {
139
99
  parse(value) {
140
100
  if (typeof value !== "object" || value === null || Array.isArray(value))
@@ -179,18 +139,10 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
179
139
  name: "sessionId",
180
140
  wire: "sessionId",
181
141
  source: "json",
182
- codec: {
183
- mode: "strict",
184
- typeSymbol: "@deepseek-ai/dsh-session/types#SessionId",
185
- schema: sessionIdSchema
186
- }
142
+ codec: strictCodec("@deepseek-ai/dsh-session/types#SessionId", sessionIdSchema)
187
143
  }
188
144
  ],
189
- result: {
190
- mode: "strict",
191
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionIds",
192
- schema: archivedSetSchema
193
- },
145
+ result: strictCodec("@michengai/dsh-archive-manager/types#ArchivedSessionIds", archivedSetSchema),
194
146
  sourceLocation: {
195
147
  file: "@michengai/dsh-archive-manager/lib/workspace.js",
196
148
  line: 1,
@@ -208,105 +160,10 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
208
160
  name: "sessionId",
209
161
  wire: "sessionId",
210
162
  source: "json",
211
- codec: {
212
- mode: "strict",
213
- typeSymbol: "@deepseek-ai/dsh-session/types#SessionId",
214
- schema: sessionIdSchema
215
- }
163
+ codec: strictCodec("@deepseek-ai/dsh-session/types#SessionId", sessionIdSchema)
216
164
  }
217
165
  ],
218
- result: {
219
- mode: "strict",
220
- typeSymbol: "@michengai/dsh-archive-manager/types#Deleted",
221
- schema: deletedSchema
222
- },
223
- sourceLocation: {
224
- file: "@michengai/dsh-archive-manager/lib/workspace.js",
225
- line: 1,
226
- column: 1
227
- }
228
- },
229
- {
230
- id: "@michengai/dsh-archive-manager#workspaceRegistry/unarchiveSessions",
231
- service: "workspaceRegistry",
232
- namespace: "workspaceRegistry",
233
- method: "unarchiveSessions",
234
- invocation: { kind: "direct" },
235
- parameters: [
236
- {
237
- name: "target",
238
- wire: "target",
239
- source: "json",
240
- codec: {
241
- mode: "strict",
242
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget",
243
- schema: archivedBatchTargetSchema
244
- }
245
- }
246
- ],
247
- result: {
248
- mode: "strict",
249
- typeSymbol: "@michengai/dsh-archive-manager/types#UnarchivedBatch",
250
- schema: unarchivedBatchSchema
251
- },
252
- sourceLocation: {
253
- file: "@michengai/dsh-archive-manager/lib/workspace.js",
254
- line: 1,
255
- column: 1
256
- }
257
- },
258
- {
259
- id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveSessions",
260
- service: "workspaceRegistry",
261
- namespace: "workspaceRegistry",
262
- method: "archiveSessions",
263
- invocation: { kind: "direct" },
264
- parameters: [
265
- {
266
- name: "sessionIds",
267
- wire: "sessionIds",
268
- source: "json",
269
- codec: {
270
- mode: "strict",
271
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchiveSessionIds",
272
- schema: archiveSessionIdsSchema
273
- }
274
- }
275
- ],
276
- result: {
277
- mode: "strict",
278
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
279
- schema: archivedWorkspaceBatchSchema
280
- },
281
- sourceLocation: {
282
- file: "@michengai/dsh-archive-manager/lib/workspace.js",
283
- line: 1,
284
- column: 1
285
- }
286
- },
287
- {
288
- id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveWorkspaceSessions",
289
- service: "workspaceRegistry",
290
- namespace: "workspaceRegistry",
291
- method: "archiveWorkspaceSessions",
292
- invocation: { kind: "direct" },
293
- parameters: [
294
- {
295
- name: "workspaceId",
296
- wire: "workspaceId",
297
- source: "json",
298
- codec: {
299
- mode: "strict",
300
- typeSymbol: "@deepseek-ai/dsh-workspace/types#WorkspaceId",
301
- schema: workspaceIdSchema
302
- }
303
- }
304
- ],
305
- result: {
306
- mode: "strict",
307
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
308
- schema: archivedWorkspaceBatchSchema
309
- },
166
+ result: strictCodec("@michengai/dsh-archive-manager/types#Deleted", deletedSchema),
310
167
  sourceLocation: {
311
168
  file: "@michengai/dsh-archive-manager/lib/workspace.js",
312
169
  line: 1,
@@ -324,18 +181,10 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
324
181
  name: "target",
325
182
  wire: "target",
326
183
  source: "json",
327
- codec: {
328
- mode: "strict",
329
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget",
330
- schema: archivedBatchTargetSchema
331
- }
184
+ codec: strictCodec("@michengai/dsh-archive-manager/types#ArchivedBatchTarget", archivedBatchTargetSchema)
332
185
  }
333
186
  ],
334
- result: {
335
- mode: "strict",
336
- typeSymbol: "@michengai/dsh-archive-manager/types#DeletedBatch",
337
- schema: deletedBatchSchema
338
- },
187
+ result: strictCodec("@michengai/dsh-archive-manager/types#DeletedBatch", deletedBatchSchema),
339
188
  sourceLocation: {
340
189
  file: "@michengai/dsh-archive-manager/lib/workspace.js",
341
190
  line: 1,
@@ -349,11 +198,7 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
349
198
  method: "archivedSessionMetadata",
350
199
  invocation: { kind: "direct" },
351
200
  parameters: [],
352
- result: {
353
- mode: "strict",
354
- typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionMetadata",
355
- schema: archivedSessionMetadataSchema
356
- },
201
+ result: strictCodec("@michengai/dsh-archive-manager/types#ArchivedSessionMetadata", archivedSessionMetadataSchema),
357
202
  sourceLocation: {
358
203
  file: "@michengai/dsh-archive-manager/lib/workspace.js",
359
204
  line: 1,
@@ -393,29 +238,113 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
393
238
  deletedSessionTombstoneLimit = 4096;
394
239
  /** 被删生命周期的日志身份(createdAt/cwd):冷复用探针区分“同 id 新会话”与 stale list() 的依据。 */
395
240
  deletedIdentities = /* @__PURE__ */ new Map();
241
+ archivedSessionPathIndex = /* @__PURE__ */ new Map();
242
+ archivedSessionPathIndexKey;
396
243
  constructor(ctx) {
397
244
  super(ctx);
245
+ const indexedPath = this.host.sessionPath;
246
+ this.host.sessionPath = (id) => {
247
+ const path = indexedPath(id);
248
+ if (path !== void 0) return path;
249
+ if (this.invalidSessionPaths.has(id)) return void 0;
250
+ try {
251
+ return this.archivedWorkspacePath(id);
252
+ } catch {
253
+ return void 0;
254
+ }
255
+ };
398
256
  this.typertRemote = bindTypertRemote(this, this.name);
399
257
  markRemoteMethod(this, "unarchiveSession");
400
258
  markRemoteMethod(this, "deleteSession");
401
- markRemoteMethod(this, "unarchiveSessions");
402
- markRemoteMethod(this, "archiveWorkspaceSessions");
403
- markRemoteMethod(this, "archiveSessions");
404
259
  markRemoteMethod(this, "deleteArchivedSessions");
405
260
  markRemoteMethod(this, "archivedSessionMetadata");
406
261
  registerHostRemote(this.ctx);
407
262
  }
263
+ /** 归档集合未变时复用反查表,避免每次 sessionPath 都扫全部工作区记账。 */
264
+ archivedWorkspacePath(sessionId) {
265
+ const state = this.requireState();
266
+ if (!state.archivedSessionIds.includes(sessionId)) return void 0;
267
+ if (this.archivedSessionPathIndexKey !== state.archivedSessionIds) {
268
+ const index = /* @__PURE__ */ new Map();
269
+ const archived = new Set(state.archivedSessionIds);
270
+ const table = this.requireTable();
271
+ for (const workspaceId of state.workspaceIds) {
272
+ const record = table.get(workspaceId);
273
+ if (record === void 0) continue;
274
+ for (const id of record.sessionIds) {
275
+ if (archived.has(id)) index.set(id, record.path);
276
+ }
277
+ }
278
+ this.archivedSessionPathIndex = index;
279
+ this.archivedSessionPathIndexKey = state.archivedSessionIds;
280
+ }
281
+ return this.archivedSessionPathIndex.get(sessionId);
282
+ }
283
+ /**
284
+ * 只有 persistence.stat 明确说文件不在时才从持久集合摘掉。
285
+ * list/header 读不出不等于文件没了;没有 stat 时宁可不落盘删除。
286
+ */
287
+ async sessionArtifactMissing(sessionId) {
288
+ const persistence = this.ctx.get("sessionPersistence");
289
+ if (typeof persistence?.stat !== "function") return false;
290
+ try {
291
+ return await persistence.stat(sessionId) === void 0;
292
+ } catch (error) {
293
+ this.ctx.logger.warn(
294
+ `archive-manager: could not stat archived session "${sessionId}": ${String(error)}`
295
+ );
296
+ return false;
297
+ }
298
+ }
299
+ /**
300
+ * 从归档集合摘掉已经确认不存在的会话。打开归档列表时调用。
301
+ * 读不出头部的标记留在持久集合里,只从本次元数据结果里隐藏。
302
+ */
303
+ async pruneUnknownArchivedSessionIds() {
304
+ return this.enqueueOperation(async () => {
305
+ const state = this.requireState();
306
+ const listed = /* @__PURE__ */ new Set();
307
+ for (const item of await this.listStoredHeaders()) {
308
+ const header = item.header ?? item;
309
+ if (typeof header?.id === "string") listed.add(header.id);
310
+ }
311
+ const sessions = this.ctx.get("sessions");
312
+ const kept = [];
313
+ const seen = /* @__PURE__ */ new Set();
314
+ for (const sessionId of state.archivedSessionIds) {
315
+ if (seen.has(sessionId)) continue;
316
+ seen.add(sessionId);
317
+ if (sessions?.get(sessionId) !== void 0) {
318
+ kept.push(sessionId);
319
+ continue;
320
+ }
321
+ if (this.deletedSessionIds.has(sessionId)) {
322
+ if (await this.coldReuseKnown(sessionId)) kept.push(sessionId);
323
+ continue;
324
+ }
325
+ if (this.headers.has(sessionId) || listed.has(sessionId)) {
326
+ kept.push(sessionId);
327
+ continue;
328
+ }
329
+ if (!await this.sessionArtifactMissing(sessionId)) kept.push(sessionId);
330
+ }
331
+ if (kept.length === state.archivedSessionIds.length && kept.every((id, index) => id === state.archivedSessionIds[index]))
332
+ return kept;
333
+ await this.setState({ ...state, archivedSessionIds: kept });
334
+ return kept;
335
+ });
336
+ }
408
337
  /**
409
338
  * 归档设置页创建时间排序所需的最小元数据。老用户可能仍有会话原文和
410
339
  * 归档标记、却没有投影缓存;这里按需从完整日志重建一次,再通知客户端
411
340
  * 刷新会话列表。已有缓存不读原文,新老 DSH 的缓存布局都走同一 put。
341
+ * 读取前只清掉文件已确认不存在的归档标记。
412
342
  */
413
343
  async archivedSessionMetadata() {
344
+ await this.pruneUnknownArchivedSessionIds();
414
345
  const items = [];
415
346
  const repairedSessionIds = [];
416
- for (const sessionId of [
417
- ...new Set(this.requireState().archivedSessionIds)
418
- ]) {
347
+ for (const sessionId of this.requireState().archivedSessionIds) {
419
348
  try {
420
349
  const header = await this.readSessionHeader(sessionId);
421
350
  if (await this.repairArchivedProjection(header))
@@ -506,17 +435,32 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
506
435
  await handle.close();
507
436
  }
508
437
  }
438
+ /**
439
+ * 把一个已知会话加入注册表全局归档集合。已归档的 id 不写入;
440
+ * 未知 id 直接跳过,不抛 `UNKNOWN_SESSION`,避免把幽灵 id 写进集合。
441
+ * @param sessionId - 要归档的会话。
442
+ */
443
+ async archiveSession(sessionId) {
444
+ return this.enqueueOperation(async () => {
445
+ if (this.requireState().archivedSessionIds.includes(sessionId)) return;
446
+ if (!await this.sessionKnown(sessionId)) return;
447
+ const state = this.requireState();
448
+ await this.setState({
449
+ ...state,
450
+ archivedSessionIds: [...state.archivedSessionIds, sessionId]
451
+ });
452
+ });
453
+ }
509
454
  /**
510
455
  * 把一个会话移出注册表全局归档集合,恢复其正常可见性(其记账位从未
511
- * 移动,会话在原工作区位置重新出现)。幂等:未归档的已知会话直接返回
512
- * 当前集合不写入;未知会话与 `archiveSession` 一样抛错。
456
+ * 移动,会话在原工作区位置重新出现)。与官方一致:不做会话存在性检查,
457
+ * 因为从集合摘掉 id 不会引入未知引用。孤儿归档标记也会被清掉;
458
+ * 未归档的 id(含不存在的 id)不写入即返回。
513
459
  * @param sessionId - 要取消归档的会话。
514
460
  * @returns 更新后的完整归档集合。
515
461
  */
516
462
  async unarchiveSession(sessionId) {
517
463
  return this.enqueueOperation(async () => {
518
- if (!await this.sessionKnown(sessionId))
519
- throw new ArchiveUnknownSessionError(sessionId);
520
464
  const state = this.requireState();
521
465
  if (!state.archivedSessionIds.includes(sessionId))
522
466
  return { archivedSessionIds: [...state.archivedSessionIds] };
@@ -527,88 +471,33 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
527
471
  )
528
472
  };
529
473
  await this.setState(next);
474
+ await this.restoreWorkspaceMembership(sessionId);
530
475
  return { archivedSessionIds: [...next.archivedSessionIds] };
531
476
  });
532
477
  }
533
- /** 按明确 ID 跨工作区归档;先校验整批,单次写入,重复归档不新增标记。 */
534
- async archiveSessions(sessionIds) {
535
- return this.enqueueOperation(async () => {
536
- const ids = archiveSessionIdsSchema.parse(sessionIds);
537
- const state = this.requireState();
538
- const archived = new Set(state.archivedSessionIds);
539
- const archivedSessionIdsAdded = ids.filter((id) => !archived.has(id));
540
- for (const id of archivedSessionIdsAdded) {
541
- if (!await this.sessionKnown(id)) throw new ArchiveUnknownSessionError(id);
542
- }
543
- const next = { ...state, archivedSessionIds: [...state.archivedSessionIds, ...archivedSessionIdsAdded] };
544
- if (archivedSessionIdsAdded.length > 0) await this.setState(next);
545
- return { archivedSessionIds: [...next.archivedSessionIds], archivedSessionIdsAdded };
546
- });
547
- }
548
478
  /**
549
- * 将一个工作区内所有会话加入归档集合。先确认所有待归档会话仍存在,
550
- * 再执行单次状态写入,因此未知会话不会导致项目只归档一部分。
479
+ * 恢复后重新编入路径索引;若会话已不在任何工作区记账但 cwd 仍对应某工作区,
480
+ * 则挂回去,避免「恢复并打开」时官方成员过滤把会话从原工作区抹掉。
551
481
  */
552
- async archiveWorkspaceSessions(workspaceId) {
553
- return this.enqueueOperation(async () => {
554
- workspaceId = workspaceIdSchema.parse(workspaceId);
555
- const workspace = this.requireTable().get(workspaceId);
556
- if (workspace === void 0)
557
- throw new Error(`unknown workspace "${workspaceId}"`);
558
- const state = this.requireState();
559
- const archived = new Set(state.archivedSessionIds);
560
- const archivedSessionIdsAdded = [...new Set(workspace.sessionIds)].filter(
561
- (sessionId) => !archived.has(sessionId)
562
- );
563
- for (const sessionId of archivedSessionIdsAdded) {
564
- if (!await this.sessionKnown(sessionId))
565
- throw new ArchiveUnknownSessionError(sessionId);
566
- }
567
- if (archivedSessionIdsAdded.length === 0)
568
- return {
569
- archivedSessionIds: [...state.archivedSessionIds],
570
- archivedSessionIdsAdded
571
- };
572
- const next = {
573
- ...state,
574
- archivedSessionIds: [
575
- ...state.archivedSessionIds,
576
- ...archivedSessionIdsAdded
577
- ]
578
- };
579
- await this.setState(next);
580
- return {
581
- archivedSessionIds: [...next.archivedSessionIds],
582
- archivedSessionIdsAdded
583
- };
584
- });
585
- }
586
- /**
587
- * 按宿主权威归档集合一次恢复全部、一个工作区或未分组的归档会话。
588
- * 目标全部来自已归档集合,因此即使日志已被外部移除,也会清掉陈旧归档标记。
589
- */
590
- async unarchiveSessions(target) {
591
- return this.enqueueOperation(async () => {
592
- const unarchivedSessionIds = this.archivedSessionIdsForTarget(target);
593
- if (unarchivedSessionIds.length === 0)
594
- return {
595
- archivedSessionIds: [...this.requireState().archivedSessionIds],
596
- unarchivedSessionIds: []
597
- };
598
- const restored = new Set(unarchivedSessionIds);
599
- const state = this.requireState();
600
- const next = {
601
- ...state,
602
- archivedSessionIds: state.archivedSessionIds.filter(
603
- (id) => !restored.has(id)
604
- )
605
- };
606
- await this.setState(next);
607
- return {
608
- archivedSessionIds: [...next.archivedSessionIds],
609
- unarchivedSessionIds
610
- };
611
- });
482
+ async restoreWorkspaceMembership(sessionId) {
483
+ let header;
484
+ try {
485
+ header = await this.readSessionHeader(sessionId);
486
+ await this.indexHeader(header);
487
+ } catch {
488
+ return;
489
+ }
490
+ const path = this.sessionPaths.get(sessionId);
491
+ if (path === void 0) return;
492
+ const table = this.requireTable();
493
+ for (const workspaceId of this.requireState().workspaceIds) {
494
+ const record = table.get(workspaceId);
495
+ if (record === void 0 || record.path !== path) continue;
496
+ if (record.sessionIds.includes(sessionId)) return;
497
+ const entity = this.entities.get(workspaceId);
498
+ if (entity !== void 0) await entity.attachSession(sessionId);
499
+ return;
500
+ }
612
501
  }
613
502
  /**
614
503
  * 按作用域永久删除归档会话。跨会话文件删除无法组成事务,因此继续处理
@@ -622,18 +511,10 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
622
511
  const failures = [];
623
512
  for (const sessionId of requestedSessionIds) {
624
513
  try {
625
- await this.deleteSessionCore(sessionId);
626
- deletedSessionIds.push(sessionId);
514
+ const result = await this.deleteSessionCore(sessionId);
515
+ if (result?.skipped) skippedSessionIds.push(sessionId);
516
+ else deletedSessionIds.push(sessionId);
627
517
  } catch (error) {
628
- if (error instanceof ArchiveUnknownSessionError) {
629
- try {
630
- await this.cleanupUnknownArchivedSession(sessionId);
631
- skippedSessionIds.push(sessionId);
632
- } catch (cleanupError) {
633
- failures.push({ sessionId, message: String(cleanupError) });
634
- }
635
- continue;
636
- }
637
518
  failures.push({ sessionId, message: String(error) });
638
519
  }
639
520
  }
@@ -699,16 +580,20 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
699
580
  * 永久删除一个会话及其全部痕迹(转录目录、工作区记账、归档标记、
700
581
  * 投影缓存行)。
701
582
  * @param sessionId - 要删除的会话。
702
- * @returns 持久化完成后的 `{ deleted: true }`。
703
- * @throws {@link ArchiveUnknownSessionError} 会话未知时抛出。
583
+ * @returns 持久化完成后的 `{ deleted: true }`。未知会话清掉残留痕迹后同样返回成功,不抛错。
704
584
  */
705
585
  async deleteSession(sessionId) {
706
- return this.enqueueOperation(() => this.deleteSessionCore(sessionId));
586
+ return this.enqueueOperation(async () => {
587
+ await this.deleteSessionCore(sessionId);
588
+ return { deleted: true };
589
+ });
707
590
  }
708
591
  /** 串行化后的删除主体(级联路径复用:它已持有操作链,绝不能再入队)。 */
709
592
  async deleteSessionCore(sessionId) {
710
- if (!await this.sessionKnown(sessionId))
711
- throw new ArchiveUnknownSessionError(sessionId);
593
+ if (!await this.sessionKnown(sessionId)) {
594
+ await this.cleanupUnknownArchivedSession(sessionId);
595
+ return { deleted: true, skipped: true };
596
+ }
712
597
  const sessions = this.ctx.get("sessions");
713
598
  const live = sessions?.get(sessionId);
714
599
  const deletedHeader = this.headers.get(sessionId) ?? live?.header;
@@ -938,7 +823,6 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
938
823
  if (!await this.sessionKnown(childId)) continue;
939
824
  await this.deleteSessionCore(childId);
940
825
  } catch (error) {
941
- if (error instanceof ArchiveUnknownSessionError) continue;
942
826
  this.ctx.logger.warn(
943
827
  `archive-manager: cascade delete of subagent session "${childId}" (child of "${sessionId}") failed: ${String(error)}`
944
828
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-archive-manager",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "description": "NPM-installable DSH Web plugin for managing archived sessions.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
@@ -38,22 +38,22 @@
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/cordis": "^4.0.1",
41
- "@deepseek-ai/dsh-client-connection": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
42
- "@deepseek-ai/dsh-client-locale": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
43
- "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
44
- "@deepseek-ai/dsh-client-store": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
45
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
46
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
47
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
48
- "@deepseek-ai/dsh-client-ui-workspace": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
49
- "@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
50
- "@deepseek-ai/dsh-session": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
51
- "@deepseek-ai/dsh-session-projection-cache": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
52
- "@deepseek-ai/dsh-spill-local": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
53
- "@deepseek-ai/dsh-storage-domain": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
54
- "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
55
- "@deepseek-ai/dsh-typert-registry": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2",
56
- "@deepseek-ai/dsh-workspace": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2"
41
+ "@deepseek-ai/dsh-client-connection": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
42
+ "@deepseek-ai/dsh-client-locale": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
43
+ "@deepseek-ai/dsh-client-runtime": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
44
+ "@deepseek-ai/dsh-client-store": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
45
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
46
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
47
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
48
+ "@deepseek-ai/dsh-client-ui-workspace": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
49
+ "@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
50
+ "@deepseek-ai/dsh-session": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
51
+ "@deepseek-ai/dsh-session-projection-cache": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
52
+ "@deepseek-ai/dsh-spill-local": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
53
+ "@deepseek-ai/dsh-storage-domain": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
54
+ "@deepseek-ai/dsh-typert-protocol": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
55
+ "@deepseek-ai/dsh-typert-registry": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2",
56
+ "@deepseek-ai/dsh-workspace": "0.1.0-rc.8 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "@deepseek-ai/dsh-client-runtime": {
@@ -65,30 +65,30 @@
65
65
  },
66
66
  "devDependencies": {
67
67
  "@deepseek-ai/cordis": "4.0.2",
68
- "@deepseek-ai/dsh-api-gateway": "0.1.5-rc.2",
69
- "@deepseek-ai/dsh-client-connection": "0.1.5-rc.2",
70
- "@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
71
- "@deepseek-ai/dsh-client-store": "0.1.5-rc.2",
72
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.2",
73
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
74
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
75
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.5-rc.2",
76
- "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
77
- "@deepseek-ai/dsh-client-ui-workspace": "0.1.5-rc.2",
78
- "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
79
- "@deepseek-ai/dsh-invariants": "0.1.5-rc.2",
80
- "@deepseek-ai/dsh-scope": "0.1.5-rc.2",
81
- "@deepseek-ai/dsh-session": "0.1.5-rc.2",
82
- "@deepseek-ai/dsh-session-persistence": "0.1.5-rc.2",
83
- "@deepseek-ai/dsh-session-projection": "0.1.5-rc.2",
84
- "@deepseek-ai/dsh-session-projection-cache": "0.1.5-rc.2",
85
- "@deepseek-ai/dsh-spill": "0.1.5-rc.2",
86
- "@deepseek-ai/dsh-spill-local": "0.1.5-rc.2",
87
- "@deepseek-ai/dsh-storage": "0.1.5-rc.2",
88
- "@deepseek-ai/dsh-storage-domain": "0.1.5-rc.2",
89
- "@deepseek-ai/dsh-typert-protocol": "0.1.5-rc.2",
90
- "@deepseek-ai/dsh-typert-registry": "0.1.5-rc.2",
91
- "@deepseek-ai/dsh-workspace": "0.1.5-rc.2",
68
+ "@deepseek-ai/dsh-api-gateway": "0.1.6-alpha.2",
69
+ "@deepseek-ai/dsh-client-connection": "0.1.6-alpha.2",
70
+ "@deepseek-ai/dsh-client-locale": "0.1.6-alpha.2",
71
+ "@deepseek-ai/dsh-client-store": "0.1.6-alpha.2",
72
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.6-alpha.2",
73
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.6-alpha.2",
74
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.6-alpha.2",
75
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.1.6-alpha.2",
76
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.6-alpha.2",
77
+ "@deepseek-ai/dsh-client-ui-workspace": "0.1.6-alpha.2",
78
+ "@deepseek-ai/dsh-host-webserver": "0.1.6-alpha.2",
79
+ "@deepseek-ai/dsh-invariants": "0.1.6-alpha.2",
80
+ "@deepseek-ai/dsh-scope": "0.1.6-alpha.2",
81
+ "@deepseek-ai/dsh-session": "0.1.6-alpha.2",
82
+ "@deepseek-ai/dsh-session-persistence": "0.1.6-alpha.2",
83
+ "@deepseek-ai/dsh-session-projection": "0.1.6-alpha.2",
84
+ "@deepseek-ai/dsh-session-projection-cache": "0.1.6-alpha.2",
85
+ "@deepseek-ai/dsh-spill": "0.1.6-alpha.2",
86
+ "@deepseek-ai/dsh-spill-local": "0.1.6-alpha.2",
87
+ "@deepseek-ai/dsh-storage": "0.1.6-alpha.2",
88
+ "@deepseek-ai/dsh-storage-domain": "0.1.6-alpha.2",
89
+ "@deepseek-ai/dsh-typert-protocol": "0.1.6-alpha.2",
90
+ "@deepseek-ai/dsh-typert-registry": "0.1.6-alpha.2",
91
+ "@deepseek-ai/dsh-workspace": "0.1.6-alpha.2",
92
92
  "react": "18.3.1",
93
93
  "react-dom": "18.3.1",
94
94
  "esbuild": "0.25.0",