@michengai/dsh-archive-manager 0.1.37 → 0.1.39

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
@@ -1,18 +1,24 @@
1
1
  import { lstat, rm } from "node:fs/promises";
2
2
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
- import {
4
- WorkspaceRegistry
5
- } from "@deepseek-ai/dsh-workspace";
3
+ import { WorkspaceRegistry } from "@deepseek-ai/dsh-workspace";
6
4
  import { bindTypertRemote, Remote } from "@deepseek-ai/dsh-typert-protocol";
7
5
  import { sessionDir } from "@deepseek-ai/dsh-spill-local";
8
6
  import { trackTombstone } from "./tombstone.js";
9
7
  function jsonlSessionDirectory(persistence, header, location) {
10
- if (persistence.name !== "session-persistence-jsonl" || location.kind !== "jsonl") return;
8
+ if (persistence.name !== "session-persistence-jsonl" || location.kind !== "jsonl")
9
+ return;
11
10
  const root = persistence.root ?? persistence.config?.root;
12
- if (typeof root !== "string" || !isAbsolute(root) || !isAbsolute(location.path)) return;
13
- if (!/^session(?:\.v[1-9][0-9]*)?\.jsonl(?:\.zstd)?$/.test(basename(location.path))) return;
11
+ if (typeof root !== "string" || !isAbsolute(root) || !isAbsolute(location.path))
12
+ return;
13
+ if (!/^session(?:\.v[1-9][0-9]*)?\.jsonl(?:\.zstd)?$/.test(
14
+ basename(location.path)
15
+ ))
16
+ return;
14
17
  if (typeof header.id !== "string" || header.id.length === 0) return;
15
- const encode = (text) => text.replace(/[^A-Za-z0-9._-]/g, (ch) => `~${ch.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`);
18
+ const encode = (text) => text.replace(
19
+ /[^A-Za-z0-9._-]/g,
20
+ (ch) => `~${ch.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`
21
+ );
16
22
  const segment = header.id === "." ? "~002E" : header.id === ".." ? "~002E~002E" : encode(header.id);
17
23
  let project = "_no-cwd";
18
24
  if (header.cwd !== void 0) {
@@ -54,70 +60,104 @@ function headerIdentity(header) {
54
60
  }
55
61
  const sessionIdSchema = {
56
62
  parse(value) {
57
- if (typeof value !== "string" || value.length === 0) throw new TypeError(`sessionId must be a non-empty string, got ${String(value)}`);
63
+ if (typeof value !== "string" || value.length === 0)
64
+ throw new TypeError(
65
+ `sessionId must be a non-empty string, got ${String(value)}`
66
+ );
58
67
  return value;
59
68
  }
60
69
  };
61
70
  const workspaceIdSchema = {
62
71
  parse(value) {
63
- if (typeof value !== "string" || value.length === 0) throw new TypeError(`workspaceId must be a non-empty string, got ${String(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
+ );
64
76
  return value;
65
77
  }
66
78
  };
67
79
  const archivedSetSchema = {
68
80
  parse(value) {
69
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
81
+ if (typeof value !== "object" || value === null || Array.isArray(value))
82
+ throw new TypeError("result must be an object");
70
83
  const ids = value.archivedSessionIds;
71
- if (!Array.isArray(ids) || ids.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
84
+ if (!Array.isArray(ids) || ids.some((id) => typeof id !== "string"))
85
+ throw new TypeError("archivedSessionIds must be a string array");
72
86
  return value;
73
87
  }
74
88
  };
75
89
  const deletedSchema = {
76
90
  parse(value) {
77
- if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true) throw new TypeError("deleted must be true");
91
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value.deleted !== true)
92
+ throw new TypeError("deleted must be true");
78
93
  return value;
79
94
  }
80
95
  };
81
96
  const archivedBatchTargetSchema = {
82
97
  parse(value) {
83
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("target must be an object");
98
+ if (typeof value !== "object" || value === null || Array.isArray(value))
99
+ throw new TypeError("target must be an object");
84
100
  if (value.scope === "all" || value.scope === "ungrouped") return value;
85
- if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0) return value;
86
- if (value.scope === "sessions" && Array.isArray(value.sessionIds) && value.sessionIds.length > 0 && value.sessionIds.every((id) => typeof id === "string" && id.length > 0)) return value;
87
- throw new TypeError("target.scope must be all, ungrouped, workspace with a non-empty workspaceId, or sessions with non-empty sessionIds");
101
+ if (value.scope === "workspace" && typeof value.workspaceId === "string" && value.workspaceId.length > 0)
102
+ return value;
103
+ if (value.scope === "sessions" && Array.isArray(value.sessionIds) && value.sessionIds.length > 0 && value.sessionIds.every((id) => typeof id === "string" && id.length > 0))
104
+ return value;
105
+ throw new TypeError(
106
+ "target.scope must be all, ungrouped, workspace with a non-empty workspaceId, or sessions with non-empty sessionIds"
107
+ );
88
108
  }
89
109
  };
90
110
  const unarchivedBatchSchema = {
91
111
  parse(value) {
92
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
93
- if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
94
- if (!Array.isArray(value.unarchivedSessionIds) || value.unarchivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("unarchivedSessionIds must be a string array");
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");
95
118
  return value;
96
119
  }
97
120
  };
98
121
  const archivedWorkspaceBatchSchema = {
99
122
  parse(value) {
100
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
101
- if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIds must be a string array");
102
- if (!Array.isArray(value.archivedSessionIdsAdded) || value.archivedSessionIdsAdded.some((id) => typeof id !== "string")) throw new TypeError("archivedSessionIdsAdded must be a string array");
123
+ if (typeof value !== "object" || value === null || Array.isArray(value))
124
+ throw new TypeError("result must be an object");
125
+ if (!Array.isArray(value.archivedSessionIds) || value.archivedSessionIds.some((id) => typeof id !== "string"))
126
+ throw new TypeError("archivedSessionIds must be a string array");
127
+ if (!Array.isArray(value.archivedSessionIdsAdded) || value.archivedSessionIdsAdded.some((id) => typeof id !== "string"))
128
+ throw new TypeError("archivedSessionIdsAdded must be a string array");
103
129
  return value;
104
130
  }
105
131
  };
106
132
  const deletedBatchSchema = {
107
133
  parse(value) {
108
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("result must be an object");
109
- for (const key of ["requestedSessionIds", "deletedSessionIds", "skippedSessionIds"]) {
110
- if (!Array.isArray(value[key]) || value[key].some((id) => typeof id !== "string")) throw new TypeError(`${key} must be a string array`);
134
+ if (typeof value !== "object" || value === null || Array.isArray(value))
135
+ throw new TypeError("result must be an object");
136
+ for (const key of [
137
+ "requestedSessionIds",
138
+ "deletedSessionIds",
139
+ "skippedSessionIds"
140
+ ]) {
141
+ if (!Array.isArray(value[key]) || value[key].some((id) => typeof id !== "string"))
142
+ throw new TypeError(`${key} must be a string array`);
111
143
  }
112
- if (!Array.isArray(value.failures) || value.failures.some((failure) => typeof failure !== "object" || failure === null || typeof failure.sessionId !== "string" || typeof failure.message !== "string")) throw new TypeError("failures must contain sessionId/message objects");
144
+ if (!Array.isArray(value.failures) || value.failures.some(
145
+ (failure) => typeof failure !== "object" || failure === null || typeof failure.sessionId !== "string" || typeof failure.message !== "string"
146
+ ))
147
+ throw new TypeError("failures must contain sessionId/message objects");
113
148
  return value;
114
149
  }
115
150
  };
116
151
  const archivedSessionMetadataSchema = {
117
152
  parse(value) {
118
- if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.items)) throw new TypeError("result.items must be an array");
119
- if (value.items.some((item) => typeof item !== "object" || item === null || typeof item.sessionId !== "string" || typeof item.createdAt !== "number" || !Number.isFinite(item.createdAt))) throw new TypeError("items must contain sessionId/createdAt objects");
120
- if (value.repairedSessionIds !== void 0 && (!Array.isArray(value.repairedSessionIds) || value.repairedSessionIds.some((id) => typeof id !== "string"))) throw new TypeError("repairedSessionIds must be a string array");
153
+ if (typeof value !== "object" || value === null || Array.isArray(value) || !Array.isArray(value.items))
154
+ throw new TypeError("result.items must be an array");
155
+ if (value.items.some(
156
+ (item) => typeof item !== "object" || item === null || typeof item.sessionId !== "string" || typeof item.createdAt !== "number" || !Number.isFinite(item.createdAt)
157
+ ))
158
+ throw new TypeError("items must contain sessionId/createdAt objects");
159
+ if (value.repairedSessionIds !== void 0 && (!Array.isArray(value.repairedSessionIds) || value.repairedSessionIds.some((id) => typeof id !== "string")))
160
+ throw new TypeError("repairedSessionIds must be a string array");
121
161
  return value;
122
162
  }
123
163
  };
@@ -128,18 +168,28 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
128
168
  namespace: "workspaceRegistry",
129
169
  method: "unarchiveSession",
130
170
  invocation: { kind: "direct" },
131
- parameters: [{
132
- name: "sessionId",
133
- wire: "sessionId",
134
- source: "json",
135
- codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-session/types#SessionId", schema: sessionIdSchema }
136
- }],
171
+ parameters: [
172
+ {
173
+ name: "sessionId",
174
+ wire: "sessionId",
175
+ source: "json",
176
+ codec: {
177
+ mode: "strict",
178
+ typeSymbol: "@deepseek-ai/dsh-session/types#SessionId",
179
+ schema: sessionIdSchema
180
+ }
181
+ }
182
+ ],
137
183
  result: {
138
184
  mode: "strict",
139
185
  typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionIds",
140
186
  schema: archivedSetSchema
141
187
  },
142
- sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
188
+ sourceLocation: {
189
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
190
+ line: 1,
191
+ column: 1
192
+ }
143
193
  },
144
194
  {
145
195
  id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteSession",
@@ -147,18 +197,28 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
147
197
  namespace: "workspaceRegistry",
148
198
  method: "deleteSession",
149
199
  invocation: { kind: "direct" },
150
- parameters: [{
151
- name: "sessionId",
152
- wire: "sessionId",
153
- source: "json",
154
- codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-session/types#SessionId", schema: sessionIdSchema }
155
- }],
200
+ parameters: [
201
+ {
202
+ name: "sessionId",
203
+ wire: "sessionId",
204
+ source: "json",
205
+ codec: {
206
+ mode: "strict",
207
+ typeSymbol: "@deepseek-ai/dsh-session/types#SessionId",
208
+ schema: sessionIdSchema
209
+ }
210
+ }
211
+ ],
156
212
  result: {
157
213
  mode: "strict",
158
214
  typeSymbol: "@michengai/dsh-archive-manager/types#Deleted",
159
215
  schema: deletedSchema
160
216
  },
161
- sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
217
+ sourceLocation: {
218
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
219
+ line: 1,
220
+ column: 1
221
+ }
162
222
  },
163
223
  {
164
224
  id: "@michengai/dsh-archive-manager#workspaceRegistry/unarchiveSessions",
@@ -166,18 +226,28 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
166
226
  namespace: "workspaceRegistry",
167
227
  method: "unarchiveSessions",
168
228
  invocation: { kind: "direct" },
169
- parameters: [{
170
- name: "target",
171
- wire: "target",
172
- source: "json",
173
- codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget", schema: archivedBatchTargetSchema }
174
- }],
229
+ parameters: [
230
+ {
231
+ name: "target",
232
+ wire: "target",
233
+ source: "json",
234
+ codec: {
235
+ mode: "strict",
236
+ typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget",
237
+ schema: archivedBatchTargetSchema
238
+ }
239
+ }
240
+ ],
175
241
  result: {
176
242
  mode: "strict",
177
243
  typeSymbol: "@michengai/dsh-archive-manager/types#UnarchivedBatch",
178
244
  schema: unarchivedBatchSchema
179
245
  },
180
- sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
246
+ sourceLocation: {
247
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
248
+ line: 1,
249
+ column: 1
250
+ }
181
251
  },
182
252
  {
183
253
  id: "@michengai/dsh-archive-manager#workspaceRegistry/archiveWorkspaceSessions",
@@ -185,18 +255,28 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
185
255
  namespace: "workspaceRegistry",
186
256
  method: "archiveWorkspaceSessions",
187
257
  invocation: { kind: "direct" },
188
- parameters: [{
189
- name: "workspaceId",
190
- wire: "workspaceId",
191
- source: "json",
192
- codec: { mode: "strict", typeSymbol: "@deepseek-ai/dsh-workspace/types#WorkspaceId", schema: workspaceIdSchema }
193
- }],
258
+ parameters: [
259
+ {
260
+ name: "workspaceId",
261
+ wire: "workspaceId",
262
+ source: "json",
263
+ codec: {
264
+ mode: "strict",
265
+ typeSymbol: "@deepseek-ai/dsh-workspace/types#WorkspaceId",
266
+ schema: workspaceIdSchema
267
+ }
268
+ }
269
+ ],
194
270
  result: {
195
271
  mode: "strict",
196
272
  typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedWorkspaceBatch",
197
273
  schema: archivedWorkspaceBatchSchema
198
274
  },
199
- sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
275
+ sourceLocation: {
276
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
277
+ line: 1,
278
+ column: 1
279
+ }
200
280
  },
201
281
  {
202
282
  id: "@michengai/dsh-archive-manager#workspaceRegistry/deleteArchivedSessions",
@@ -204,18 +284,28 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
204
284
  namespace: "workspaceRegistry",
205
285
  method: "deleteArchivedSessions",
206
286
  invocation: { kind: "direct" },
207
- parameters: [{
208
- name: "target",
209
- wire: "target",
210
- source: "json",
211
- codec: { mode: "strict", typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget", schema: archivedBatchTargetSchema }
212
- }],
287
+ parameters: [
288
+ {
289
+ name: "target",
290
+ wire: "target",
291
+ source: "json",
292
+ codec: {
293
+ mode: "strict",
294
+ typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedBatchTarget",
295
+ schema: archivedBatchTargetSchema
296
+ }
297
+ }
298
+ ],
213
299
  result: {
214
300
  mode: "strict",
215
301
  typeSymbol: "@michengai/dsh-archive-manager/types#DeletedBatch",
216
302
  schema: deletedBatchSchema
217
303
  },
218
- sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
304
+ sourceLocation: {
305
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
306
+ line: 1,
307
+ column: 1
308
+ }
219
309
  },
220
310
  {
221
311
  id: "@michengai/dsh-archive-manager#workspaceRegistry/archivedSessionMetadata",
@@ -229,7 +319,11 @@ const ARCHIVE_MANAGER_INVOCATIONS = [
229
319
  typeSymbol: "@michengai/dsh-archive-manager/types#ArchivedSessionMetadata",
230
320
  schema: archivedSessionMetadataSchema
231
321
  },
232
- sourceLocation: { file: "@michengai/dsh-archive-manager/lib/workspace.js", line: 1, column: 1 }
322
+ sourceLocation: {
323
+ file: "@michengai/dsh-archive-manager/lib/workspace.js",
324
+ line: 1,
325
+ column: 1
326
+ }
233
327
  }
234
328
  ];
235
329
  const ARCHIVE_MANAGER_TYPERT = {
@@ -276,114 +370,160 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
276
370
  registerHostRemote(this.ctx);
277
371
  }
278
372
  /**
279
- * 归档设置页创建时间排序所需的最小元数据。老用户可能仍有会话原文和
280
- * 归档标记、却没有投影缓存;这里按需从完整日志重建一次,再通知客户端
281
- * 刷新会话列表。已有缓存不读原文,新老 DSH 的缓存布局都走同一 put。
282
- */
373
+ * 归档设置页创建时间排序所需的最小元数据。老用户可能仍有会话原文和
374
+ * 归档标记、却没有投影缓存;这里按需从完整日志重建一次,再通知客户端
375
+ * 刷新会话列表。已有缓存不读原文,新老 DSH 的缓存布局都走同一 put。
376
+ */
283
377
  async archivedSessionMetadata() {
284
378
  const items = [];
285
379
  const repairedSessionIds = [];
286
- for (const sessionId of [...new Set(this.requireState().archivedSessionIds)]) {
380
+ for (const sessionId of [
381
+ ...new Set(this.requireState().archivedSessionIds)
382
+ ]) {
287
383
  try {
288
384
  const header = await this.readSessionHeader(sessionId);
289
- if (await this.repairArchivedProjection(header)) repairedSessionIds.push(sessionId);
290
- if (typeof header.createdAt === "number" && Number.isFinite(header.createdAt)) items.push({ sessionId, createdAt: header.createdAt });
385
+ if (await this.repairArchivedProjection(header))
386
+ repairedSessionIds.push(sessionId);
387
+ if (typeof header.createdAt === "number" && Number.isFinite(header.createdAt))
388
+ items.push({ sessionId, createdAt: header.createdAt });
291
389
  } catch (error) {
292
- this.ctx.logger.warn(`archive-manager: could not read creation time for archived session "${sessionId}": ${String(error)}`);
390
+ this.ctx.logger.warn(
391
+ `archive-manager: could not read creation time for archived session "${sessionId}": ${String(error)}`
392
+ );
293
393
  }
294
394
  }
295
- return { items, ...repairedSessionIds.length === 0 ? {} : { repairedSessionIds } };
395
+ return {
396
+ items,
397
+ ...repairedSessionIds.length === 0 ? {} : { repairedSessionIds }
398
+ };
296
399
  }
297
400
  /** 从会话原文补齐缺失的派生缓存;任何失败都只降级为原有无摘要列表。 */
298
401
  async repairArchivedProjection(header) {
299
402
  const cache = this.ctx.get("sessionProjectionCache");
300
403
  const persistence = this.ctx.get("sessionPersistence");
301
404
  const projections = this.ctx.get("sessionProjections");
302
- if (cache === void 0 || typeof cache.cachedSnapshot !== "function" || typeof cache.put !== "function") return false;
303
- if (persistence === void 0 || typeof persistence.readFrom !== "function" && typeof persistence.open !== "function" || projections === void 0 || typeof projections.restore !== "function") return false;
405
+ if (cache === void 0 || typeof cache.cachedSnapshot !== "function" || typeof cache.put !== "function")
406
+ return false;
407
+ if (persistence === void 0 || typeof persistence.readFrom !== "function" && typeof persistence.open !== "function" || projections === void 0 || typeof projections.restore !== "function")
408
+ return false;
304
409
  try {
305
- if (!header.isSeeded && cache.cachedSnapshot(header, 0) !== void 0) return false;
410
+ if (!header.isSeeded && cache.cachedSnapshot(header, 0) !== void 0)
411
+ return false;
306
412
  const stored = await this.readStoredProjectionSource(persistence, header.id);
307
413
  const meta = stored.meta ?? header;
308
414
  if (meta.isSeeded === true && stored.inheritedEventCount === void 0) {
309
- this.ctx.logger.warn(`archive-manager: projection repair for seeded archived session "${header.id}" skipped because its inherited event count is unavailable`);
415
+ this.ctx.logger.warn(
416
+ `archive-manager: projection repair for seeded archived session "${header.id}" skipped because its inherited event count is unavailable`
417
+ );
310
418
  return false;
311
419
  }
312
420
  const inheritedEventCount = stored.inheritedEventCount ?? 0;
313
421
  if (meta.isSeeded !== true && inheritedEventCount !== 0) {
314
- this.ctx.logger.warn(`archive-manager: projection repair for unseeded archived session "${header.id}" skipped because its inherited event count is not zero`);
422
+ this.ctx.logger.warn(
423
+ `archive-manager: projection repair for unseeded archived session "${header.id}" skipped because its inherited event count is not zero`
424
+ );
315
425
  return false;
316
426
  }
317
427
  if (cache.cachedSnapshot(meta, inheritedEventCount) !== void 0) return false;
318
- const restored = projections.restore({}, stored.events, 0, meta, inheritedEventCount);
319
- if (restored === void 0 || typeof restored !== "object" || restored.checkpoint === void 0) return false;
320
- await cache.put(header.id, {
321
- // 支持的宿主头部均有 version;新版缓存校验格式代际,旧版逐字段比较忽略此键。
322
- formatVersion: meta.version,
323
- createdAt: meta.createdAt,
324
- ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
325
- isSeeded: meta.isSeeded ?? false,
428
+ const restored = projections.restore(
429
+ {},
430
+ stored.events,
431
+ 0,
432
+ meta,
326
433
  inheritedEventCount
327
- }, restored.checkpoint);
434
+ );
435
+ if (restored === void 0 || typeof restored !== "object" || restored.checkpoint === void 0)
436
+ return false;
437
+ await cache.put(
438
+ header.id,
439
+ {
440
+ // 支持的宿主头部均有 version;新版缓存校验格式代际,旧版逐字段比较忽略此键。
441
+ formatVersion: meta.version,
442
+ createdAt: meta.createdAt,
443
+ ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
444
+ isSeeded: meta.isSeeded ?? false,
445
+ inheritedEventCount
446
+ },
447
+ restored.checkpoint
448
+ );
328
449
  return true;
329
450
  } catch (error) {
330
- this.ctx.logger.warn(`archive-manager: projection repair for archived session "${header.id}" failed: ${String(error)}`);
451
+ this.ctx.logger.warn(
452
+ `archive-manager: projection repair for archived session "${header.id}" failed: ${String(error)}`
453
+ );
331
454
  return false;
332
455
  }
333
456
  }
334
457
  /** 新版读句柄必须关闭;旧版仍沿用 readFrom,避免激活 Agent 或写入会话日志。 */
335
458
  async readStoredProjectionSource(persistence, sessionId) {
336
- if (typeof persistence.readFrom === "function") return persistence.readFrom(sessionId, 0);
459
+ if (typeof persistence.readFrom === "function")
460
+ return persistence.readFrom(sessionId, 0);
337
461
  const handle = await persistence.open(sessionId, "read");
338
462
  try {
339
463
  const { events } = await handle.read(0);
340
- return { meta: handle.header, inheritedEventCount: handle.inheritedEventCount, events };
464
+ return {
465
+ meta: handle.header,
466
+ inheritedEventCount: handle.inheritedEventCount,
467
+ events
468
+ };
341
469
  } finally {
342
470
  await handle.close();
343
471
  }
344
472
  }
345
473
  /**
346
- * 把一个会话移出注册表全局归档集合,恢复其正常可见性(其记账位从未
347
- * 移动,会话在原工作区位置重新出现)。幂等:未归档的已知会话直接返回
348
- * 当前集合不写入;未知会话与 `archiveSession` 一样抛错。
349
- * @param sessionId - 要取消归档的会话。
350
- * @returns 更新后的完整归档集合。
351
- */
474
+ * 把一个会话移出注册表全局归档集合,恢复其正常可见性(其记账位从未
475
+ * 移动,会话在原工作区位置重新出现)。幂等:未归档的已知会话直接返回
476
+ * 当前集合不写入;未知会话与 `archiveSession` 一样抛错。
477
+ * @param sessionId - 要取消归档的会话。
478
+ * @returns 更新后的完整归档集合。
479
+ */
352
480
  async unarchiveSession(sessionId) {
353
481
  return this.enqueueOperation(async () => {
354
- if (!await this.sessionKnown(sessionId)) throw new ArchiveUnknownSessionError(sessionId);
482
+ if (!await this.sessionKnown(sessionId))
483
+ throw new ArchiveUnknownSessionError(sessionId);
355
484
  const state = this.requireState();
356
- if (!state.archivedSessionIds.includes(sessionId)) return { archivedSessionIds: [...state.archivedSessionIds] };
485
+ if (!state.archivedSessionIds.includes(sessionId))
486
+ return { archivedSessionIds: [...state.archivedSessionIds] };
357
487
  const next = {
358
488
  ...state,
359
- archivedSessionIds: state.archivedSessionIds.filter((id) => id !== sessionId)
489
+ archivedSessionIds: state.archivedSessionIds.filter(
490
+ (id) => id !== sessionId
491
+ )
360
492
  };
361
493
  await this.setState(next);
362
494
  return { archivedSessionIds: [...next.archivedSessionIds] };
363
495
  });
364
496
  }
365
497
  /**
366
- * 将一个工作区内所有会话加入归档集合。先确认所有待归档会话仍存在,
367
- * 再执行单次状态写入,因此未知会话不会导致项目只归档一部分。
368
- */
498
+ * 将一个工作区内所有会话加入归档集合。先确认所有待归档会话仍存在,
499
+ * 再执行单次状态写入,因此未知会话不会导致项目只归档一部分。
500
+ */
369
501
  async archiveWorkspaceSessions(workspaceId) {
370
502
  return this.enqueueOperation(async () => {
371
503
  workspaceId = workspaceIdSchema.parse(workspaceId);
372
504
  const workspace = this.requireTable().get(workspaceId);
373
- if (workspace === void 0) throw new Error(`unknown workspace "${workspaceId}"`);
505
+ if (workspace === void 0)
506
+ throw new Error(`unknown workspace "${workspaceId}"`);
374
507
  const state = this.requireState();
375
508
  const archived = new Set(state.archivedSessionIds);
376
- const archivedSessionIdsAdded = [...new Set(workspace.sessionIds)].filter((sessionId) => !archived.has(sessionId));
509
+ const archivedSessionIdsAdded = [...new Set(workspace.sessionIds)].filter(
510
+ (sessionId) => !archived.has(sessionId)
511
+ );
377
512
  for (const sessionId of archivedSessionIdsAdded) {
378
- if (!await this.sessionKnown(sessionId)) throw new ArchiveUnknownSessionError(sessionId);
513
+ if (!await this.sessionKnown(sessionId))
514
+ throw new ArchiveUnknownSessionError(sessionId);
379
515
  }
380
- if (archivedSessionIdsAdded.length === 0) return {
381
- archivedSessionIds: [...state.archivedSessionIds],
382
- archivedSessionIdsAdded
383
- };
516
+ if (archivedSessionIdsAdded.length === 0)
517
+ return {
518
+ archivedSessionIds: [...state.archivedSessionIds],
519
+ archivedSessionIdsAdded
520
+ };
384
521
  const next = {
385
522
  ...state,
386
- archivedSessionIds: [...state.archivedSessionIds, ...archivedSessionIdsAdded]
523
+ archivedSessionIds: [
524
+ ...state.archivedSessionIds,
525
+ ...archivedSessionIdsAdded
526
+ ]
387
527
  };
388
528
  await this.setState(next);
389
529
  return {
@@ -393,21 +533,24 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
393
533
  });
394
534
  }
395
535
  /**
396
- * 按宿主权威归档集合一次恢复全部、一个工作区或未分组的归档会话。
397
- * 目标全部来自已归档集合,因此即使日志已被外部移除,也会清掉陈旧归档标记。
398
- */
536
+ * 按宿主权威归档集合一次恢复全部、一个工作区或未分组的归档会话。
537
+ * 目标全部来自已归档集合,因此即使日志已被外部移除,也会清掉陈旧归档标记。
538
+ */
399
539
  async unarchiveSessions(target) {
400
540
  return this.enqueueOperation(async () => {
401
541
  const unarchivedSessionIds = this.archivedSessionIdsForTarget(target);
402
- if (unarchivedSessionIds.length === 0) return {
403
- archivedSessionIds: [...this.requireState().archivedSessionIds],
404
- unarchivedSessionIds: []
405
- };
542
+ if (unarchivedSessionIds.length === 0)
543
+ return {
544
+ archivedSessionIds: [...this.requireState().archivedSessionIds],
545
+ unarchivedSessionIds: []
546
+ };
406
547
  const restored = new Set(unarchivedSessionIds);
407
548
  const state = this.requireState();
408
549
  const next = {
409
550
  ...state,
410
- archivedSessionIds: state.archivedSessionIds.filter((id) => !restored.has(id))
551
+ archivedSessionIds: state.archivedSessionIds.filter(
552
+ (id) => !restored.has(id)
553
+ )
411
554
  };
412
555
  await this.setState(next);
413
556
  return {
@@ -417,9 +560,9 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
417
560
  });
418
561
  }
419
562
  /**
420
- * 按作用域永久删除归档会话。跨会话文件删除无法组成事务,因此继续处理
421
- * 后续目标并把成功、并发消失和失败分别返回给客户端。
422
- */
563
+ * 按作用域永久删除归档会话。跨会话文件删除无法组成事务,因此继续处理
564
+ * 后续目标并把成功、并发消失和失败分别返回给客户端。
565
+ */
423
566
  async deleteArchivedSessions(target) {
424
567
  return this.enqueueOperation(async () => {
425
568
  const requestedSessionIds = this.archivedSessionIdsForTarget(target);
@@ -443,14 +586,19 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
443
586
  failures.push({ sessionId, message: String(error) });
444
587
  }
445
588
  }
446
- return { requestedSessionIds, deletedSessionIds, skippedSessionIds, failures };
589
+ return {
590
+ requestedSessionIds,
591
+ deletedSessionIds,
592
+ skippedSessionIds,
593
+ failures
594
+ };
447
595
  });
448
596
  }
449
597
  /**
450
- * 清理已无转录的陈旧归档项。缓存墓碑只在清除在途写入期间短暂持有:
451
- * workspace 没有可记录的旧 header 身份,永久保留它会挡住未来的冷复用。
452
- * 归档标记最后清除,前序可失败步骤出错时批量入口仍能再次命中。
453
- */
598
+ * 清理已无转录的陈旧归档项。缓存墓碑只在清除在途写入期间短暂持有:
599
+ * workspace 没有可记录的旧 header 身份,永久保留它会挡住未来的冷复用。
600
+ * 归档标记最后清除,前序可失败步骤出错时批量入口仍能再次命中。
601
+ */
454
602
  async cleanupUnknownArchivedSession(sessionId) {
455
603
  const projCache = this.ctx.get("sessionProjectionCache");
456
604
  await projCache?.whenIdle?.();
@@ -465,7 +613,9 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
465
613
  if (state.archivedSessionIds.includes(sessionId)) {
466
614
  await this.setState({
467
615
  ...state,
468
- archivedSessionIds: state.archivedSessionIds.filter((id) => id !== sessionId)
616
+ archivedSessionIds: state.archivedSessionIds.filter(
617
+ (id) => id !== sessionId
618
+ )
469
619
  });
470
620
  }
471
621
  }
@@ -481,30 +631,33 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
481
631
  }
482
632
  if (target.scope === "workspace") {
483
633
  const workspace = this.requireTable().get(target.workspaceId);
484
- if (workspace === void 0) throw new Error(`unknown workspace "${target.workspaceId}"`);
634
+ if (workspace === void 0)
635
+ throw new Error(`unknown workspace "${target.workspaceId}"`);
485
636
  const accounted2 = new Set(workspace.sessionIds);
486
637
  return archivedSessionIds.filter((id) => accounted2.has(id));
487
638
  }
488
639
  const accounted = /* @__PURE__ */ new Set();
489
640
  const table = this.requireTable();
490
641
  for (const workspaceId of state.workspaceIds) {
491
- for (const sessionId of table.get(workspaceId)?.sessionIds ?? []) accounted.add(sessionId);
642
+ for (const sessionId of table.get(workspaceId)?.sessionIds ?? [])
643
+ accounted.add(sessionId);
492
644
  }
493
645
  return archivedSessionIds.filter((id) => !accounted.has(id));
494
646
  }
495
647
  /**
496
- * 永久删除一个会话及其全部痕迹(转录目录、工作区记账、归档标记、
497
- * 投影缓存行)。
498
- * @param sessionId - 要删除的会话。
499
- * @returns 持久化完成后的 `{ deleted: true }`。
500
- * @throws {@link ArchiveUnknownSessionError} 会话未知时抛出。
501
- */
648
+ * 永久删除一个会话及其全部痕迹(转录目录、工作区记账、归档标记、
649
+ * 投影缓存行)。
650
+ * @param sessionId - 要删除的会话。
651
+ * @returns 持久化完成后的 `{ deleted: true }`。
652
+ * @throws {@link ArchiveUnknownSessionError} 会话未知时抛出。
653
+ */
502
654
  async deleteSession(sessionId) {
503
655
  return this.enqueueOperation(() => this.deleteSessionCore(sessionId));
504
656
  }
505
657
  /** 串行化后的删除主体(级联路径复用:它已持有操作链,绝不能再入队)。 */
506
658
  async deleteSessionCore(sessionId) {
507
- if (!await this.sessionKnown(sessionId)) throw new ArchiveUnknownSessionError(sessionId);
659
+ if (!await this.sessionKnown(sessionId))
660
+ throw new ArchiveUnknownSessionError(sessionId);
508
661
  const sessions = this.ctx.get("sessions");
509
662
  const live = sessions?.get(sessionId);
510
663
  const deletedHeader = this.headers.get(sessionId) ?? live?.header;
@@ -512,7 +665,8 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
512
665
  await sessions.flush(live);
513
666
  const entry = sessions.liveEntryFor(live);
514
667
  sessions.detachEntered(entry);
515
- } else if (sessions !== void 0) await this.publishColdSessionRemoval(sessionId, sessions);
668
+ } else if (sessions !== void 0)
669
+ await this.publishColdSessionRemoval(sessionId, sessions);
516
670
  const projCache = this.ctx.get("sessionProjectionCache");
517
671
  await projCache?.whenIdle?.();
518
672
  if (projCache !== void 0) await projCache.delete(sessionId);
@@ -523,12 +677,15 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
523
677
  if (state.archivedSessionIds.includes(sessionId)) {
524
678
  await this.setState({
525
679
  ...state,
526
- archivedSessionIds: state.archivedSessionIds.filter((id) => id !== sessionId)
680
+ archivedSessionIds: state.archivedSessionIds.filter(
681
+ (id) => id !== sessionId
682
+ )
527
683
  });
528
684
  }
529
685
  await this.removeFromWorkspaceAccounts(sessionId);
530
686
  this.forgetIndexedSession(sessionId);
531
- if (deletedHeader !== void 0) this.deletedIdentities.set(sessionId, headerIdentity(deletedHeader));
687
+ if (deletedHeader !== void 0)
688
+ this.deletedIdentities.set(sessionId, headerIdentity(deletedHeader));
532
689
  this.publishDeletedSession(sessionId);
533
690
  return { deleted: true };
534
691
  }
@@ -537,13 +694,15 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
537
694
  try {
538
695
  this.ctx.emit("api-session/removed", sessionId);
539
696
  } catch (error) {
540
- this.ctx.logger.warn(`archive-manager: session "${sessionId}" deleted but removal notification failed: ${String(error)}`);
697
+ this.ctx.logger.warn(
698
+ `archive-manager: session "${sessionId}" deleted but removal notification failed: ${String(error)}`
699
+ );
541
700
  }
542
701
  }
543
702
  /**
544
- * 从父类内存索引中遗忘已删除会话,并阻止后续 indexHeaders 把它加回。
545
- * 实时会话以同 id 重新出现时(自定义 id 复用)会撤掉墓碑。
546
- */
703
+ * 从父类内存索引中遗忘已删除会话,并阻止后续 indexHeaders 把它加回。
704
+ * 实时会话以同 id 重新出现时(自定义 id 复用)会撤掉墓碑。
705
+ */
547
706
  clearTombstone(sessionId) {
548
707
  this.deletedSessionIds.delete(sessionId);
549
708
  this.deletedIdentities.delete(sessionId);
@@ -552,15 +711,21 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
552
711
  this.ctx.get("sessionProjectionCache")?.clearTombstone?.(sessionId);
553
712
  }
554
713
  forgetIndexedSession(sessionId) {
555
- for (const evicted of trackTombstone(this.deletedSessionIds, this.deletedSessionOrder, sessionId, this.deletedSessionTombstoneLimit)) this.deletedIdentities.delete(evicted);
714
+ for (const evicted of trackTombstone(
715
+ this.deletedSessionIds,
716
+ this.deletedSessionOrder,
717
+ sessionId,
718
+ this.deletedSessionTombstoneLimit
719
+ ))
720
+ this.deletedIdentities.delete(evicted);
556
721
  this.headers.delete(sessionId);
557
722
  this.sessionPaths.delete(sessionId);
558
723
  this.invalidSessionPaths.delete(sessionId);
559
724
  }
560
725
  /**
561
- * 已删除会话对归档/删除入口都视为未知。实时复用同一 id 时撤墓碑,
562
- * 避免挡住新会话。
563
- */
726
+ * 已删除会话对归档/删除入口都视为未知。实时复用同一 id 时撤墓碑,
727
+ * 避免挡住新会话。
728
+ */
564
729
  async sessionKnown(id) {
565
730
  if (this.ctx.get("sessions")?.get(id) !== void 0) {
566
731
  this.clearTombstone(id);
@@ -570,40 +735,46 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
570
735
  return super.sessionKnown(id);
571
736
  }
572
737
  /**
573
- * 墓碑分支的冷复用探针:其他进程以同 id 重建并落盘的新会话(日志身份
574
- * 不同)撤墓碑放行并重新编入索引;stale list() 里同生命周期的旧头部
575
- * 仍视为未知。身份不可考(删除时未取到头部)时保守维持未知。
576
- */
738
+ * 墓碑分支的冷复用探针:其他进程以同 id 重建并落盘的新会话(日志身份
739
+ * 不同)撤墓碑放行并重新编入索引;stale list() 里同生命周期的旧头部
740
+ * 仍视为未知。身份不可考(删除时未取到头部)时保守维持未知。
741
+ */
577
742
  async coldReuseKnown(id) {
578
743
  const deletedIdentity = this.deletedIdentities.get(id);
579
744
  if (deletedIdentity === void 0) return false;
580
745
  const persistence = this.ctx.get("sessionPersistence");
581
- if (persistence === void 0 || typeof persistence.list !== "function") return false;
746
+ if (persistence === void 0 || typeof persistence.list !== "function")
747
+ return false;
582
748
  let header;
583
749
  try {
584
750
  header = (await this.listStoredHeaders()).find((item) => item.id === id);
585
751
  } catch (error) {
586
- this.ctx.logger.warn(`archive-manager: cold-reuse probe for "${id}" failed: ${String(error)}`);
752
+ this.ctx.logger.warn(
753
+ `archive-manager: cold-reuse probe for "${id}" failed: ${String(error)}`
754
+ );
587
755
  return false;
588
756
  }
589
757
  if (header === void 0) return false;
590
758
  const listed = headerIdentity(header);
591
- if (listed.createdAt === deletedIdentity.createdAt && listed.cwd === deletedIdentity.cwd) return false;
759
+ if (listed.createdAt === deletedIdentity.createdAt && listed.cwd === deletedIdentity.cwd)
760
+ return false;
592
761
  this.clearTombstone(id);
593
762
  await this.indexHeader(header);
594
763
  return true;
595
764
  }
596
765
  /**
597
- * 父类 indexHeaders 只增不减;跳过墓碑 id,避免 stale persistence.list()
598
- * 把已删除会话重新编入 headers。
599
- */
766
+ * 父类 indexHeaders 只增不减;跳过墓碑 id,避免 stale persistence.list()
767
+ * 把已删除会话重新编入 headers。
768
+ */
600
769
  async indexHeader(header) {
601
770
  if (this.deletedSessionIds.has(header.id)) return;
602
771
  return super.indexHeader(header);
603
772
  }
604
773
  /** 统一旧版头部数组与 0.1.3 的持久化快照,供父类索引和本插件枚举共用。 */
605
774
  async listStoredHeaders() {
606
- return (await this.ctx.sessionPersistence.list()).map((item) => item.header ?? item);
775
+ return (await this.ctx.sessionPersistence.list()).map(
776
+ (item) => item.header ?? item
777
+ );
607
778
  }
608
779
  async indexHeaders(items) {
609
780
  for (const item of items) await this.indexHeader(item.header ?? item);
@@ -611,7 +782,8 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
611
782
  /** 为未处于实时状态的持久化会话发布相同的移除事件。 */
612
783
  async publishColdSessionRemoval(sessionId, sessions) {
613
784
  const persistence = this.ctx.get("sessionPersistence");
614
- if (persistence === void 0 || typeof persistence.prepare !== "function") return;
785
+ if (persistence === void 0 || typeof persistence.prepare !== "function")
786
+ return;
615
787
  try {
616
788
  const preparation = await persistence.prepare(sessionId);
617
789
  const detach = sessions.enter(preparation.session);
@@ -622,19 +794,25 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
622
794
  preparation[Symbol.dispose]();
623
795
  }
624
796
  } catch (error) {
625
- this.ctx.logger.warn(`archive-manager: could not publish removal for stored session "${sessionId}": ${String(error)}`);
797
+ this.ctx.logger.warn(
798
+ `archive-manager: could not publish removal for stored session "${sessionId}": ${String(error)}`
799
+ );
626
800
  }
627
801
  }
628
802
  /** 官方 JSONL 已知布局清理会话专属目录;其他后端只删除定位到的工件。 */
629
803
  async removeTranscriptDirectory(sessionId) {
630
804
  const persistence = this.ctx.get("sessionPersistence");
631
805
  if (persistence === void 0 || typeof persistence.locate !== "function") {
632
- throw new Error(`cannot delete session "${sessionId}": the session persistence backend does not expose locate() to resolve its transcript artifact`);
806
+ throw new Error(
807
+ `cannot delete session "${sessionId}": the session persistence backend does not expose locate() to resolve its transcript artifact`
808
+ );
633
809
  }
634
810
  const header = await this.readSessionHeader(sessionId);
635
811
  const location = persistence.locate(header);
636
812
  if (location === void 0 || typeof location.path !== "string") {
637
- throw new Error(`cannot delete session "${sessionId}": the session persistence backend could not resolve its transcript artifact`);
813
+ throw new Error(
814
+ `cannot delete session "${sessionId}": the session persistence backend could not resolve its transcript artifact`
815
+ );
638
816
  }
639
817
  let target = { path: location.path, kind: "transcript artifact" };
640
818
  try {
@@ -649,15 +827,21 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
649
827
  if (error?.code === "ENOENT") continue;
650
828
  throw error;
651
829
  }
652
- if (stat.isSymbolicLink()) throw new Error(`refusing to delete through symbolic link "${path}"`);
653
- if (!stat.isDirectory()) throw new Error(`expected session storage directory "${path}"`);
830
+ if (stat.isSymbolicLink())
831
+ throw new Error(`refusing to delete through symbolic link "${path}"`);
832
+ if (!stat.isDirectory())
833
+ throw new Error(`expected session storage directory "${path}"`);
654
834
  }
655
835
  } else if (persistence.name === "session-persistence-jsonl") {
656
- this.ctx.logger.warn(`archive-manager: session "${sessionId}": JSONL directory ownership could not be verified; falling back to artifact-only deletion at "${location.path}" (parent directory retained)`);
836
+ this.ctx.logger.warn(
837
+ `archive-manager: session "${sessionId}": JSONL directory ownership could not be verified; falling back to artifact-only deletion at "${location.path}" (parent directory retained)`
838
+ );
657
839
  }
658
840
  await rm(target.path, { recursive: true, force: true });
659
841
  if (typeof persistence.stat === "function" && await persistence.stat(sessionId) !== void 0) {
660
- throw new Error(`session "${sessionId}" is still present in persistence after artifact removal`);
842
+ throw new Error(
843
+ `session "${sessionId}" is still present in persistence after artifact removal`
844
+ );
661
845
  }
662
846
  } catch (error) {
663
847
  const message = `cannot delete session "${sessionId}": cleanup of ${target.kind} "${target.path}" failed; bookkeeping retained for retry`;
@@ -676,24 +860,27 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
676
860
  const next = await table.update(workspaceId, (current) => ({
677
861
  ...current,
678
862
  sessionIds: current.sessionIds.filter((id) => id !== sessionId),
679
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
863
+ updatedAt: /* @__PURE__ */ (/* @__PURE__ */ new Date()).toISOString()
680
864
  }));
681
865
  const entity = this.entities.get(workspaceId);
682
866
  if (entity !== void 0) entity.record = next;
683
867
  }
684
868
  }
685
869
  /** 尽力而为的级联删除:删除 `sessionId` 的 SUBAGENT 子会话。
686
- * 仅头部标记 `origin: "subagent"` 的会话参与:单凭 `parentSession` 有歧义
687
- *(fork 分支也携带它),而 fork 分支是独立的用户会话,绝不能被级联删除。 */
870
+ * 仅头部标记 `origin: "subagent"` 的会话参与:单凭 `parentSession` 有歧义
871
+ *(fork 分支也携带它),而 fork 分支是独立的用户会话,绝不能被级联删除。 */
688
872
  async deleteDescendants(sessionId) {
689
873
  try {
690
874
  const descendants = [];
691
875
  const sessions = this.ctx.get("sessions");
692
- if (sessions !== void 0) for (const session of sessions.list()) {
693
- if (session.header.parentSession === sessionId && session.header.origin === "subagent") descendants.push(session.id);
694
- }
876
+ if (sessions !== void 0)
877
+ for (const session of sessions.list()) {
878
+ if (session.header.parentSession === sessionId && session.header.origin === "subagent")
879
+ descendants.push(session.id);
880
+ }
695
881
  for (const header of await this.listStoredHeaders()) {
696
- if (header.parentSession === sessionId && header.origin === "subagent" && !descendants.includes(header.id)) descendants.push(header.id);
882
+ if (header.parentSession === sessionId && header.origin === "subagent" && !descendants.includes(header.id))
883
+ descendants.push(header.id);
697
884
  }
698
885
  for (const childId of descendants) {
699
886
  try {
@@ -701,11 +888,15 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
701
888
  await this.deleteSessionCore(childId);
702
889
  } catch (error) {
703
890
  if (error instanceof ArchiveUnknownSessionError) continue;
704
- this.ctx.logger.warn(`archive-manager: cascade delete of subagent session "${childId}" (child of "${sessionId}") failed: ${String(error)}`);
891
+ this.ctx.logger.warn(
892
+ `archive-manager: cascade delete of subagent session "${childId}" (child of "${sessionId}") failed: ${String(error)}`
893
+ );
705
894
  }
706
895
  }
707
896
  } catch (error) {
708
- this.ctx.logger.warn(`archive-manager: descendant enumeration for deleted session "${sessionId}" failed: ${String(error)}`);
897
+ this.ctx.logger.warn(
898
+ `archive-manager: descendant enumeration for deleted session "${sessionId}" failed: ${String(error)}`
899
+ );
709
900
  }
710
901
  }
711
902
  /** 尽力而为的 spill 清理:移除该会话作用域的 spill 目录。 */
@@ -713,9 +904,14 @@ var ArchiveWorkspaceRegistry = class extends WorkspaceRegistry {
713
904
  try {
714
905
  const spill = this.ctx.get("spillStore");
715
906
  if (spill === void 0 || typeof spill.root !== "string") return;
716
- await rm(sessionDir(spill.root, sessionId), { recursive: true, force: true });
907
+ await rm(sessionDir(spill.root, sessionId), {
908
+ recursive: true,
909
+ force: true
910
+ });
717
911
  } catch (error) {
718
- this.ctx.logger.warn(`archive-manager: spill cleanup for deleted session "${sessionId}" failed: ${String(error)}`);
912
+ this.ctx.logger.warn(
913
+ `archive-manager: spill cleanup for deleted session "${sessionId}" failed: ${String(error)}`
914
+ );
719
915
  }
720
916
  }
721
917
  };