@xrn1997/dsh-session-delete 0.4.0 → 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.
- package/LICENSE +201 -201
- package/README.md +141 -135
- package/cordis.patch.yml +3 -3
- package/lib/client.js +1477 -563
- package/lib/index.js +1230 -755
- package/lib/types/api/dispatch.d.ts +6 -2
- package/lib/types/api/route-table.d.ts +29 -9
- package/lib/types/api/trash-rows.d.ts +3 -3
- package/lib/types/client/busy-gate.d.ts +6 -2
- package/lib/types/client/confirm-close.d.ts +4 -1
- package/lib/types/client/context.d.ts +3 -2
- package/lib/types/client/copy.d.ts +54 -4
- package/lib/types/client/delete-confirm.d.ts +1 -1
- package/lib/types/client/delete-menu-item.d.ts +1 -1
- package/lib/types/client/host-rows.d.ts +5 -1
- package/lib/types/client/markdown.d.ts +6 -7
- package/lib/types/client/orphan-maintenance.d.ts +10 -25
- package/lib/types/client/receipt.d.ts +3 -1
- package/lib/types/client/remote.d.ts +7 -3
- package/lib/types/client/store.d.ts +14 -3
- package/lib/types/client/trash-detail.d.ts +48 -6
- package/lib/types/client/trash-entry.d.ts +1 -1
- package/lib/types/client/trash-nav.d.ts +12 -8
- package/lib/types/client/ui/Toast.d.ts +3 -1
- package/lib/types/client/ui/icons.d.ts +1 -1
- package/lib/types/client/undo-toast.d.ts +2 -1
- package/lib/types/client/use-maintenance-section.d.ts +52 -0
- package/lib/types/client/use-maintenance.d.ts +7 -35
- package/lib/types/client/use-trash-panel-controller.d.ts +35 -14
- package/lib/types/client/use-trash-query.d.ts +10 -8
- package/lib/types/client/use-trash-selection.d.ts +15 -12
- package/lib/types/client/write-seats.d.ts +32 -16
- package/lib/types/client/write-settlement.d.ts +1 -1
- package/lib/types/client/write.d.ts +13 -3
- package/lib/types/domain/closure.d.ts +44 -0
- package/lib/types/log/zstd-frames.d.ts +2 -3
- package/lib/types/ports/fs-port.d.ts +15 -12
- package/lib/types/ports/host-port.d.ts +4 -5
- package/lib/types/ports/presence-port.d.ts +9 -0
- package/lib/types/shared/wire.d.ts +119 -26
- package/lib/types/trash/attachments.d.ts +8 -1
- package/lib/types/trash/detail.d.ts +32 -22
- package/lib/types/trash/entry.d.ts +7 -1
- package/lib/types/trash/groups.d.ts +6 -1
- package/lib/types/trash/manifest.d.ts +4 -4
- package/lib/types/verbs/batch-carried.d.ts +1 -3
- package/lib/types/verbs/live-admission.d.ts +1 -1
- package/lib/types/verbs/member-presence.d.ts +3 -1
- package/lib/types/verbs/move-group.d.ts +2 -1
- package/lib/types/verbs/orphan-subagents.d.ts +11 -5
- package/lib/types/verbs/permanent-removal.d.ts +39 -10
- package/lib/types/verbs/trash-scope.d.ts +36 -0
- package/package.json +117 -107
- package/lib/types/verbs/purge-trash.d.ts +0 -35
|
@@ -13,37 +13,71 @@
|
|
|
13
13
|
* 撞了就是整棵插件树 boot 失败——所以这条路径是与生态的硬边界,不是显示好不好看的问题。
|
|
14
14
|
*/
|
|
15
15
|
export declare const SESSION_DELETE_API_PREFIX = "/dsh-session-delete-api";
|
|
16
|
-
/**
|
|
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、逐条回结果;
|
|
17
21
|
* 单条就是长度为 1 的批量,仓里不留第二条执行路径。
|
|
18
|
-
* - `
|
|
19
|
-
* - `
|
|
20
|
-
* - `
|
|
21
|
-
* - `
|
|
22
|
-
* - `
|
|
23
|
-
* - `
|
|
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` 的第五个动作,
|
|
24
28
|
* 这一段只负责"有多少、在哪、多大"。
|
|
25
|
-
* - `
|
|
26
|
-
* - `
|
|
27
|
-
* - `
|
|
29
|
+
* - `attachment`:附件原始字节(全仓唯一的二进制响应段;失败仍回 JSON 信封)。
|
|
30
|
+
* - `attachmentOrphans`:无引用图片对象的扫描报告(纯读;数量/字节 + 供预览的对象清单)。
|
|
31
|
+
* - `attachmentClean`:清掉报告那一刻无引用的图片对象(聚合回执 `AttachmentCleanupWire`)。 */
|
|
28
32
|
export declare const ROUTES: {
|
|
29
|
-
readonly list:
|
|
33
|
+
readonly list: {
|
|
34
|
+
readonly path: "/list";
|
|
35
|
+
readonly method: "GET";
|
|
36
|
+
};
|
|
30
37
|
/** 通道的"在不在"专用:没有入参、没有副作用、没有磁盘读。 */
|
|
31
|
-
readonly ping:
|
|
38
|
+
readonly ping: {
|
|
39
|
+
readonly path: "/ping";
|
|
40
|
+
readonly method: "GET";
|
|
41
|
+
};
|
|
32
42
|
/** 单条详情(读回收站目录里那条会话自己的日志)。 */
|
|
33
|
-
readonly detail:
|
|
43
|
+
readonly detail: {
|
|
44
|
+
readonly path: "/detail";
|
|
45
|
+
readonly method: "POST";
|
|
46
|
+
};
|
|
34
47
|
/** 一组 id 上的同一个动作;删除确认框那句「含 N 个子会话」也走这里(`action: 'count'`,纯读语义)。 */
|
|
35
|
-
readonly apply:
|
|
48
|
+
readonly apply: {
|
|
49
|
+
readonly path: "/apply";
|
|
50
|
+
readonly method: "POST";
|
|
51
|
+
};
|
|
36
52
|
/** 清空回收站。 */
|
|
37
|
-
readonly empty:
|
|
53
|
+
readonly empty: {
|
|
54
|
+
readonly path: "/empty";
|
|
55
|
+
readonly method: "POST";
|
|
56
|
+
};
|
|
38
57
|
/** 纯读:扫描回收站之外的无主子会话(父已不在语料/清册)。 */
|
|
39
|
-
readonly orphans:
|
|
58
|
+
readonly orphans: {
|
|
59
|
+
readonly path: "/orphans";
|
|
60
|
+
readonly method: "GET";
|
|
61
|
+
};
|
|
40
62
|
/** 附件字节(图片/普通文件):全仓唯一的二进制响应段,读的是宿主附件库。 */
|
|
41
|
-
readonly attachment:
|
|
63
|
+
readonly attachment: {
|
|
64
|
+
readonly path: "/attachment";
|
|
65
|
+
readonly method: "GET";
|
|
66
|
+
};
|
|
42
67
|
/** 纯读:扫全部引用源与附件库 `objects`,报无引用图片的数量、字节与预览清单。 */
|
|
43
|
-
readonly attachmentOrphans:
|
|
68
|
+
readonly attachmentOrphans: {
|
|
69
|
+
readonly path: "/attachment-orphans";
|
|
70
|
+
readonly method: "GET";
|
|
71
|
+
};
|
|
44
72
|
/** 聚合清理:只删 `objects` 下无引用的图片,回 `AttachmentCleanupWire`。 */
|
|
45
|
-
readonly attachmentClean:
|
|
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
82
|
* (前两个还没进回收站,第三个压根没有回收站行),`restore`/`purge` 是**回收站条目 id**。
|
|
49
83
|
* 两域都是 string(线上形状);这里不引入品牌类型——错域不靠类型拦,交给各动词在运行期按自己的
|
|
@@ -125,16 +159,54 @@ export interface TrashDetailAttachmentWire {
|
|
|
125
159
|
width?: number;
|
|
126
160
|
height?: number;
|
|
127
161
|
}
|
|
128
|
-
/**
|
|
129
|
-
*
|
|
130
|
-
|
|
131
|
-
|
|
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;
|
|
174
|
+
time: number;
|
|
175
|
+
text: string;
|
|
176
|
+
truncated: boolean;
|
|
177
|
+
attachments: TrashDetailAttachmentWire[];
|
|
178
|
+
}
|
|
179
|
+
/** 宿主注入的上下文(`user/message` 里 `source.kind !== 'user'` 的那些)。 */
|
|
180
|
+
export interface TrashDetailInjectionBlockWire {
|
|
181
|
+
kind: 'injection';
|
|
182
|
+
id: string;
|
|
132
183
|
time: number;
|
|
184
|
+
/** 日志里的 `source.kind` 原串(`runtime-context` / `agent-instructions` / …;日志里没写就是空串);界面自己映射标签。 */
|
|
185
|
+
sourceKind: string;
|
|
133
186
|
text: string;
|
|
134
187
|
truncated: boolean;
|
|
135
188
|
attachments: TrashDetailAttachmentWire[];
|
|
136
189
|
}
|
|
137
|
-
/**
|
|
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
|
+
* 计数是**整条日志**的,不受分页影响。 */
|
|
138
210
|
export interface TrashDetailWire {
|
|
139
211
|
entryId: string;
|
|
140
212
|
/** 日志 header 里的原串(**无损**,比从 `projectKey` 反推那一路强;组头怎么念项目名见 `src/client/trash-view.ts` 的 `projectLabel`,那是另一回事)。 */
|
|
@@ -146,11 +218,23 @@ export interface TrashDetailWire {
|
|
|
146
218
|
models: string[];
|
|
147
219
|
turnCount: number;
|
|
148
220
|
userMessageCount: number;
|
|
221
|
+
/** 日志里**每一条** assistant/message 记录(含只带 reasoning / tool-call 的那些)。 */
|
|
149
222
|
assistantMessageCount: number;
|
|
150
223
|
toolCallCount: number;
|
|
224
|
+
/** 宿主注入的上下文块数(空文本且无附件的注入不建块,也不计)。 */
|
|
151
225
|
injectedCount: number;
|
|
152
|
-
|
|
153
|
-
|
|
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;
|
|
154
238
|
}
|
|
155
239
|
/** 一次永久删除顺带、或一次孤儿附件清理的附件读数。`skipped` 有值 = 这一轮**部分或整体没清**:
|
|
156
240
|
* 有候选日志或剩余引用源读不出(原因上屏)。此时 `removed` 仍可能 > 0——损坏的只是某一组候选,
|
|
@@ -206,6 +290,8 @@ export interface OrphanCleanStatsWire {
|
|
|
206
290
|
* 附件段是 **query 参数**(GET 二进制响应,没有请求体)。 */
|
|
207
291
|
export declare const PARAMS: {
|
|
208
292
|
readonly entryId: "entryId";
|
|
293
|
+
/** `/detail` 的分页游标:只取这个块序号之前的块。缺省 = 从尾部取。 */
|
|
294
|
+
readonly before: "before";
|
|
209
295
|
readonly action: "action";
|
|
210
296
|
readonly ids: "ids";
|
|
211
297
|
readonly attachmentId: "id";
|
|
@@ -242,6 +328,13 @@ export type RemoteResult<T> = {
|
|
|
242
328
|
* 浏览器半是 `src/client/remote.ts` 的 `throwRemoteFailure`。
|
|
243
329
|
*/
|
|
244
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>;
|
|
245
338
|
/** 失败码的域。与宿主官方域(`workspace/*`、`session/*`…)不重名,见本文件。 */
|
|
246
339
|
export declare const SESSION_DELETE_ERROR_DOMAIN = "sessiondelete";
|
|
247
340
|
/** 五个**动词码**(本文件那张表)。`io` 是唯一兜底。 */
|
|
@@ -16,7 +16,11 @@ export interface FileRef {
|
|
|
16
16
|
bytes: number;
|
|
17
17
|
}
|
|
18
18
|
export type AttachmentRef = ImageRef | FileRef;
|
|
19
|
-
|
|
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}`;
|
|
20
24
|
/** `statFile` / `readFile` / `sizeOfDir` 里只有 ENOENT 能当"不在";权限、盘挂、I/O 错误都必须露成读不出。 */
|
|
21
25
|
export declare function isMissing(error: unknown): boolean;
|
|
22
26
|
/** 合法附件 ID → 宿主附件库里的绝对路径。`name` 不参与拼接。 */
|
|
@@ -37,6 +41,9 @@ export interface AttachmentRemovalDeps extends AttachmentReadDeps {
|
|
|
37
41
|
* 同样按不可读处理(宁可留下对象等维护卡兜底,不冒"没引用")。
|
|
38
42
|
* **规范日志旁的文件也要过关**:老一代规范日志可以并存(新代包含旧代);0 字节临时日志是
|
|
39
43
|
* 写盘未完成的空产物,也没有引用。非空临时日志、明文日志或其他任何文件仍按不可读处理(fail closed)。
|
|
44
|
+
* **宿主的内核写锁不是会话内容**:0 字节的锁在判目录形状之前先剥掉——不然每个已实体化的会话目录
|
|
45
|
+
* (含搬进回收站的条目)都会被读成"读不懂",整份引用扫描在真机上恒跳过;没有日志的锁目录因此与
|
|
46
|
+
* 空目录同判。非空的锁说明我们对这个文件的理解失效(宿主不会往里写字节),仍按不可读处理。
|
|
40
47
|
*/
|
|
41
48
|
export declare function imageIdsOfDir(deps: AttachmentReadDeps, dir: string): Promise<{
|
|
42
49
|
ids: Set<string>;
|
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 回收站**单条详情**:从回收站目录里那条会话自己的日志读出元信息与正文块(用例见 `tests/trash-detail.test.ts`)。
|
|
3
3
|
*
|
|
4
4
|
* 三层里的策略层——编解码在 `src/log/*`,这里只回答"给谁看、给多少、算不算失败"。
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 四条硬口径,都不是口味:
|
|
7
7
|
* ① **寻址只认清册(raw 索引)**:`entryId` 先在对账之外的 raw 索引里查,目录一律由 `trashDirOf(record.id, home)` 推
|
|
8
8
|
* ⇒ 外部输入进不了文件系统寻址(不给"传个路径就读任意目录"留缝)。
|
|
9
|
-
* ②
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
+
* 计数是整条日志的,不受分页与任何字符闸影响。
|
|
15
21
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 单条/累计两个闸:单条超限截断并把那一条标成 `truncated`,累计到上限后剩下的整条不再收、只累进
|
|
19
|
-
* `omittedByPayloadCap`——所以计数可以大于正文条数,详情里两件事分开说。
|
|
20
|
-
* 附件随消息一起收(`attachmentsInContent`),也有两个闸:每条 20、总量 200;超出的**静默丢**,
|
|
21
|
-
* 不累进 `omittedByPayloadCap`——那个计数说的是"整条正文没进来",拿它兼报少了几张附件会让两个数都对不上。
|
|
22
|
+
* 附件随块一起收(`attachmentsInContent`):每块最多 20、每页最多 200;超出的**静默丢**,不另立计数——
|
|
23
|
+
* "少了几张图"与"这块被截断"不是同一件事,拿一个数兼报会让两个数都对不上。
|
|
22
24
|
*/
|
|
23
25
|
import { type TrashEntry } from './entry.js';
|
|
24
26
|
import type { FsPort } from '../ports/fs-port.js';
|
|
25
27
|
import type { TrashDetailWire } from '../shared/wire.js';
|
|
26
|
-
/**
|
|
27
|
-
export declare const
|
|
28
|
-
/**
|
|
29
|
-
export declare const
|
|
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;
|
|
30
36
|
export type TrashDetail = TrashDetailWire;
|
|
31
37
|
export type DetailDeps = {
|
|
32
38
|
/** 详情自己直接要的三件(判目录在不在、挑日志、读日志)。对账不在这条路上,所以不需要 `listDirs`。 */
|
|
@@ -37,9 +43,13 @@ export type DetailDeps = {
|
|
|
37
43
|
rows: () => Promise<readonly TrashEntry[]>;
|
|
38
44
|
home: string;
|
|
39
45
|
};
|
|
40
|
-
/**
|
|
46
|
+
/** 四个分页/截断参数(可调是为把策略单独验穿;生产走的就是这四个默认值,用例里钉着)。 */
|
|
41
47
|
export interface DetailLimits {
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
perBlockChars: number;
|
|
49
|
+
pageBlocks: number;
|
|
50
|
+
/** 一页正文的总字符闸;缺省 `PAGE_CHAR_LIMIT`。 */
|
|
51
|
+
pageChars?: number;
|
|
52
|
+
/** 子会话摘要的条数闸;缺省 `SUBAGENT_SUMMARY_LIMIT`。 */
|
|
53
|
+
subagents?: number;
|
|
44
54
|
}
|
|
45
|
-
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;
|
|
@@ -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)。 */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 接得住孩子的只有组根——自己不记 `parentEntryId` 的那一行。父行不在、或父行自己也是孩子时,
|
|
5
5
|
* 孩子必须自己占一行,否则它的字节会被头部合计数两遍,而它自己既看不见也清不掉。
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* 这些函数都是纯函数、都吃一份快照;什么时候收缩、把收缩后的视图存在哪里归调用方,本模块不持有可变状态。
|
|
8
8
|
*/
|
|
9
9
|
import type { TrashEntry } from './entry.js';
|
|
10
10
|
/** 一条露面的组根,连同它接住的孩子(顺序与清册一致)。 */
|
|
@@ -29,6 +29,11 @@ export declare function visibleGroups(rows: readonly TrashEntry[]): TrashGroupVi
|
|
|
29
29
|
* 调用方据此报 `trash-empty` / 判"已被同批前一条带走"。
|
|
30
30
|
*/
|
|
31
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[];
|
|
32
37
|
/** 一条 id 在「原始快照 vs 当前视图」里的三态。判别联合:**只有 `fresh` 带当前这一组**——
|
|
33
38
|
* 组语义只对"还在"的 id 有意义,carried / absent 没有可交的组,调用方也就无从误用。 */
|
|
34
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
|
|
6
|
-
|
|
7
|
-
|
|
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` 的
|
|
@@ -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;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 活体准入:删除 / 永久删除 / 孤儿清理之前,逐个问会话是否在宿主内存里 attach 着;
|
|
3
3
|
* 任一在跑就整组拒绝、零副作用(ADR-0008)。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 边界:什么时候查归调用方——永久删除在计划里查一次、执行器第一笔写前用计划里的会话名单再查一次(ADR-0027);
|
|
6
6
|
* 本模块只拥有「查什么、怎么判、怎么说」。
|
|
7
7
|
*/
|
|
8
8
|
import type { HostIdentityPort } from '../ports/host-port.js';
|
|
@@ -15,7 +15,9 @@ import type { Journal, JournalUndo } from './member-journal.js';
|
|
|
15
15
|
/** 本模块实际用到的三条宿主能力。`Pick` 此刻覆盖 `HostPresencePort` 的三条方法,仍留着它当闸:
|
|
16
16
|
* 将来端口多出第四条时,这里不会被动跟着变宽——能力面只暴露这三条,批量通告的编排归 `verbs/batch.ts`。 */
|
|
17
17
|
export type MemberPresenceHost = Pick<HostPresencePort, 'evictLive' | 'announceRemoved' | 'announceRestored'>;
|
|
18
|
-
/**
|
|
18
|
+
/** 本仓的在场能力实现,也是**全仓唯一的盖章处**:端口形状住 `src/ports/presence-port.ts`,这里只做
|
|
19
|
+
* 三态与真值表。品牌字段是类型层的(unique symbol 不导出、没有运行时值),所以只有这一处显式断言
|
|
20
|
+
* 能把三件方法盖成 `PresenceCapability`——手拼同样的三件满足不了那个面(`tests/deps.test.ts` 钉住)。
|
|
19
21
|
* `announceRestored` 直接交宿主那一手:能力面与宿主面同名同签名,再包一层只会多一个转调。 */
|
|
20
22
|
export declare function createMemberPresence(host: MemberPresenceHost): PresenceCapability;
|
|
21
23
|
/** 一次 undo 之后要发哪一条通告:`leave` = 成员已不在可见面,`return` = 已回到原可见状态。 */
|
|
@@ -12,7 +12,8 @@ export interface GroupMove<M, R> {
|
|
|
12
12
|
readonly root: M;
|
|
13
13
|
/** 跟随者:按序跑,每个都拿到根结果(彼此看不到对方的结果)。 */
|
|
14
14
|
readonly followers: readonly M[];
|
|
15
|
-
move
|
|
15
|
+
/** 属性声明:`move` 会被解构出来按序调用(实现不读 `this`),方法简写会带隐式 `this: GroupMove`。 */
|
|
16
|
+
move: (member: M, rootResult: R | undefined) => Promise<{
|
|
16
17
|
journal: Journal;
|
|
17
18
|
result: R;
|
|
18
19
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SessionIdentity } from '../ports/host-port.js';
|
|
2
2
|
import type { TrashEntry } from '../trash/entry.js';
|
|
3
3
|
import type { OrphanCleanStatsWire, OrphanReportWire } from '../shared/wire.js';
|
|
4
4
|
import type { CoreDeps, RemovalDeps } from '../deps.js';
|
|
@@ -27,8 +27,9 @@ interface RawGroup {
|
|
|
27
27
|
* 彻底删除(组语义见 `src/trash/groups.ts`),把它的根当孤儿删掉就是**替用户做了一个没人点过的决定**。孤儿只
|
|
28
28
|
* 有一种来路:父被彻底删掉了(或被手工从盘上清掉了),清册里再也没有这一行。
|
|
29
29
|
*
|
|
30
|
-
* 根按**传递闭包**收全部后代(与 `HostCorpusPort.subagentMembersOf`
|
|
31
|
-
*
|
|
30
|
+
* 根按**传递闭包**收全部后代(与 `HostCorpusPort.subagentMembersOf` 同一实现:闭包核心住
|
|
31
|
+
* `src/domain/closure.ts` 的 `orphanRootsOf`,孩子与孙代都算,深度不设上限),同一条记录只归一组
|
|
32
|
+
* (`claimed`)——数据里真出现环时结果是"少数一条",不是把一个会话删两遍。
|
|
32
33
|
* 归堆与去重**两边都去 `session-` 前缀**:同一个父在两代宿主里写法不同(见 `bareSessionId` 的来历)。
|
|
33
34
|
*/
|
|
34
35
|
export declare function orphanGroupsOf(corpus: readonly SessionIdentity[], trashSessionIds: readonly string[]): RawGroup[];
|
|
@@ -39,10 +40,15 @@ export declare function orphanGroupsOf(corpus: readonly SessionIdentity[], trash
|
|
|
39
40
|
*
|
|
40
41
|
* `listSessions()` 回 `{ available: false }` 时**必须抛**:那是宿主的会话语料服务没挂,不是"这个家没有孤儿"。
|
|
41
42
|
* 拿缺席当空语料 ⇒ 报告说"零条孤儿"、界面上一句"没有要清理的",而盘上真有几百 MB——那是假话,
|
|
42
|
-
* 所以这里按 `io` 响亮失败(AGENTS.md:`available: false` 意味着服务缺席,扫描方不许冒充读数)。
|
|
43
|
+
* 所以这里按 `io` 响亮失败(AGENTS.md:`available: false` 意味着服务缺席,扫描方不许冒充读数)。
|
|
44
|
+
*
|
|
45
|
+
* `roots` 只收请求点名的那几组、**在量字节之前过滤**:`/orphans` 不传(报告全部),
|
|
46
|
+
* `orphan-clean` 传这次的根——否则点一组会把整个家所有孤儿的树都递归量一遍。 */
|
|
43
47
|
export declare function loadOrphanGroups(deps: CoreDeps,
|
|
44
48
|
/** 调用方选好并交来的清册行:`/orphans` 交 `read.rows()`,`orphan-clean` 交 `ledger.list()`。 */
|
|
45
|
-
trash: readonly TrashEntry[]
|
|
49
|
+
trash: readonly TrashEntry[],
|
|
50
|
+
/** 只要这些根(两边都去 `session-` 前缀再比);缺席 = 全量扫描。 */
|
|
51
|
+
roots?: readonly string[]): Promise<readonly OrphanGroup[]>;
|
|
46
52
|
/** 把一组组收成那份报告:根清单 + 闭包总数/总字节 + 失踪父的**去重计数** + 按项目目录的分摊。
|
|
47
53
|
* `groups` 按字节从多到少排(界面上"哪家还剩得最多"要看第一行)。 */
|
|
48
54
|
export declare function reportOrphans(groups: readonly OrphanGroup[]): OrphanReportWire;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 永久删除的测量、计划与执行:先记录目录/字节/账本归属,再按计划动盘。
|
|
3
|
-
*
|
|
3
|
+
* 计划函数只读;活体准入在计划阶段、任何写之前完成,执行器动第一笔写前再查一次。
|
|
4
|
+
*
|
|
5
|
+
* 测量与补账本归属**分家**:`/list` 的只读计划只吃 {@link measureMembers}(零账本读,不再为每个
|
|
6
|
+
* 盘上后代问一次 `workspaceOf`,`/list` 的失败面不再含账本归属读);`workspaceOf` 只有
|
|
7
|
+
* purge / orphan-clean 的删除计划({@link planMemberRemovals})才付——executor 摘账本要用它。
|
|
4
8
|
*/
|
|
5
9
|
import { type TrashEntry } from '../trash/entry.js';
|
|
6
10
|
import type { TrashGroupView } from '../trash/groups.js';
|
|
@@ -11,8 +15,8 @@ export interface MeasuredMemberDir {
|
|
|
11
15
|
readonly dir: string;
|
|
12
16
|
readonly bytes: number;
|
|
13
17
|
}
|
|
14
|
-
/** 一条成员被删之前要记下的三件:目录、字节、账本归属(缺席 =
|
|
15
|
-
*
|
|
18
|
+
/** 一条成员被删之前要记下的三件:目录、字节、账本归属(缺席 = 未分组)。purge / orphan-clean 的
|
|
19
|
+
* 删除计划共用这一份形状;`/list` 的只读计划不吃它——那一面零账本读,见 {@link measureMembers}。 */
|
|
16
20
|
export interface PlannedMemberRemoval {
|
|
17
21
|
readonly sessionId: string;
|
|
18
22
|
readonly dir: string;
|
|
@@ -25,6 +29,21 @@ export declare function measureMemberDir(deps: CoreDeps, member: {
|
|
|
25
29
|
readonly sessionId: string;
|
|
26
30
|
readonly projectDir: string;
|
|
27
31
|
}): Promise<MeasuredMemberDir | undefined>;
|
|
32
|
+
/** 一条成员的**测量**读数:身份两格 + 目录 + 实测字节。没有账本归属那一格——归属是删除动词才要
|
|
33
|
+
* 读的账本事实(见 {@link measureMembers} 与 {@link PlannedMemberRemoval} 的分工)。 */
|
|
34
|
+
export interface MeasuredMember {
|
|
35
|
+
readonly sessionId: string;
|
|
36
|
+
readonly projectDir: string;
|
|
37
|
+
readonly dir: string;
|
|
38
|
+
readonly bytes: number;
|
|
39
|
+
}
|
|
40
|
+
/** 只测量(目录 + 实测字节):`/list` 的只读计划只吃这一支,零账本读。目录不在的成员跳过
|
|
41
|
+
* (记录与盘不一致是那条成员自己的事)。为什么不顺手把归属也问了:`/list` 投影只用字节与条数,
|
|
42
|
+
* 那一格立刻被丢弃——每开一次回收站为它付 N 次宿主调用,还把账本归属读拉进 `/list` 的失败面。 */
|
|
43
|
+
export declare function measureMembers(deps: CoreDeps, members: readonly {
|
|
44
|
+
readonly sessionId: string;
|
|
45
|
+
readonly projectDir: string;
|
|
46
|
+
}[]): Promise<MeasuredMember[]>;
|
|
28
47
|
/** 回收站里的一条要删的行:目录、在不在、实测字节(缺席 = 0)。purge 与 `/list` 都由它量。 */
|
|
29
48
|
export interface PlannedTrashRow {
|
|
30
49
|
readonly entry: TrashEntry;
|
|
@@ -32,10 +51,11 @@ export interface PlannedTrashRow {
|
|
|
32
51
|
readonly present: boolean;
|
|
33
52
|
readonly bytes: number;
|
|
34
53
|
}
|
|
35
|
-
/** `/list` 的一行(一组)所需的只读数:`releaseBytes` 是这一组**实测**会释放的字节。
|
|
54
|
+
/** `/list` 的一行(一组)所需的只读数:`releaseBytes` 是这一组**实测**会释放的字节。
|
|
55
|
+
* `members` 是 {@link MeasuredMember}(测量读数,无账本归属格):清单只用它的字节与条数。 */
|
|
36
56
|
export interface VisibleTrashGroup extends TrashGroupView {
|
|
37
57
|
readonly trash: readonly PlannedTrashRow[];
|
|
38
|
-
readonly members: readonly
|
|
58
|
+
readonly members: readonly MeasuredMember[];
|
|
39
59
|
readonly releaseBytes: number;
|
|
40
60
|
readonly subagentBytes: number;
|
|
41
61
|
readonly strandedBytes: number;
|
|
@@ -46,7 +66,8 @@ export interface VisibleTrashGroup extends TrashGroupView {
|
|
|
46
66
|
* 不用清册里记的 `sizeBytes`(那是删除那一刻的事实,盘上早就变了)。
|
|
47
67
|
*
|
|
48
68
|
* 盘上后代经 `selectStrandedMembers` 选出(排除全部清册 id、bare id 全局只算一次);目录不在的成员
|
|
49
|
-
* 不进 `members`(与
|
|
69
|
+
* 不进 `members`(与 purge 计划同口径)。成员只吃 {@link measureMembers}:**零账本读**——不再为每个
|
|
70
|
+
* 盘上后代问一次 `workspaceOf`,`/list` 的失败面不再含账本归属读(归属只有 purge 摘账本才付)。
|
|
50
71
|
*/
|
|
51
72
|
export declare function planVisibleTrash(deps: CoreDeps, rows: readonly TrashEntry[], groups: readonly TrashGroupView[]): Promise<VisibleTrashGroup[]>;
|
|
52
73
|
/** 一次永久删除的只读计划:哪些目录要删、各自实测多少字节、哪些会话要先摘归档集。
|
|
@@ -67,7 +88,8 @@ export interface RemovalPlan {
|
|
|
67
88
|
}
|
|
68
89
|
/**
|
|
69
90
|
* purge 的删除计划:`targets` 由调用方按组语义(`trash/groups.ts`)选好,这里只量、只筛、只查活体。
|
|
70
|
-
*
|
|
91
|
+
* **计划函数是执行前的第一道闸**——调用方拿到计划才允许写;计划与第一笔写之间的窗口由
|
|
92
|
+
* `executeRemoval` 在首笔写前复查(见该函数头注)。
|
|
71
93
|
*
|
|
72
94
|
* - `trash` 按 targets 原序逐个实测(目录缺席的行 `present:false` / 0 字节,但清册行仍要摘)。
|
|
73
95
|
* - 盘上后代经 `selectStrandedMembers` 选出(一次问全部 targets、排除全部清册 id、bare id 全局只计划一次)。
|
|
@@ -76,7 +98,7 @@ export interface RemovalPlan {
|
|
|
76
98
|
* - `attachments` 由被删目录(targets + 这些成员)里的图片引用与全部剩余引用源算独占;共享的保留、
|
|
77
99
|
* 剩余引用源读不出就整体跳过(判据住 `trash/attachments.ts`,这里只接线)。
|
|
78
100
|
* - 活体准入在量目录与任何写之前逐个查 targets 与**全部枚举到的**盘上后代;任一在跑就整条拒绝、
|
|
79
|
-
*
|
|
101
|
+
* 零副作用。没有跳过它的开关;执行器首笔写前还会用计划里的 `unarchive` 名单复查(ADR-0027)。
|
|
80
102
|
* - `sessionQuery` 缺席时端口回 `{ available: false }` ⇒ members 为空、不抛(盘上残留留给 `GET /orphans` 兜底)。
|
|
81
103
|
*/
|
|
82
104
|
export declare function planTrashRemoval(deps: CoreDeps, rows: readonly TrashEntry[], targets: readonly TrashEntry[]): Promise<RemovalPlan>;
|
|
@@ -96,8 +118,15 @@ export declare function planOrphanRemoval(deps: CoreDeps, members: readonly {
|
|
|
96
118
|
readonly projectDir: string;
|
|
97
119
|
}[]): Promise<RemovalPlan>;
|
|
98
120
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
121
|
+
* 按计划执行一次永久删除:不再量。回执的字节念计划(`plan.bytes`),执行期不再累加一遍。
|
|
122
|
+
*
|
|
123
|
+
* **计划 + 执行器、准入查两遍**(ADR-0027):计划期准入证明计划成立时没有会话在跑;执行器首笔写前
|
|
124
|
+
* 再用同一份名单复查,补上附件扫描撑开的窗口。
|
|
125
|
+
*
|
|
126
|
+
* 计划期准入与第一笔写之间还隔着附件引用的全 home 扫描(`planImageAttachmentRemoval`),大 home
|
|
127
|
+
* 上那是秒级窗口——期间用户 resume 一个会话,它的目录随后仍会被删。所以动任何写之前用计划里同一份
|
|
128
|
+
* 会话名单**再查一次活体**(只读、廉价);这一查失败仍然零写入。名单取 `plan.unarchive`:
|
|
129
|
+
* 它覆盖所有会被删目录的会话(回收站行与盘上成员),目录已不在的成员没有可删的东西。
|
|
101
130
|
*
|
|
102
131
|
* 顺序:整批摘归档集 → 摘有账本的成员的账本 → 逐条回收站行
|
|
103
132
|
* (在才删目录 → 摘清册行 → 通告成员离场)→ 逐条盘上成员(删目录 → 通告成员离场)→ 逐条附件对象。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TrashEntry } from '../trash/entry.js';
|
|
2
|
+
import type { RemovalDeps } from '../deps.js';
|
|
3
|
+
import type { PurgeStatsWire } from '../shared/wire.js';
|
|
4
|
+
import { type OrphanGroup } from './orphan-subagents.js';
|
|
5
|
+
/**
|
|
6
|
+
* **清册作用域(trash scope)**:purge 这条写路径的「拿目标」owner。调用方把一份选好读面的清册
|
|
7
|
+
* 快照交进来(写入口 = 真相读;报告入口不经作用域——`/orphans` 直接把 raw 快照交 `loadOrphanGroups`。
|
|
8
|
+
* 选哪一格是路由行 / 批量层的事,ADR-0012 的边界不在这里翻),快照与**收缩视图**从此归本模块、
|
|
9
|
+
* **不外露**:三态判据与按组收缩都仍读 `trash/groups.ts` 的 `claimOf` / `withoutGroup`,这里只**持有**
|
|
10
|
+
* 那份视图,不抄第二遍判据。三个成员的输入输出以 `CONTEXT.md` 的「清册作用域」与下面各成员的
|
|
11
|
+
* 注释为准,头注不重写一遍表面契约。
|
|
12
|
+
*
|
|
13
|
+
* 彻底删除:从回收站里**真的**抹掉一组(或整站),并连带那些"还留在盘上、没被带走"的后代。
|
|
14
|
+
* **这里只做两件事:拿目标、把计划交给执行器**。量目录、筛盘上后代、活体准入与"先退归档集、
|
|
15
|
+
* 再摘账本、最后删目录"的顺序都住 `verbs/permanent-removal.ts`——计划只读,执行器不再量、只在第一笔
|
|
16
|
+
* 写前补一次只读的活体复查,计划是执行前的第一道闸(别在这里再长出一段手写步骤)。
|
|
17
|
+
*
|
|
18
|
+
* **不做回滚**:目录删掉就没有"退回去"这回事(删到一半失败时已删目录不回滚;带部分计数的
|
|
19
|
+
* `details` 回执只属于 `orphan-clean`,purge 的 `removed` / `freedBytes` 仍只在成功回执里给)。
|
|
20
|
+
* 对应地,`kind:'trash'` 的计划失败时执行器只抛那一步的 `io` 与原文、不带 details。
|
|
21
|
+
*
|
|
22
|
+
* 回执里的 `attachments` 原样来自执行器:独占图片在会话目录删完之后才清,附件失败只计数、不改
|
|
23
|
+
* `removed` / `freedBytes`(`skipped` = 有候选或剩余引用源读不出、这一轮部分或整体没清,原因上屏)。
|
|
24
|
+
* 「已被同批前一条带走」的幂等回执**不带** `attachments`——那一趟没有附件读数,缺席与 0 是两件事,
|
|
25
|
+
* 不拿零冒充"清过"(与旧批量回执同形)。
|
|
26
|
+
*/
|
|
27
|
+
export interface TrashScope {
|
|
28
|
+
/** purge 这一组;已被同批带走 ⇒ `{removed:0,freedBytes:0}`;absent(原始快照里就认不出)⇒
|
|
29
|
+
* `trash-empty`,原文点名 id。 */
|
|
30
|
+
purge(id: string): Promise<PurgeStatsWire>;
|
|
31
|
+
/** 清空:全部行一组。 */
|
|
32
|
+
purgeAll(): Promise<PurgeStatsWire>;
|
|
33
|
+
/** 点名根的无主组(根两边去 `session-` 前缀再比);roots 缺席 = 全量。 */
|
|
34
|
+
orphanGroups(roots?: readonly string[]): Promise<readonly OrphanGroup[]>;
|
|
35
|
+
}
|
|
36
|
+
export declare function createTrashScope(deps: RemovalDeps, rows: readonly TrashEntry[]): TrashScope;
|