@xrn1997/dsh-session-delete 0.3.1 → 0.5.0

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.
Files changed (83) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +141 -129
  3. package/cordis.patch.yml +3 -3
  4. package/lib/client.js +2811 -1264
  5. package/lib/index.js +2368 -1277
  6. package/lib/types/api/attachment-orphans.d.ts +11 -0
  7. package/lib/types/api/attachment.d.ts +23 -0
  8. package/lib/types/api/dispatch.d.ts +13 -29
  9. package/lib/types/api/request-trust.d.ts +45 -0
  10. package/lib/types/api/route-table.d.ts +52 -18
  11. package/lib/types/api/trash-rows.d.ts +3 -3
  12. package/lib/types/client/attachment-view.d.ts +27 -0
  13. package/lib/types/client/batch-bar.d.ts +1 -1
  14. package/lib/types/client/batch-confirm.d.ts +2 -2
  15. package/lib/types/client/batch-menu-item.d.ts +1 -1
  16. package/lib/types/client/batch-select.d.ts +1 -1
  17. package/lib/types/client/busy-gate.d.ts +6 -2
  18. package/lib/types/client/confirm-close.d.ts +14 -0
  19. package/lib/types/client/context.d.ts +3 -2
  20. package/lib/types/client/copy.d.ts +102 -42
  21. package/lib/types/client/delete-confirm.d.ts +1 -3
  22. package/lib/types/client/delete-menu-item.d.ts +1 -1
  23. package/lib/types/client/host-rows.d.ts +5 -1
  24. package/lib/types/client/main-view.d.ts +1 -1
  25. package/lib/types/client/maintenance-parts.d.ts +25 -0
  26. package/lib/types/client/maintenance-settings.d.ts +22 -0
  27. package/lib/types/client/markdown.d.ts +9 -2
  28. package/lib/types/client/orphan-maintenance.d.ts +16 -13
  29. package/lib/types/client/receipt.d.ts +44 -2
  30. package/lib/types/client/remote.d.ts +38 -50
  31. package/lib/types/client/store.d.ts +14 -3
  32. package/lib/types/client/trash-confirm.d.ts +2 -15
  33. package/lib/types/client/trash-detail.d.ts +48 -6
  34. package/lib/types/client/trash-entry.d.ts +1 -1
  35. package/lib/types/client/trash-nav.d.ts +12 -8
  36. package/lib/types/client/trash-panel.d.ts +4 -3
  37. package/lib/types/client/ui/Checkbox.d.ts +1 -1
  38. package/lib/types/client/ui/Modal.d.ts +2 -2
  39. package/lib/types/client/ui/Toast.d.ts +3 -1
  40. package/lib/types/client/ui/icons.d.ts +7 -12
  41. package/lib/types/client/ui/top-layer.d.ts +10 -0
  42. package/lib/types/client/undo-notices.d.ts +52 -0
  43. package/lib/types/client/undo-toast.d.ts +6 -59
  44. package/lib/types/client/use-column-resize.d.ts +9 -20
  45. package/lib/types/client/use-maintenance-section.d.ts +52 -0
  46. package/lib/types/client/use-maintenance.d.ts +17 -0
  47. package/lib/types/client/use-panel-width.d.ts +1 -1
  48. package/lib/types/client/use-trash-panel-controller.d.ts +54 -16
  49. package/lib/types/client/use-trash-query.d.ts +39 -34
  50. package/lib/types/client/use-trash-selection.d.ts +15 -12
  51. package/lib/types/client/write-seats.d.ts +36 -28
  52. package/lib/types/client/write-settlement.d.ts +11 -17
  53. package/lib/types/client/write.d.ts +21 -8
  54. package/lib/types/deps.d.ts +18 -7
  55. package/lib/types/domain/closure.d.ts +44 -0
  56. package/lib/types/index.d.ts +6 -2
  57. package/lib/types/log/session-log.d.ts +4 -3
  58. package/lib/types/log/zstd-frames.d.ts +13 -8
  59. package/lib/types/ports/agent-handles.d.ts +1 -1
  60. package/lib/types/ports/connection-port.d.ts +33 -0
  61. package/lib/types/ports/fs-port.d.ts +24 -9
  62. package/lib/types/ports/host-port.d.ts +21 -50
  63. package/lib/types/ports/presence-port.d.ts +31 -0
  64. package/lib/types/shared/wire.d.ts +175 -38
  65. package/lib/types/trash/attachments.d.ts +92 -0
  66. package/lib/types/trash/detail.d.ts +46 -16
  67. package/lib/types/trash/entry.d.ts +10 -4
  68. package/lib/types/trash/groups.d.ts +9 -11
  69. package/lib/types/trash/manifest.d.ts +4 -4
  70. package/lib/types/trash/storage-manifest.d.ts +1 -1
  71. package/lib/types/verbs/batch-carried.d.ts +1 -3
  72. package/lib/types/verbs/delete-session.d.ts +2 -2
  73. package/lib/types/verbs/errors.d.ts +2 -0
  74. package/lib/types/verbs/live-admission.d.ts +4 -7
  75. package/lib/types/verbs/member-journal.d.ts +3 -4
  76. package/lib/types/verbs/member-presence.d.ts +16 -37
  77. package/lib/types/verbs/move-group.d.ts +2 -1
  78. package/lib/types/verbs/orphan-subagents.d.ts +19 -12
  79. package/lib/types/verbs/permanent-removal.d.ts +60 -25
  80. package/lib/types/verbs/restore-session.d.ts +7 -13
  81. package/lib/types/verbs/trash-scope.d.ts +36 -0
  82. package/package.json +117 -106
  83. package/lib/types/verbs/purge-trash.d.ts +0 -30
@@ -1,18 +1,9 @@
1
1
  /**
2
- * 跨半 wire 契约:`/dsh-session-delete-api` 的**前缀、六个端点段、请求字段名**——
3
- * Node 半(`src/api/dispatch.ts` 注册并分发)与浏览器半(`src/client/remote.ts` 同源 fetch)
4
- * 之间唯一的那份真相。两侧都 import 它,不各写一份字面量。
2
+ * 跨半 wire 契约:前缀、九个端点、请求/响应形状与失败码。
3
+ * Node 半(src/api/dispatch.ts)与浏览器半(src/client/remote.ts)共用这一份,不各写字面量。
5
4
  *
6
- * 两条构建期硬约束(不是偏好):
7
- * ① tsdown 的浏览器半纯度门拦 Node 内建与平台模块表外的 `@deepseek-ai/*` 值 import——
8
- * 本文件两者都不涉及,且**零运行时依赖**(client 半把它整个 inline 进 bundle);
9
- * ② 跨插件一律 `import type`——本文件干脆没有 import。
10
- *
11
- * 信封(`RemoteResult`)与失败形状(`RemoteFailure`)也在这里——**因为它们本来就是两半的共同契约**:
12
- * Node 半按它产出(`src/api/dispatch.ts` 的失败支就是 `RemoteFailure` 那三件),浏览器半按它判别
13
- * (`src/client/remote.ts` 的 `unwrapRemoteResult` / `failureOf`)。形状取自宿主网关客户端面的原文
14
- * 「每次 unary 调用 resolve 成 RemoteResult、不为载体问题 reject」,但**它是本仓自己的 wire 契约**,
15
- * 不是对宿主模块的 import。
5
+ * 构建约束:本文件零运行时依赖、无 import;浏览器半把整个文件 inline 进 bundle。
6
+ * 信封 {ok:true,value} / {ok:false,error:{code,message,details?}} 也是两半的共同契约。
16
7
  */
17
8
  /**
18
9
  * 插件专属前缀。
@@ -22,30 +13,75 @@
22
13
  * 撞了就是整棵插件树 boot 失败——所以这条路径是与生态的硬边界,不是显示好不好看的问题。
23
14
  */
24
15
  export declare const SESSION_DELETE_API_PREFIX = "/dsh-session-delete-api";
25
- /** 六个段名(相对前缀)。2026-10-17 批量重构:写动词从「一条一段」收成**一段 `/apply`**——
26
- * 一次 HTTP 带一组 id、逐条回结果;单条就是长度为 1 的批量,仓里不再留第二条执行路径。
27
- * - `GET /list`:**对账并读**清单(`session-trash/` 里的目录是准)。
28
- * - `GET /ping`:最便宜的在/不在(不碰清册、不碰盘、不进链)——客户端探测用它,不用一次全表读。
29
- * - `POST /detail`:单条详情(用 POST 表达一个读:判据就写在 `src/api/route-table.ts` 那一行的上方)。
30
- * - `POST /apply`:批量执行一个动作(`delete` / `restore` / `purge` / `count` / `orphan-clean`),逐条回 `ApplyOutcome`。
31
- * - `POST /empty`:清空回收站(聚合回执,**不**按条回)。
32
- * - `GET /orphans`:无主子会话的扫描报告(纯读,2026-10-19)。**清理不在这里**——它是 `/apply` 的
33
- * 第五个动作,这一段只负责"有多少、在哪、多大"。 */
16
+ /** 九个段名(相对前缀)。**每项 `{ path, method }` 两键**:某段用什么方法也是线上形状的一部分
17
+ * (ADR-0003),家就在这一张表——路由记录(`src/api/route-table.ts`)逐行投影(`ROUTES.x.method`),
18
+ * 浏览器半的 GET 段同样投影、写段把 `ROUTES.x.method` 交给 `src/client/remote.ts` 的 `post()`,
19
+ * 两半都不逐段手写一份。
20
+ * 写动作统一走一段 `/apply`:一次 HTTP 带一组 id、逐条回结果;
21
+ * 单条就是长度为 1 的批量,仓里不留第二条执行路径。
22
+ * - `list`:对账并读清单(`session-trash/` 里的目录是准)。
23
+ * - `ping`:最便宜的在/不在(不碰清册、不碰盘、不进链),供客户端探测。
24
+ * - `detail`:单条详情(用 POST 表达一个读,判据在 `src/api/route-table.ts` 的 detail 行)。
25
+ * - `apply`:批量执行一个动作(`delete` / `restore` / `purge` / `count` / `orphan-clean`),逐条回 `ApplyOutcome`。
26
+ * - `empty`:清空回收站(聚合回执,不按条回)。
27
+ * - `orphans`:无主子会话的扫描报告(纯读)。清理是 `/apply` 的第五个动作,
28
+ * 这一段只负责"有多少、在哪、多大"。
29
+ * - `attachment`:附件原始字节(全仓唯一的二进制响应段;失败仍回 JSON 信封)。
30
+ * - `attachmentOrphans`:无引用图片对象的扫描报告(纯读;数量/字节 + 供预览的对象清单)。
31
+ * - `attachmentClean`:清掉报告那一刻无引用的图片对象(聚合回执 `AttachmentCleanupWire`)。 */
34
32
  export declare const ROUTES: {
35
- readonly list: "/list";
33
+ readonly list: {
34
+ readonly path: "/list";
35
+ readonly method: "GET";
36
+ };
36
37
  /** 通道的"在不在"专用:没有入参、没有副作用、没有磁盘读。 */
37
- readonly ping: "/ping";
38
+ readonly ping: {
39
+ readonly path: "/ping";
40
+ readonly method: "GET";
41
+ };
38
42
  /** 单条详情(读回收站目录里那条会话自己的日志)。 */
39
- readonly detail: "/detail";
43
+ readonly detail: {
44
+ readonly path: "/detail";
45
+ readonly method: "POST";
46
+ };
40
47
  /** 一组 id 上的同一个动作;删除确认框那句「含 N 个子会话」也走这里(`action: 'count'`,纯读语义)。 */
41
- readonly apply: "/apply";
48
+ readonly apply: {
49
+ readonly path: "/apply";
50
+ readonly method: "POST";
51
+ };
42
52
  /** 清空回收站。 */
43
- readonly empty: "/empty";
53
+ readonly empty: {
54
+ readonly path: "/empty";
55
+ readonly method: "POST";
56
+ };
44
57
  /** 纯读:扫描回收站之外的无主子会话(父已不在语料/清册)。 */
45
- readonly orphans: "/orphans";
58
+ readonly orphans: {
59
+ readonly path: "/orphans";
60
+ readonly method: "GET";
61
+ };
62
+ /** 附件字节(图片/普通文件):全仓唯一的二进制响应段,读的是宿主附件库。 */
63
+ readonly attachment: {
64
+ readonly path: "/attachment";
65
+ readonly method: "GET";
66
+ };
67
+ /** 纯读:扫全部引用源与附件库 `objects`,报无引用图片的数量、字节与预览清单。 */
68
+ readonly attachmentOrphans: {
69
+ readonly path: "/attachment-orphans";
70
+ readonly method: "GET";
71
+ };
72
+ /** 聚合清理:只删 `objects` 下无引用的图片,回 `AttachmentCleanupWire`。 */
73
+ readonly attachmentClean: {
74
+ readonly path: "/attachment-clean";
75
+ readonly method: "POST";
76
+ };
46
77
  };
78
+ /** 段名 → 那一段的路径。从结构化段名表派生:加一段只在上面加一项,`Record<RouteName, …>` 类型闸
79
+ * (`src/api/route-table.ts`)与运行期键集比对(`tests/route-table.test.ts`)都跟着这一格走。 */
80
+ export type RouteName = (typeof ROUTES)[keyof typeof ROUTES]['path'];
47
81
  /** `/apply` 的五个动作。id 的含义随动作走:`delete`/`count`/`orphan-clean` 是**宿主会话 id**
48
- * (前两个还没进回收站,第三个压根没有回收站行),`restore`/`purge` 是**回收站条目 id**。 */
82
+ * (前两个还没进回收站,第三个压根没有回收站行),`restore`/`purge` 是**回收站条目 id**。
83
+ * 两域都是 string(线上形状);这里不引入品牌类型——错域不靠类型拦,交给各动词在运行期按自己的
84
+ * 语义处理,批处理不猜格式。 */
49
85
  export type ApplyAction = 'delete' | 'restore' | 'purge' | 'count' | 'orphan-clean';
50
86
  /** 每个动作的成功值:`count` 是一个数(拿不到就是 0,界面上那半句整句不出现);
51
87
  * `delete`/`restore` 是回收站那一行(`null` = 这一条已被**同批前一条**带走,幂等 no-op);
@@ -112,14 +148,65 @@ export interface TrashRowWire extends TrashEntryWire {
112
148
  /** 删除时记下的工作区**已经不在账本里**(宿主的「删除工作区」只删注册、保留日志)。 */
113
149
  workspaceMissing?: boolean;
114
150
  }
115
- /** 单条详情里的一条消息(`trash/detail.ts` 读出来的那一份)。 */
116
- export interface TrashDetailMessageWire {
117
- role: 'user' | 'assistant';
151
+ /** 单条详情里的一条附件读数(`trash/attachments.ts` 的 `attachmentsInContent` 产出、详情投影上线)。
152
+ * `attachmentId` 是宿主附件库的 sha256 键,也是 `GET /attachment` 的唯一寻址依据。 */
153
+ export interface TrashDetailAttachmentWire {
154
+ attachmentId: string;
155
+ kind: 'image' | 'file';
156
+ name?: string;
157
+ mediaType?: string;
158
+ bytes: number;
159
+ width?: number;
160
+ height?: number;
161
+ }
162
+ /** 单条详情里的一块正文(`trash/detail.ts` 读出来的那一份)。
163
+ *
164
+ * **判别联合**:`kind` 决定这块怎么画、哪些字段有意义。`attachments` 在每种块上都必填
165
+ * (没有附件就是空数组)——"这一条带没带附件"不靠字段缺席判断。
166
+ * `truncated` / `argsTruncated` / `resultTruncated` 说的是**单块载荷闸**截过一刀;
167
+ * 分页游标(`olderCursor`)管的是"更早的块还没取",两者不是同一件事。
168
+ * 出线的每块**都有内容**(非空正文或至少一个附件;工具块的名字与状态就是内容):判据住
169
+ * `src/trash/detail.ts`,空块不建也不出线,渲染侧不用兜。 */
170
+ export interface TrashDetailTextBlockWire {
171
+ kind: 'user' | 'assistant' | 'reasoning' | 'system' | 'compaction';
172
+ /** 块在整条日志里的稳定序号(分页游标也用它);跨页不重复。 */
173
+ id: string;
118
174
  time: number;
119
175
  text: string;
120
176
  truncated: boolean;
177
+ attachments: TrashDetailAttachmentWire[];
121
178
  }
122
- /** 回收站单条详情的线上形状(`POST /detail`)。 */
179
+ /** 宿主注入的上下文(`user/message` 里 `source.kind !== 'user'` 的那些)。 */
180
+ export interface TrashDetailInjectionBlockWire {
181
+ kind: 'injection';
182
+ id: string;
183
+ time: number;
184
+ /** 日志里的 `source.kind` 原串(`runtime-context` / `agent-instructions` / …;日志里没写就是空串);界面自己映射标签。 */
185
+ sourceKind: string;
186
+ text: string;
187
+ truncated: boolean;
188
+ attachments: TrashDetailAttachmentWire[];
189
+ }
190
+ /** 一次工具调用:`tool/call` 建块、`tool/result` 按 `callId` 回填。
191
+ * `unfinished` = 日志走完也没等到结果(会话可能在工具执行中被杀),不是失败。 */
192
+ export interface TrashDetailToolBlockWire {
193
+ kind: 'tool';
194
+ id: string;
195
+ time: number;
196
+ callId: string;
197
+ name: string;
198
+ status: 'ok' | 'error' | 'unfinished';
199
+ args: string;
200
+ argsTruncated: boolean;
201
+ result: string;
202
+ resultTruncated: boolean;
203
+ attachments: TrashDetailAttachmentWire[];
204
+ }
205
+ export type TrashDetailBlockWire = TrashDetailTextBlockWire | TrashDetailInjectionBlockWire | TrashDetailToolBlockWire;
206
+ /** 回收站单条详情的线上形状(`POST /detail`)。
207
+ *
208
+ * 正文按块分页:`blocks` 是当前这一页(从新往旧取),`olderCursor` 指向更早一页的边界;
209
+ * 计数是**整条日志**的,不受分页影响。 */
123
210
  export interface TrashDetailWire {
124
211
  entryId: string;
125
212
  /** 日志 header 里的原串(**无损**,比从 `projectKey` 反推那一路强;组头怎么念项目名见 `src/client/trash-view.ts` 的 `projectLabel`,那是另一回事)。 */
@@ -131,16 +218,51 @@ export interface TrashDetailWire {
131
218
  models: string[];
132
219
  turnCount: number;
133
220
  userMessageCount: number;
221
+ /** 日志里**每一条** assistant/message 记录(含只带 reasoning / tool-call 的那些)。 */
134
222
  assistantMessageCount: number;
135
223
  toolCallCount: number;
224
+ /** 宿主注入的上下文块数(空文本且无附件的注入不建块,也不计)。 */
136
225
  injectedCount: number;
137
- messages: TrashDetailMessageWire[];
138
- omittedByPayloadCap: number;
226
+ blocks: TrashDetailBlockWire[];
227
+ /** 更早一页的块序号;`null` = 已经到顶。把它原样回传给 `POST /detail` 的 `before`。 */
228
+ olderCursor: number | null;
229
+ /** 跟着这条父条目一起进回收站、并**折进这一行**的孩子(清册里 `parentEntryId` 指向它,
230
+ * 且组语义接得住——折的判据复用 `src/trash/groups.ts` 的 `visibleGroups`),按清册原序。
231
+ * 确有孩子时出线;**缺席 = 没有孩子**,不是空数组。孩子的详情仍按 `entryId` 走同一个 `/detail`。
232
+ * 最多过线 `src/trash/detail.ts` 的 `SUBAGENT_SUMMARY_LIMIT` 条,超出时 `subagentsTotal` 给总数。
233
+ * 这是详情线上唯一带清册事实的一格:孩子不在客户端清单里,没有「按 entryId 与清单配对」
234
+ * 那条路可走(`src/client/remote.ts` 头注的例外说明)。 */
235
+ subagents?: TrashEntryWire[];
236
+ /** 子会话总数;**只在 `subagents` 被条数闸截断时出线**(缺席 = 上面就是全部)。 */
237
+ subagentsTotal?: number;
238
+ }
239
+ /** 一次永久删除顺带、或一次孤儿附件清理的附件读数。`skipped` 有值 = 这一轮**部分或整体没清**:
240
+ * 有候选日志或剩余引用源读不出(原因上屏)。此时 `removed` 仍可能 > 0——损坏的只是某一组候选,
241
+ * 其余照常清;跳过不是失败:`failed` 是逐文件真出错的那几件。 */
242
+ export interface AttachmentCleanupWire {
243
+ removed: number;
244
+ freedBytes: number;
245
+ failed: number;
246
+ skipped?: string;
247
+ }
248
+ /** 孤儿附件报告里的一条:内容哈希 + 实测字节。没有原始文件名(附件库只存内容)。 */
249
+ export interface AttachmentOrphanItemWire {
250
+ attachmentId: string;
251
+ bytes: number;
252
+ }
253
+ /** 孤儿附件报告(`GET /attachment-orphans`):数量、总字节与对象清单。
254
+ * 清单只带 id/bytes(不含路径/文件名),供设置页预览缩略图,最多 200 条;数量与字节按全量算。
255
+ * 读不出任何一处引用源时**不返回这一形状**,而是 `io` 失败——不拿 0 冒充"没有孤儿"。 */
256
+ export interface AttachmentOrphanReportWire {
257
+ count: number;
258
+ totalBytes: number;
259
+ items: AttachmentOrphanItemWire[];
139
260
  }
140
- /** 清空回收站的回执(与旧 `purge` 同形)。 */
261
+ /** 清空回收站的聚合回执。`attachments` 缺席 = 这一版/这一轮没有附件清理读数(不拿零冒充"清过")。 */
141
262
  export interface PurgeStatsWire {
142
263
  removed: number;
143
264
  freedBytes: number;
265
+ attachments?: AttachmentCleanupWire;
144
266
  }
145
267
  /** 无主子会话扫描里**一个项目目录**的汇总(界面上那条"哪一家还剩多少"的读数)。 */
146
268
  export interface OrphanProjectGroupWire {
@@ -157,16 +279,24 @@ export interface OrphanReportWire {
157
279
  missingParents: number;
158
280
  groups: readonly OrphanProjectGroupWire[];
159
281
  }
160
- /** 清理一组无主子会话的回执(与 {@link PurgeStatsWire} 同形,但计的是"删掉的会话目录")。 */
282
+ /** 清理一组无主子会话的回执(与 {@link PurgeStatsWire} 同形,但计的是"删掉的会话目录")。
283
+ * `attachments` 的口径与 purge 相同。 */
161
284
  export interface OrphanCleanStatsWire {
162
285
  removed: number;
163
286
  freedBytes: number;
287
+ attachments?: AttachmentCleanupWire;
164
288
  }
165
- /** 请求体字段名:浏览器半发、Node 半读,同名同义(`empty` 没有入参)。 */
289
+ /** 请求参数名:浏览器半发、Node 半读,同名同义(`empty` 没有入参)。
290
+ * 附件段是 **query 参数**(GET 二进制响应,没有请求体)。 */
166
291
  export declare const PARAMS: {
167
292
  readonly entryId: "entryId";
293
+ /** `/detail` 的分页游标:只取这个块序号之前的块。缺省 = 从尾部取。 */
294
+ readonly before: "before";
168
295
  readonly action: "action";
169
296
  readonly ids: "ids";
297
+ readonly attachmentId: "id";
298
+ readonly attachmentKind: "kind";
299
+ readonly attachmentName: "name";
170
300
  };
171
301
  /**
172
302
  * 失败信封的**线上形状**(本文件):`code` 是判别依据、`message` 是上屏原文、
@@ -198,6 +328,13 @@ export type RemoteResult<T> = {
198
328
  * 浏览器半是 `src/client/remote.ts` 的 `throwRemoteFailure`。
199
329
  */
200
330
  export declare function isDSHRemoteError(value: unknown): value is RemoteFailure;
331
+ /**
332
+ * 运行期判形「这段 JSON 是不是信封」。中间层(反代 / SPA 兜底)会回 `{}`、`{"error":"…"}` 这类
333
+ * 不是信封的 JSON;判形不过就该走「回的不是信封」诊断,而不是把垃圾喂给展开层炸出
334
+ * TypeError / "undefined" 上屏(issue #8 第 1 条)。判据就是两臂的形状:`ok` 必须是布尔,
335
+ * `ok:true` 须带 `value` 键(值可以是 null),`ok:false` 的 `error` 须是带 string `code`/`message` 的对象。
336
+ */
337
+ export declare function isRemoteResult(value: unknown): value is RemoteResult<unknown>;
201
338
  /** 失败码的域。与宿主官方域(`workspace/*`、`session/*`…)不重名,见本文件。 */
202
339
  export declare const SESSION_DELETE_ERROR_DOMAIN = "sessiondelete";
203
340
  /** 五个**动词码**(本文件那张表)。`io` 是唯一兜底。 */
@@ -0,0 +1,92 @@
1
+ import type { FsPort } from '../ports/fs-port.js';
2
+ import { type AttachmentCleanupWire } from '../shared/wire.js';
3
+ export interface ImageRef {
4
+ kind: 'image';
5
+ id: string;
6
+ mediaType?: string;
7
+ bytes: number;
8
+ width?: number;
9
+ height?: number;
10
+ name?: string;
11
+ }
12
+ export interface FileRef {
13
+ kind: 'file';
14
+ id: string;
15
+ name: string;
16
+ bytes: number;
17
+ }
18
+ export type AttachmentRef = ImageRef | FileRef;
19
+ /** 合法附件 ID:形状是 `sha256:` + 64 位小写十六进制。
20
+ * 谓词必须收成**模板字面量形状**而不是 `string`:`attachmentPathOf` 里 `!isAttachmentId(id)` 的
21
+ * 假支若按「是 string」取反会被 TS 收成 `never`(参数本来就是 string),插值就成了 never 读数。
22
+ * 这里判的仍是同一条正则,谓词只是把「形状」写进类型面。 */
23
+ export declare function isAttachmentId(value: unknown): value is `sha256:${string}`;
24
+ /** `statFile` / `readFile` / `sizeOfDir` 里只有 ENOENT 能当"不在";权限、盘挂、I/O 错误都必须露成读不出。 */
25
+ export declare function isMissing(error: unknown): boolean;
26
+ /** 合法附件 ID → 宿主附件库里的绝对路径。`name` 不参与拼接。 */
27
+ export declare function attachmentPathOf(home: string, kind: 'image' | 'file', id: string): string;
28
+ /** 一条消息的 `content[]` 里的附件块;畸形 id / 非附件段一律跳过。 */
29
+ export declare function attachmentsInContent(content: unknown): AttachmentRef[];
30
+ export interface AttachmentReadDeps {
31
+ fs: Pick<FsPort, 'exists' | 'listFiles' | 'listDirs' | 'readFile' | 'statFile' | 'realpath'>;
32
+ home: string;
33
+ }
34
+ export interface AttachmentRemovalDeps extends AttachmentReadDeps {
35
+ fs: Pick<FsPort, 'exists' | 'listFiles' | 'listDirs' | 'readFile' | 'statFile' | 'realpath' | 'removeFile'>;
36
+ }
37
+ /** 一个会话目录里的图片引用;`unreadable` 有值 = 这一组不可知(调用方按"跳过它"处理)。
38
+ *
39
+ * **没有规范 zstd 日志 ≠ 没有引用**:明文日志、别的版本/布局、以及子目录都可能藏着引用。
40
+ * **有规范日志但目录里还有子目录也不够**:日志只覆盖本目录,子目录里的引用读它看不见——
41
+ * 同样按不可读处理(宁可留下对象等维护卡兜底,不冒"没引用")。
42
+ * **规范日志旁的文件也要过关**:老一代规范日志可以并存(新代包含旧代);0 字节临时日志是
43
+ * 写盘未完成的空产物,也没有引用。非空临时日志、明文日志或其他任何文件仍按不可读处理(fail closed)。
44
+ * **宿主的内核写锁不是会话内容**:0 字节的锁在判目录形状之前先剥掉——不然每个已实体化的会话目录
45
+ * (含搬进回收站的条目)都会被读成"读不懂",整份引用扫描在真机上恒跳过;没有日志的锁目录因此与
46
+ * 空目录同判。非空的锁说明我们对这个文件的理解失效(宿主不会往里写字节),仍按不可读处理。
47
+ */
48
+ export declare function imageIdsOfDir(deps: AttachmentReadDeps, dir: string): Promise<{
49
+ ids: Set<string>;
50
+ unreadable?: string;
51
+ }>;
52
+ interface PlannedImageRemoval {
53
+ id: string;
54
+ path: string;
55
+ bytes: number;
56
+ }
57
+ export interface ImageRemovalPlan {
58
+ entries: readonly PlannedImageRemoval[];
59
+ bytes: number;
60
+ skipped?: string;
61
+ }
62
+ /**
63
+ * 独占计划:候选 = 本次被删会话日志里的图片 id;剩余引用 = 活动会话 + 全部未删 `session-trash`
64
+ * 目录 + storages/cache。
65
+ * 剩余引用源读不出 ⇒ 整体跳过;候选日志读不出 / 附件对象 stat 失败 ⇒ 只跳过那一部分,其余照常。
66
+ */
67
+ export declare function planImageAttachmentRemoval(deps: AttachmentReadDeps, removedDirs: readonly string[]): Promise<ImageRemovalPlan>;
68
+ /**
69
+ * 逐文件删;ENOENT 由端口的 `force` 吞掉;其他错误计数并继续。
70
+ *
71
+ * 删除动作只认「`home` + `id` 自己推导出来的那一条路径」:计划是数据,手搭的/被改过的条目
72
+ * 只要路径与推导不一致就拒绝(计数为失败,不删),源文件因此没有删除入口。
73
+ */
74
+ export declare function executeImageAttachmentRemoval(deps: AttachmentRemovalDeps, plan: ImageRemovalPlan): Promise<{
75
+ removed: number;
76
+ freedBytes: number;
77
+ failed: number;
78
+ }>;
79
+ /**
80
+ * 扫「当前没有任何引用源引用」的图片对象。
81
+ *
82
+ * 引用侧与删除计划共用同一条判据:活动会话 + 全部 `session-trash` 目录 + `storages/cache` JSON。
83
+ * 任何一处读不出 ⇒ 整份报告跳过(fail closed),绝不把"读不出"折成"没有引用"。
84
+ * 候选侧只认 `attachments/v1/objects` 下 `<2 hex>/<64 hex>` 这一种真实形状:`file-objects` /
85
+ * `tmp` / `v2` / `cache` 与非规范名字一律不进候选——清理只处理 `.dsh` 内的图片副本。
86
+ *
87
+ * 对象列表或 stat 读不出同样整份跳过:报告宁可失败,也不给一份"少算了一些"的清理计划。
88
+ */
89
+ export declare function scanOrphanImageAttachments(deps: AttachmentReadDeps): Promise<ImageRemovalPlan>;
90
+ /** 清掉扫描那一刻无引用的图片对象;扫描跳过 ⇒ 零删,并原样带回跳过的原因。 */
91
+ export declare function cleanOrphanImageAttachments(deps: AttachmentRemovalDeps): Promise<AttachmentCleanupWire>;
92
+ export {};
@@ -1,25 +1,55 @@
1
- import type { TrashLedger } from './ledger.js';
1
+ /**
2
+ * 回收站**单条详情**:从回收站目录里那条会话自己的日志读出元信息与正文块(用例见 `tests/trash-detail.test.ts`)。
3
+ *
4
+ * 三层里的策略层——编解码在 `src/log/*`,这里只回答"给谁看、给多少、算不算失败"。
5
+ *
6
+ * 四条硬口径,都不是口味:
7
+ * ① **寻址只认清册(raw 索引)**:`entryId` 先在对账之外的 raw 索引里查,目录一律由 `trashDirOf(record.id, home)` 推
8
+ * ⇒ 外部输入进不了文件系统寻址(不给"传个路径就读任意目录"留缝)。
9
+ * ② **正文是块,不是消息**:`user/message` 按 `source.kind` 拆成 user / injection;`assistant/message.content`
10
+ * 按 `text` / `reasoning` 拆块,附件并进相邻的文字块;`tool/call` 建工具块、`tool/result` 按 `callId` 回填;
11
+ * `system/message` 与 `compaction/summary` 各成一块。turn/step/job/request 这类簿记事件不进正文——它们不是
12
+ * "用户想看的对话",铺进来只会把真正的提问和回复淹掉。
13
+ * ③ **分页是游标,不是总量闸**:一次只回尾部一页,`olderCursor` 指页首;客户端把游标原样回传取更早一页。
14
+ * 页有两道闸:块数 `pageBlocks` 与正文总量 `pageChars`(单块超字符闸仍保留一块,游标才能前进);
15
+ * 每块另有 `perBlockChars` 的单块闸(正文与工具参数/结果各自算),超了标 `truncated`。
16
+ * 构建时用**有界缓冲只留当前页**,不把整条日志的正文堆在内存里;工具结果可能落在游标之后,
17
+ * 仍会回填到页内那个调用块(别把"已完成"误报成"未完成")。
18
+ * ④ **计数与正文同源过滤、各过各的闸**:`userMessageCount` / `injectedCount` 只数建了块的;`assistantMessageCount`
19
+ * 数**每一条** assistant/message(含只带 reasoning / tool-call 的);`toolCallCount` 数每一条 tool/call。
20
+ * 计数是整条日志的,不受分页与任何字符闸影响。
21
+ *
22
+ * 附件随块一起收(`attachmentsInContent`):每块最多 20、每页最多 200;超出的**静默丢**,不另立计数——
23
+ * "少了几张图"与"这块被截断"不是同一件事,拿一个数兼报会让两个数都对不上。
24
+ */
25
+ import { type TrashEntry } from './entry.js';
2
26
  import type { FsPort } from '../ports/fs-port.js';
3
- import type { TrashDetailMessageWire, TrashDetailWire } from '../shared/wire.js';
4
- /** 每条文字的上限(字符)。超出即截断并置 `truncated`。 */
5
- export declare const PER_MESSAGE_CHAR_CAP = 3000;
6
- /** 一次回信的正文总量上限(字符)。 */
7
- export declare const TOTAL_CHAR_CAP = 500000;
8
- /** 详情正文里的一条消息 / 整份详情:**线上形状的家在 `shared/wire.ts`**(ADR-0003)——
9
- * Node 半产出它,浏览器半按同一份类型消费,这里只留旧名做别名。 */
10
- export type TrashDetailMessage = TrashDetailMessageWire;
27
+ import type { TrashDetailWire } from '../shared/wire.js';
28
+ /** 每块文字的上限(字符)。正文、工具参数、工具结果各自吃这一刀。 */
29
+ export declare const PER_BLOCK_CHAR_CAP = 16000;
30
+ /** 一页最多多少块。 */
31
+ export declare const PAGE_BLOCK_LIMIT = 30;
32
+ /** 一页正文的总字符上限(块按日志顺序累计;单块超闸时仍保留一块)。 */
33
+ export declare const PAGE_CHAR_LIMIT = 500000;
34
+ /** 子会话摘要最多列多少条;超出时只过线前 N 条,总数另走 `subagentsTotal`。 */
35
+ export declare const SUBAGENT_SUMMARY_LIMIT = 200;
11
36
  export type TrashDetail = TrashDetailWire;
12
37
  export type DetailDeps = {
13
38
  /** 详情自己直接要的三件(判目录在不在、挑日志、读日志)。对账不在这条路上,所以不需要 `listDirs`。 */
14
39
  fs: Pick<FsPort, 'exists' | 'listFiles' | 'readFile'>;
15
- /** 详情只读清册的 **raw 面**:`/detail` 声明为 pure,读一次不能顺手对账——对账会写清册行,那是写动作,
16
- * 必须排在到达序链上。代价是有意的:只有目录、清册还没这一行的手工搬入条目读不到(`/list` 对账后可见)。 */
17
- ledger: Pick<TrashLedger, 'raw'>;
40
+ /** 路由交来的清册 **raw 行**:`/detail` 声明为 pure,读一次不能顺手对账——对账会写清册行,那是写动作,
41
+ * 必须排在到达序链上。读面由路由记录选好(`read: 'raw'`),这里只消费行,不再自己挑 `list()` / `raw()`。
42
+ * 代价是有意的:只有目录、清册还没这一行的手工搬入条目读不到(`/list` 对账后可见)。 */
43
+ rows: () => Promise<readonly TrashEntry[]>;
18
44
  home: string;
19
45
  };
20
- /** 两个载荷闸(可调是为把策略单独验穿;生产走的就是这两个默认值,用例里钉着)。 */
46
+ /** 四个分页/截断参数(可调是为把策略单独验穿;生产走的就是这四个默认值,用例里钉着)。 */
21
47
  export interface DetailLimits {
22
- perMessageChars: number;
23
- totalChars: number;
48
+ perBlockChars: number;
49
+ pageBlocks: number;
50
+ /** 一页正文的总字符闸;缺省 `PAGE_CHAR_LIMIT`。 */
51
+ pageChars?: number;
52
+ /** 子会话摘要的条数闸;缺省 `SUBAGENT_SUMMARY_LIMIT`。 */
53
+ subagents?: number;
24
54
  }
25
- export declare function readTrashDetail(deps: DetailDeps, limits?: DetailLimits): (entryId: string) => Promise<TrashDetail>;
55
+ export declare function readTrashDetail(deps: DetailDeps, limits?: DetailLimits): (entryId: string, before?: number) => Promise<TrashDetail>;
@@ -1,6 +1,8 @@
1
+ import { type TrashEntryWire } from '../shared/wire.js';
1
2
  /** 回收站的一条记录。`id` 由 `makeEntryId` 生成(`<删除时刻>-<原目录名>`)——**它不是随手起的键**:
2
3
  * 回收站目录名就是它,恢复也按它反推出原目录名放回(CONTEXT.md 的「条目」条),所以 id 的形状有契约意义。
3
- * 其余字段在删除那一刻就地取。 */
4
+ * 目录名里的空白/引号照原样进 id(往返契约压倒 HTML 合法性——DOM id 的编码归浏览器半的
5
+ * `trash-panel.tsx`)。其余字段在删除那一刻就地取。 */
4
6
  export interface TrashEntry {
5
7
  id: string;
6
8
  sessionId: string;
@@ -8,8 +10,8 @@ export interface TrashEntry {
8
10
  /** 删除时它所属的工作区;**空串 = 删除时就没有账目**(「未分组」的会话,宿主的"删除工作区"会造出这种),
9
11
  * 恢复时据此跳过 `attach`。 */
10
12
  workspaceId: string;
11
- /** 删除**之前**的归档态,**只作事实记录**:2026-10-21 起删除不归档、恢复也不按它还原可见性。
12
- * 旧清单迁移只用它区分"删除时是我们归档的"(false 且当前仍归档)与"用户删前自己归档的"(true,保持归档)。 */
13
+ /** 删除前的归档态,只作事实记录:删除不再改变归档,恢复也不按它还原可见性。
14
+ * 旧条目迁移用它区分“删除时被我们归档的”(false 且当前仍归档)与“用户删前自己归档的”(true)。 */
13
15
  wasArchived: boolean;
14
16
  deletedAt: number;
15
17
  /** 删除时**尽力**取的会话标题(来源:宿主 projection cache 服务;底层是
@@ -19,7 +21,7 @@ export interface TrashEntry {
19
21
  sizeBytes: number;
20
22
  /** **组语义**:这一条是跟着父一起进回收站的子会话 ⇒ 记父那一行的 `id`。
21
23
  * 缺席 = 这一行是父(或它自己就是被单独删掉的)。清单据此把孩子折进父行、不单独列。
22
- * 老代留底 / 清册被清后由目录重建出来的行**没有这个字段**(文件侧没有它的副本,与
24
+ * 清册被清后由目录重建出来的行没有这个字段(文件侧没有它的副本,与
23
25
  * `workspaceId`/`wasArchived`/`title` 同一类降级),那一趟它们会各占一行——那是"分组信息随清册一起没了"
24
26
  * 的如实结果,不假装知道(CONTEXT.md 的「无主子会话」条)。 */
25
27
  parentEntryId?: string;
@@ -43,6 +45,10 @@ export declare const REQUIRED_ENTRY_FIELDS: {
43
45
  readonly title: "string";
44
46
  readonly sizeBytes: "number";
45
47
  };
48
+ /** 清册行 → 线上清册行形状。**显式字面量、不用 `...row`**:线上多一格必填时这里编译不过。
49
+ * 只搬清册里真有的事实、不带任何只读数;`/list` 的 `toWire` 与详情的子会话摘要共用这一份
50
+ * (否则同一条清册行会有两份会漂移的投影)。 */
51
+ export declare function entryWire(row: TrashEntry): TrashEntryWire;
46
52
  export declare function makeEntryId(deletedAt: number, sessionDirName: string): string;
47
53
  /** `makeEntryId` 的逆。时间戳按 UTC 拆字段再 `Date.UTC` 合回——不交给 `Date.parse`
48
54
  * 去猜(`20260930T130500Z` 不是任何被承认的日期格式,猜出来的是 NaN)。 */
@@ -1,17 +1,10 @@
1
1
  /**
2
- * **组语义的唯一家**(ADR-0001):回收站里「这一组有谁」以及「哪些行在清单里露面」。
2
+ * 组语义的唯一家(ADR-0001):回收站里「这一组有谁」以及「哪些行在清单里露面」。
3
3
  *
4
- * 此前这条关系在四个地方各算一遍,而且已经算出两种答案:
5
- * `assembleTrashRows` 与 `restoreSession` 都认为「父自己也是孩子时,它接不住孩子」,
6
- * 而 `purgeTrash` 的 `id === x || parentEntryId === x` 不认这条 ⇒ 嵌套组上它会连带删掉不属于这一组的孙代
7
- * (不可恢复)。判据原文只在这里写一次:
4
+ * 接得住孩子的只有组根——自己不记 `parentEntryId` 的那一行。父行不在、或父行自己也是孩子时,
5
+ * 孩子必须自己占一行,否则它的字节会被头部合计数两遍,而它自己既看不见也清不掉。
8
6
  *
9
- * **接得住孩子的只有组根**——自己不记 `parentEntryId` 的那一行。父行不在、或父行自己也是孩子时,
10
- * 孩子必须自己占一行,否则它的字节会被头部合计数两遍(而它自己既看不见也清不掉)。
11
- *
12
- * 三个函数都是**纯函数**、都吃一份快照:`/apply` 那条路会在同一批里多次重问;批量层持有一份
13
- * 会收缩的当前视图(`verbs/batch-carried.ts`),收缩时机归调用方——groups module 不持有可变状态,
14
- * 也就没有"这份快照谁负责更新"的第二套契约。
7
+ * 这些函数都是纯函数、都吃一份快照;什么时候收缩、把收缩后的视图存在哪里归调用方,本模块不持有可变状态。
15
8
  */
16
9
  import type { TrashEntry } from './entry.js';
17
10
  /** 一条露面的组根,连同它接住的孩子(顺序与清册一致)。 */
@@ -36,6 +29,11 @@ export declare function visibleGroups(rows: readonly TrashEntry[]): TrashGroupVi
36
29
  * 调用方据此报 `trash-empty` / 判"已被同批前一条带走"。
37
30
  */
38
31
  export declare function groupOf(rows: readonly TrashEntry[], id: string): TrashEntry[];
32
+ /**
33
+ * 把一条 id 所属的**整组**从快照里摘掉,回一份新快照(组语义仍只读 `groupOf`,这一条判据不另写一遍)。
34
+ * 「同批前一条已把这一组带走」的收缩就是它:调用方各自持有那份可变视图,本函数不记账。
35
+ */
36
+ export declare function withoutGroup(rows: readonly TrashEntry[], id: string): TrashEntry[];
39
37
  /** 一条 id 在「原始快照 vs 当前视图」里的三态。判别联合:**只有 `fresh` 带当前这一组**——
40
38
  * 组语义只对"还在"的 id 有意义,carried / absent 没有可交的组,调用方也就无从误用。 */
41
39
  export type GroupClaim = {
@@ -2,11 +2,11 @@ import type { TrashEntry } from './entry.js';
2
2
  /** 回收站清单的 **raw 索引端口**:只给 `src/trash/ledger.ts` 与对账实现;动词与路由只认 ledger,
3
3
  * 不认 storage domain 的形状。`list()` 不保证与 `session-trash/` 目录一致(对账在 ledger 的 `list()`)。 */
4
4
  export interface ManifestPort {
5
- list(): Promise<TrashEntry[]>;
6
- add(e: TrashEntry): Promise<void>;
7
- remove(id: string): Promise<void>;
5
+ /** 属性声明:`list` 会被摘下来转交给对账(`manifest: { list: manifest.list, … }`),实现不读 `this`。 */
6
+ list: () => Promise<TrashEntry[]>;
7
+ add: (e: TrashEntry) => Promise<void>;
8
+ remove: (id: string) => Promise<void>;
8
9
  }
9
- export declare function createMemoryManifest(seed?: TrashEntry[]): ManifestPort;
10
10
  /** 宿主 storage domain 的最小面。这是**接缝**,不是对宿主 API 的断言:
11
11
  * 探针只枚举到 `ctx.storageDomain` 有 `open` / `get` / `domains` / `reserved` 等成员,
12
12
  * **没有**枚举 `open()` 返回的 domain 句柄面 ⇒ 真实绑定由 `src/trash/storage-manifest.ts` 的
@@ -4,7 +4,7 @@ import { type ManifestPort } from './manifest.js';
4
4
  * 清单的真实绑定:官方 storage domain → `ManifestPort`(`StorageDomainLike` 在动词层保持一种读法)。
5
5
  * 域**懒开**且成功句柄只开一次(`apply` 是同步的,而 `open()` 是异步的),并挂一条 `ctx.effect` 清理:
6
6
  * 插件卸载时关掉句柄并把 `close()` 的落地交回框架(句柄归调用方所有,这是 `open()` 的 JSDoc 明写的契约;
7
- * 2026-10-17 起不再 `void` 走掉——没关严的句柄会让下一份同名域撞 `already-open`)。
7
+ * 不能 `void` 走掉——没关严的句柄会让下一份同名域撞 `already-open`)。
8
8
  *
9
9
  * **失败不缓存**:`open()` 被拒是瞬态(域被别的持有者占着、装载竞态),把第一次的 rejected promise
10
10
  * 缓存起来等于"这个会话永远删不掉"——下一次读会重新走 `open()`,成功才落下活句柄。
@@ -3,8 +3,6 @@ import { type GroupClaim } from '../trash/groups.js';
3
3
  export interface EntryBatchCarried {
4
4
  claim(id: string): GroupClaim;
5
5
  take(id: string): void;
6
- /** 收缩中的当前视图(`take` 过的那一组已经不在里面)。 */
7
- rows(): readonly TrashEntry[];
8
6
  }
9
7
  /** session 域没有组可交:claim 只回答 fresh / carried。 */
10
8
  export type SessionClaim = {
@@ -14,7 +12,7 @@ export interface SessionBatchCarried {
14
12
  claim(id: string): SessionClaim;
15
13
  take(id: string): void;
16
14
  }
17
- /** 恢复 / 彻底删除:原始快照认得、当前视图已经空了 ⇒ 同批前一条带走的。 */
15
+ /** 恢复:原始快照认得、当前视图已经空了 ⇒ 同批前一条带走的。 */
18
16
  export declare function createEntryCarried(original: readonly TrashEntry[]): EntryBatchCarried;
19
17
  /** 删除:会话 id 域;`take` 只收这次真的搬走的成员(父 + 盘上的孩子)。 */
20
18
  export declare function createSessionCarried(): SessionBatchCarried;
@@ -1,5 +1,5 @@
1
1
  import { type TrashEntry } from '../trash/entry.js';
2
- import type { DeleteDeps } from '../deps.js';
2
+ import type { DeleteSessionDeps } from '../deps.js';
3
3
  export interface DeleteResult {
4
4
  readonly entry: TrashEntry;
5
5
  /** 这一次真的搬进回收站的成员(父 + 盘上存在的孩子);批量层用它建同批已带走集。 */
@@ -22,4 +22,4 @@ export interface DeleteResult {
22
22
  * 组语义归 `trash/groups.ts`)。
23
23
  * 结果带回这次真的搬走的整组(`movedSessionIds`)——它是动词交回的内部数据,不再靠回调借用动词的手。
24
24
  */
25
- export declare function deleteSession(deps: DeleteDeps): (sessionId: string) => Promise<DeleteResult>;
25
+ export declare function deleteSession(deps: DeleteSessionDeps): (sessionId: string) => Promise<DeleteResult>;
@@ -9,6 +9,8 @@ export interface VerbError extends Error {
9
9
  cause?: unknown;
10
10
  }
11
11
  export declare function verbError(code: VerbCode, message: string, cause?: unknown, details?: Record<string, unknown>): VerbError;
12
+ /** `trash-empty` 的原文:有具体条目就点名那个 id,拿不到时只报通用那句。 */
13
+ export declare function trashEmpty(entryId?: string): VerbError;
12
14
  /** 路由自检用的失败:带 HTTP 状态语义,**不**冒充五个动词码。 */
13
15
  export declare function routeError(code: RouteCode, httpStatus: number, message: string): VerbError & {
14
16
  httpStatus: number;
@@ -1,12 +1,9 @@
1
1
  /**
2
- * 活体准入:一次删除 / 永久删除 / 孤儿清理之前,逐个问「这个会话在宿主内存里 attach 着吗」——
3
- * 任一个在跑就整组拒绝、零副作用(词表见 CONTEXT.md 的「活体」条;裁决见 ADR-0008)。
2
+ * 活体准入:删除 / 永久删除 / 孤儿清理之前,逐个问会话是否在宿主内存里 attach 着;
3
+ * 任一在跑就整组拒绝、零副作用(ADR-0008)。
4
4
  *
5
- * 为什么单独一个 module:删除的逐个成员与永久删除计划的 targets + 盘上后代,此前各手写一遍同一条
6
- * 循环、同一句原文。两处只要有一处改了措辞或漏查一个 id,客户端拿到的就是两种「还在跑」。
7
- *
8
- * 边界:**什么时候查归调用方**。永久删除的准入仍住在计划里、在任何写之前(ADR-0006:计划就是执行前的闸);
9
- * 这个 module 只拥有「查什么、怎么判、怎么说」。
5
+ * 边界:什么时候查归调用方——永久删除在计划里查一次、执行器第一笔写前用计划里的会话名单再查一次(ADR-0027);
6
+ * 本模块只拥有「查什么、怎么判、怎么说」。
10
7
  */
11
8
  import type { HostIdentityPort } from '../ports/host-port.js';
12
9
  /** 逐个问,任一在跑就抛 `live`。名单的挑选(哪些成员算这一组)归调用方,这里只按传入顺序问完。 */