@xrn1997/dsh-session-delete 0.2.2 → 0.3.1
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/README.md +10 -28
- package/lib/client.js +2012 -1117
- package/lib/index.js +1271 -893
- package/lib/types/api/dispatch.d.ts +2 -2
- package/lib/types/api/route-table.d.ts +1 -1
- package/lib/types/api/trash-rows.d.ts +13 -6
- package/lib/types/client/batch-bar.d.ts +7 -6
- package/lib/types/client/batch-confirm.d.ts +6 -4
- package/lib/types/client/batch-menu-item.d.ts +2 -2
- package/lib/types/client/batch-select.d.ts +7 -50
- package/lib/types/client/batch-selection-store.d.ts +30 -0
- package/lib/types/client/busy-gate.d.ts +6 -0
- package/lib/types/client/copy.d.ts +4 -1
- package/lib/types/client/delete-confirm.d.ts +2 -0
- package/lib/types/client/host-rows.d.ts +50 -0
- package/lib/types/client/main-view.d.ts +42 -0
- package/lib/types/client/orphan-maintenance.d.ts +27 -0
- package/lib/types/client/receipt.d.ts +73 -0
- package/lib/types/client/remote.d.ts +25 -19
- package/lib/types/client/store.d.ts +48 -0
- package/lib/types/client/trash-nav.d.ts +30 -0
- package/lib/types/client/trash-panel.d.ts +1 -1
- package/lib/types/client/trash-store.d.ts +2 -0
- package/lib/types/client/ui/icons.d.ts +3 -3
- package/lib/types/client/undo-toast.d.ts +30 -6
- package/lib/types/client/use-column-resize.d.ts +2 -2
- package/lib/types/client/use-panel-width.d.ts +1 -1
- package/lib/types/client/use-trash-panel-controller.d.ts +42 -0
- package/lib/types/client/use-trash-query.d.ts +20 -9
- package/lib/types/client/write-seats.d.ts +52 -0
- package/lib/types/client/write-settlement.d.ts +72 -0
- package/lib/types/client/write.d.ts +51 -0
- package/lib/types/deps.d.ts +14 -0
- package/lib/types/index.d.ts +7 -13
- package/lib/types/log/zstd-frames.d.ts +1 -1
- package/lib/types/ports/agent-handles.d.ts +25 -0
- package/lib/types/ports/home.d.ts +19 -0
- package/lib/types/ports/host-port.d.ts +60 -44
- package/lib/types/remote/map-error.d.ts +2 -2
- package/lib/types/shared/wire.d.ts +18 -9
- package/lib/types/trash/detail.d.ts +6 -3
- package/lib/types/trash/entry.d.ts +4 -4
- package/lib/types/trash/groups.d.ts +17 -6
- package/lib/types/trash/ledger.d.ts +20 -0
- package/lib/types/trash/manifest.d.ts +4 -2
- package/lib/types/trash/reconcile.d.ts +6 -5
- package/lib/types/trash/storage-manifest.d.ts +12 -0
- package/lib/types/verbs/batch-carried.d.ts +20 -0
- package/lib/types/verbs/batch.d.ts +1 -1
- package/lib/types/verbs/delete-session.d.ts +11 -13
- package/lib/types/verbs/errors.d.ts +2 -6
- package/lib/types/verbs/live-admission.d.ts +13 -0
- package/lib/types/verbs/member-journal.d.ts +26 -6
- package/lib/types/verbs/member-presence.d.ts +67 -0
- package/lib/types/verbs/move-group.d.ts +20 -0
- package/lib/types/verbs/orphan-subagents.d.ts +21 -40
- package/lib/types/verbs/permanent-removal.d.ts +114 -0
- package/lib/types/verbs/purge-trash.d.ts +21 -9
- package/lib/types/verbs/restore-session.d.ts +14 -12
- package/package.json +3 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 成员离场 / 归位的通告协议:摘活体、三态判断、成对事件只在这里做。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有它:delete(成功与回滚)/ restore 回滚 / purge(两个分支)/ orphan-clean
|
|
5
|
+
* 六处调用曾经各写一遍
|
|
6
|
+
* `evictLive → !== 'unknown' → announceRemoved`;删除回滚还要自己拼目录与账本两个布尔
|
|
7
|
+
* 才发 `announceRestored`。
|
|
8
|
+
* 这条协议说错一次就是客户端留着假行、或删掉真行,所以只留一个家。
|
|
9
|
+
*
|
|
10
|
+
* 边界:
|
|
11
|
+
* - 批量恢复自己发起的那次通告(`announceRestored(ids)`)归 `verbs/batch.ts`(ADR-0002):同一个方法、
|
|
12
|
+
* 同一份预取,只是调用方是那一批的主人。这里只判单成员事实。
|
|
13
|
+
* - 本模块不决定哪个键算归位:动词在 `tail.onUndone` 里把 `restored(key)` 读成事实再交进来,真值表住这里;
|
|
14
|
+
* `run` / `undo` 之后补一条通告的**形状**由 {@link withPresenceTail} 拥有。
|
|
15
|
+
* - 本模块用到的三条(`evictLive` / `announceRemoved` / `announceRestored`)按契约不抛(evictLive 三态、
|
|
16
|
+
* announce* 内部吞异常),这里不再叠 catch。
|
|
17
|
+
*/
|
|
18
|
+
import type { HostPresencePort } from '../ports/host-port.js';
|
|
19
|
+
import type { Journal, JournalUndo } from './member-journal.js';
|
|
20
|
+
/** 本模块实际用到的三条宿主能力。`Pick` 此刻覆盖 `HostPresencePort` 的三条方法,仍留着它当闸:
|
|
21
|
+
* 将来端口多出第四条时,这里不会被动跟着变宽——这里是单成员协议,批量恢复由 `verbs/batch.ts`
|
|
22
|
+
* 自己发起,不该因为共用同一颗 adapter 就在类型上声明依赖。 */
|
|
23
|
+
export type MemberPresenceHost = Pick<HostPresencePort, 'evictLive' | 'announceRemoved' | 'announceRestored'>;
|
|
24
|
+
export interface MemberBackFacts {
|
|
25
|
+
/** 目录搬回原处了吗。 */
|
|
26
|
+
readonly diskBack: boolean;
|
|
27
|
+
/** 账本挂回去了吗(本来没有账目时算 true)。 */
|
|
28
|
+
readonly workspaceBack: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface MemberPresence {
|
|
31
|
+
/** 成员已经不在宿主可见列表:摘活体;三态不是 unknown 时发 removed。
|
|
32
|
+
* `backInTrash: false` 表示目录没退回回收站(恢复回滚失败),此时什么都不做。 */
|
|
33
|
+
memberGone(sessionId: string, facts?: {
|
|
34
|
+
readonly backInTrash?: boolean;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/** 成员已经回到原来的可见状态:目录与账本两件写都回去才发 added。 */
|
|
37
|
+
memberBack(sessionId: string, facts: MemberBackFacts): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createMemberPresence(host: MemberPresenceHost): MemberPresence;
|
|
40
|
+
/** 一次 undo 之后要发哪一条通告:`leave` = 成员已不在可见面,`return` = 已回到原可见状态。 */
|
|
41
|
+
export type PresenceAnnounce = {
|
|
42
|
+
readonly kind: 'leave';
|
|
43
|
+
readonly backInTrash: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: 'return';
|
|
46
|
+
readonly diskBack: boolean;
|
|
47
|
+
readonly workspaceBack: boolean;
|
|
48
|
+
};
|
|
49
|
+
/** 动词给工厂的那一格:哪个成员、run 成不成功要不要补离场、undo 之后按逐步结果发哪一条。 */
|
|
50
|
+
export interface PresenceTail {
|
|
51
|
+
readonly sessionId: string;
|
|
52
|
+
/** `run()` 成功后补离场通告(删除用)。省略 = run 不动在场(恢复的 run 本来就不该发东西)。 */
|
|
53
|
+
readonly onDone?: 'leave';
|
|
54
|
+
/** `undo()` 完成后按逐步结果发通告。 */
|
|
55
|
+
onUndone(undo: JournalUndo): PresenceAnnounce;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 给一次成员移动的 journal 补上「在场那条尾巴」:run 完全成功之后离场、undo 之后按事实归位或离场。
|
|
59
|
+
*
|
|
60
|
+
* 为什么是个工厂:删除与恢复此前各手拼一个匿名 journal,形状逐字相同(`journal.run` 加上
|
|
61
|
+
* `presence.memberGone`/`journal.undo` 加上 `presence.memberBack`),只差键 → 事实的映射。
|
|
62
|
+
* 两处各写一遍,就是「补在哪一拍」这条顺序又有第二个家。
|
|
63
|
+
*
|
|
64
|
+
* 边界:`memberGone` 的 `backInTrash:false` 早退与 `memberBack` 的两件齐备真值表仍归 `createMemberPresence`;
|
|
65
|
+
* 这里只把事实原样递过去,不替它判断。
|
|
66
|
+
*/
|
|
67
|
+
export declare function withPresenceTail(presence: MemberPresence, journal: Journal, tail: PresenceTail): Journal;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组级成员移动:组根先跑(拿到 `undefined`),跟随者按序跑并拿到根结果;任一失败就把**已经做成**的
|
|
3
|
+
* 按相反顺序退回。
|
|
4
|
+
*
|
|
5
|
+
* 这里拥有顺序、整组回滚与**组根结果的交回**:根先跑,跟随者按序拿到根结果;调用方直接拿
|
|
6
|
+
* 返回的根结果,不按位置索引结果数组。跟随者自己的结果不向上交回——当前调用方不需要。
|
|
7
|
+
* 单成员的步骤与反向动作仍归 `member-journal.ts`;这条归属的裁决见 ADR-0002。
|
|
8
|
+
*/
|
|
9
|
+
import { type Journal } from './member-journal.js';
|
|
10
|
+
export interface GroupMove<M, R> {
|
|
11
|
+
/** 组根:先跑,`move` 收到的 `rootResult` 是 `undefined`。 */
|
|
12
|
+
readonly root: M;
|
|
13
|
+
/** 跟随者:按序跑,每个都拿到根结果(彼此看不到对方的结果)。 */
|
|
14
|
+
readonly followers: readonly M[];
|
|
15
|
+
move(member: M, rootResult: R | undefined): Promise<{
|
|
16
|
+
journal: Journal;
|
|
17
|
+
result: R;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export declare function moveGroup<M, R>({ root, followers, move, }: GroupMove<M, R>): Promise<R>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type SessionIdentity } from '../ports/host-port.js';
|
|
2
|
+
import type { TrashEntry } from '../trash/entry.js';
|
|
2
3
|
import type { OrphanCleanStatsWire, OrphanReportWire } from '../shared/wire.js';
|
|
3
|
-
import type {
|
|
4
|
+
import type { CoreDeps } from '../deps.js';
|
|
4
5
|
/** 组里一个人的盘上身份(字节是**扫描那一刻**量的,清理时按目录重量一次,不拿这份快照冒充准数)。 */
|
|
5
6
|
export interface OrphanMember {
|
|
6
7
|
readonly sessionId: string;
|
|
@@ -13,27 +14,6 @@ export interface OrphanGroup {
|
|
|
13
14
|
readonly parentSessionId: string;
|
|
14
15
|
readonly members: readonly OrphanMember[];
|
|
15
16
|
}
|
|
16
|
-
export interface MeasuredMemberDir {
|
|
17
|
-
readonly dir: string;
|
|
18
|
-
readonly bytes: number;
|
|
19
|
-
}
|
|
20
|
-
/** 一个成员被删之前要记下的三件:目录、字节、账本归属(缺席 = 未分组)。purge 与 orphan-clean 共用。 */
|
|
21
|
-
export interface MemberRemovalPlan extends MeasuredMemberDir {
|
|
22
|
-
readonly sessionId: string;
|
|
23
|
-
readonly workspaceId?: string;
|
|
24
|
-
}
|
|
25
|
-
/** 成员的盘上目录与字节(扫描、清单装配与 purge 共用这一条路径/存在性/度量口径;
|
|
26
|
-
* 目录缺席回 `undefined`,不拿一份"记录还在、盘上已经没了"的旧读数冒充盘上残留)。 */
|
|
27
|
-
export declare function measureMemberDir(deps: DeleteDeps, member: {
|
|
28
|
-
readonly sessionId: string;
|
|
29
|
-
readonly projectDir: string;
|
|
30
|
-
}): Promise<MeasuredMemberDir | undefined>;
|
|
31
|
-
/** 把一组成员折成"删之前要记下的计划";目录不在的成员跳过(记录与盘不一致是那条成员自己的事)。
|
|
32
|
-
* `purge` 的盘上后代与 `orphan-clean` 的整组都走这一处,别再各写一遍 measure + workspaceOf。 */
|
|
33
|
-
export declare function planMemberRemovals(deps: DeleteDeps, members: readonly {
|
|
34
|
-
readonly sessionId: string;
|
|
35
|
-
readonly projectDir: string;
|
|
36
|
-
}[]): Promise<MemberRemovalPlan[]>;
|
|
37
17
|
/** {@link orphanGroupsOf} 的中间形状:还没量字节的那一组(纯函数不碰盘)。 */
|
|
38
18
|
interface RawGroup {
|
|
39
19
|
readonly root: SessionIdentity;
|
|
@@ -44,39 +24,40 @@ interface RawGroup {
|
|
|
44
24
|
* 无主子会话 = `origin:"subagent"`、父**既不在当前语料(live ∪ persisted)也不在回收站清册里**的会话。
|
|
45
25
|
*
|
|
46
26
|
* 为什么清册里的父不算孤儿:那种是 **stranded**——父还在回收站里等着,孩子本该跟着父一起恢复或一起
|
|
47
|
-
*
|
|
27
|
+
* 彻底删除(组语义见 `src/trash/groups.ts`),把它的根当孤儿删掉就是**替用户做了一个没人点过的决定**。孤儿只
|
|
48
28
|
* 有一种来路:父被彻底删掉了(或被手工从盘上清掉了),清册里再也没有这一行。
|
|
49
29
|
*
|
|
50
|
-
* 根按**传递闭包**收全部后代(与 `
|
|
30
|
+
* 根按**传递闭包**收全部后代(与 `HostCorpusPort.subagentMembersOf` 同一口径:孩子与孙代都算,深度不设上限),
|
|
51
31
|
* 同一条记录只归一组(`claimed`)——数据里真出现环时结果是"少数一条",不是把一个会话删两遍。
|
|
52
32
|
* 归堆与去重**两边都去 `session-` 前缀**:同一个父在两代宿主里写法不同(见 `bareSessionId` 的来历)。
|
|
53
33
|
*/
|
|
54
34
|
export declare function orphanGroupsOf(corpus: readonly SessionIdentity[], trashSessionIds: readonly string[]): RawGroup[];
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
35
|
+
/** **本函数是纯扫描,不对账、不写清册行**(`/orphans` 声明为 pure,一次扫描不能顺手写行)。清册默认读
|
|
36
|
+
* `ledger.raw()`;写路径(`orphan-clean`)可以在调它之前先 `ledger.list()` 对账,并把交回的行递进来,
|
|
37
|
+
* 免得同一份索引再读一遍——否则刚被人手工搬进 `session-trash/` 的父目录还没有行,会被当成"父没了"
|
|
38
|
+
* 直接删盘。
|
|
57
39
|
*
|
|
58
|
-
* `listSessions()` 回 `
|
|
59
|
-
*
|
|
60
|
-
* 所以这里按 `io` 响亮失败(AGENTS.md:`
|
|
61
|
-
export declare function loadOrphanGroups(deps:
|
|
40
|
+
* `listSessions()` 回 `{ available: false }` 时**必须抛**:那是宿主的会话语料服务没挂,不是"这个家没有孤儿"。
|
|
41
|
+
* 拿缺席当空语料 ⇒ 报告说"零条孤儿"、界面上一句"没有要清理的",而盘上真有几百 MB——那是假话,
|
|
42
|
+
* 所以这里按 `io` 响亮失败(AGENTS.md:`available: false` 意味着服务缺席,扫描方不许冒充读数)。 */
|
|
43
|
+
export declare function loadOrphanGroups(deps: CoreDeps,
|
|
44
|
+
/** 调用方已对账过的清册行(`ledger.list()`);缺席则读 `ledger.raw()`。 */
|
|
45
|
+
trash?: readonly TrashEntry[]): Promise<readonly OrphanGroup[]>;
|
|
62
46
|
/** 把一组组收成那份报告:根清单 + 闭包总数/总字节 + 失踪父的**去重计数** + 按项目目录的分摊。
|
|
63
47
|
* `groups` 按字节从多到少排(界面上"哪家还剩得最多"要看第一行)。 */
|
|
64
48
|
export declare function reportOrphans(groups: readonly OrphanGroup[]): OrphanReportWire;
|
|
65
49
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* 删目录之后照 purge 的规矩摘活体并如实通告。不碰投影缓存与附件。
|
|
50
|
+
* 清一组:`planOrphanRemoval`(只读计划,整组 live 准入在一切写之前)→ `executeRemoval`(只写)。
|
|
51
|
+
* 量目录、摘归档集与账本、删目录与成员通告的顺序和失败形状都住 `verbs/permanent-removal.ts`;
|
|
52
|
+
* 这里只把这一组接上去,别在这再长出一段手写步骤(purge 走的是同一条路)。
|
|
70
53
|
*
|
|
71
54
|
* 顺序与删除那一半是**对偶**的,但不是照抄:
|
|
72
|
-
* -
|
|
73
|
-
*
|
|
55
|
+
* - 删除那一半不再用归档藏会话(ADR-0008):它摘账本、搬目录,最后才摘活体。这里目录是**删掉**
|
|
56
|
+
* 不是搬走,藏不藏都不影响可见性 ⇒ 归档集反而**在写之前整批摘掉**:失败时目录还在,下一次还能重试,
|
|
74
57
|
* 不会留下"会话没了但还在归档集里挂着"的幽灵 id。
|
|
75
|
-
* - `unarchive` 对未归档的会话是官方幂等 no-op(`registry.unarchiveSession
|
|
58
|
+
* - `unarchive` 对未归档的会话是官方幂等 no-op(`registry.unarchiveSession`),所以这里不需要那次
|
|
76
59
|
* `wasArchived` 读——这里没有"还原到删除前"的义务。
|
|
77
60
|
* - 摘账本只在账本真记着它时做(`workspaceOf` 未命中 = 本来就是「未分组」,不是故障)。
|
|
78
|
-
* - `evictLive` 的三态照 `HostPort.evictLive` 的契约:`unknown` 时**不通告**(发了就是假话),
|
|
79
|
-
* `evicted` / `not-live` 才发 `api-session/removed`。冷会话没有活体可摘,通告照样必须发。
|
|
80
61
|
*/
|
|
81
|
-
export declare function cleanOrphanGroup(deps:
|
|
62
|
+
export declare function cleanOrphanGroup(deps: CoreDeps, group: OrphanGroup): Promise<OrphanCleanStatsWire>;
|
|
82
63
|
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 永久删除的**测量、计划与执行**:一次永久删除(`/list` 的只读读数或 purge / orphan-clean 的删除计划)
|
|
3
|
+
* 之前要记下的事实(目录、字节、账本归属),以及拿到计划之后按它动盘的那一步。计划函数**只读**——
|
|
4
|
+
* 写不写由执行器决定,但活体准入在这里就做掉,调用方拿到计划才允许动盘。
|
|
5
|
+
*/
|
|
6
|
+
import { type TrashEntry } from '../trash/entry.js';
|
|
7
|
+
import type { TrashGroupView } from '../trash/groups.js';
|
|
8
|
+
import type { CoreDeps } from '../deps.js';
|
|
9
|
+
export interface MeasuredMemberDir {
|
|
10
|
+
readonly dir: string;
|
|
11
|
+
readonly bytes: number;
|
|
12
|
+
}
|
|
13
|
+
/** 一条成员被删之前要记下的三件:目录、字节、账本归属(缺席 = 未分组)。`/list` 的只读计划与
|
|
14
|
+
* purge / orphan-clean 的删除计划共用这一份形状。 */
|
|
15
|
+
export interface PlannedMemberRemoval {
|
|
16
|
+
readonly sessionId: string;
|
|
17
|
+
readonly dir: string;
|
|
18
|
+
readonly bytes: number;
|
|
19
|
+
readonly workspaceId?: string;
|
|
20
|
+
}
|
|
21
|
+
/** 成员的盘上目录与字节(扫描、清单装配与 purge 共用这一条路径/存在性/度量口径;
|
|
22
|
+
* 目录缺席回 `undefined`,不拿一份"记录还在、盘上已经没了"的旧读数冒充盘上残留)。 */
|
|
23
|
+
export declare function measureMemberDir(deps: CoreDeps, member: {
|
|
24
|
+
readonly sessionId: string;
|
|
25
|
+
readonly projectDir: string;
|
|
26
|
+
}): Promise<MeasuredMemberDir | undefined>;
|
|
27
|
+
/** 把一组成员折成"删之前要记下的计划";目录不在的成员跳过(记录与盘不一致是那条成员自己的事)。
|
|
28
|
+
* `purge` 的盘上后代、`orphan-clean` 的整组与 `/list` 的盘上残留都走这一处,别再各写一遍
|
|
29
|
+
* measure + workspaceOf。 */
|
|
30
|
+
export declare function planMemberRemovals(deps: CoreDeps, members: readonly {
|
|
31
|
+
readonly sessionId: string;
|
|
32
|
+
readonly projectDir: string;
|
|
33
|
+
}[]): Promise<PlannedMemberRemoval[]>;
|
|
34
|
+
/** 回收站里的一条要删的行:目录、在不在、实测字节(缺席 = 0)。purge 与 `/list` 都由它量。 */
|
|
35
|
+
export interface PlannedTrashRow {
|
|
36
|
+
readonly entry: TrashEntry;
|
|
37
|
+
readonly dir: string;
|
|
38
|
+
readonly present: boolean;
|
|
39
|
+
readonly bytes: number;
|
|
40
|
+
}
|
|
41
|
+
/** `/list` 的一行(一组)所需的只读数:`releaseBytes` 是这一组**实测**会释放的字节。 */
|
|
42
|
+
export interface VisibleTrashGroup extends TrashGroupView {
|
|
43
|
+
readonly trash: readonly PlannedTrashRow[];
|
|
44
|
+
readonly members: readonly PlannedMemberRemoval[];
|
|
45
|
+
readonly releaseBytes: number;
|
|
46
|
+
readonly subagentBytes: number;
|
|
47
|
+
readonly strandedBytes: number;
|
|
48
|
+
readonly strandedCount: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* `/list` 的只读计划:一行 = 一组。释放量与 purge 用同一条口径——回收站目录与成员目录**现测**,
|
|
52
|
+
* 不用清册里记的 `sizeBytes`(那是删除那一刻的事实,盘上早就变了)。
|
|
53
|
+
*
|
|
54
|
+
* 盘上后代经 `selectStrandedMembers` 选出(排除全部清册 id、bare id 全局只算一次);目录不在的成员
|
|
55
|
+
* 不进 `members`(与 `planMemberRemovals` 同口径)。
|
|
56
|
+
*/
|
|
57
|
+
export declare function planVisibleTrash(deps: CoreDeps, rows: readonly TrashEntry[], groups: readonly TrashGroupView[]): Promise<VisibleTrashGroup[]>;
|
|
58
|
+
/** 一次永久删除的只读计划:哪些目录要删、各自实测多少字节、哪些会话要先摘归档集。
|
|
59
|
+
* `kind` 同时决定执行器在**删目录阶段**失败时交不交部分回执:`trash`(purge)不带、`orphan`
|
|
60
|
+
* (orphan-clean)带 `{removed, freedBytes, failedSessionId}`——回执与 `kind` 在生产里 1:1,
|
|
61
|
+
* 所以由 `kind` 派生,不另存一份会跟它漂移的字段;更早的 `unarchive` / `detach` 一步不套回执。 */
|
|
62
|
+
export interface RemovalPlan {
|
|
63
|
+
readonly kind: 'trash' | 'orphan';
|
|
64
|
+
readonly trash: readonly PlannedTrashRow[];
|
|
65
|
+
readonly members: readonly PlannedMemberRemoval[];
|
|
66
|
+
readonly unarchive: readonly string[];
|
|
67
|
+
/** 计划量到的合计;成功回执的 freedBytes 只念它。 */
|
|
68
|
+
readonly bytes: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* purge 的删除计划:`targets` 由调用方按组语义(`trash/groups.ts`)选好,这里只量、只筛、只查活体。
|
|
72
|
+
* **计划函数就是执行前的闸**——调用方拿到计划才允许写,所以准入必须发生在任何写之前。
|
|
73
|
+
*
|
|
74
|
+
* - `trash` 按 targets 原序逐个实测(目录缺席的行 `present:false` / 0 字节,但清册行仍要摘)。
|
|
75
|
+
* - 盘上后代经 `selectStrandedMembers` 选出(一次问全部 targets、排除全部清册 id、bare id 全局只计划一次)。
|
|
76
|
+
* - `members` 只收盘上还有目录的成员(目录缺席没有可删的字节);`unarchive` = targets + 这些成员:
|
|
77
|
+
* 只摘这一次真要碰的 id。
|
|
78
|
+
* - 活体准入在量目录与任何写之前逐个查 targets 与**全部枚举到的**盘上后代;任一在跑就整条拒绝、
|
|
79
|
+
* 零副作用。没有跳过它的开关——计划就是执行前的闸(ADR-0006)。
|
|
80
|
+
* - `sessionQuery` 缺席时端口回 `{ available: false }` ⇒ members 为空、不抛(盘上残留留给 `GET /orphans` 兜底)。
|
|
81
|
+
*/
|
|
82
|
+
export declare function planTrashRemoval(deps: CoreDeps, rows: readonly TrashEntry[], targets: readonly TrashEntry[]): Promise<RemovalPlan>;
|
|
83
|
+
/**
|
|
84
|
+
* orphan-clean 的删除计划:成员是调用方扫出来的传递闭包(`orphanGroupsOf`)。
|
|
85
|
+
*
|
|
86
|
+
* - `members` 只收盘上还有目录的成员(目录缺席没有可删的字节),`bytes` 只算这些实测值。
|
|
87
|
+
* - `unarchive` 覆盖**全部传入成员**(含目录缺席):orphan-clean 在删目录之前整批摘归档集,
|
|
88
|
+
* 目录缺席的 id 也可能还挂在归档集里;重试时不会再长出一条幽灵 id。
|
|
89
|
+
* - live 准入覆盖**全部传入成员**(含目录缺席):它们都在这一组的可见面里,任一在跑就整组拒绝。
|
|
90
|
+
* - 回执由 `kind:'orphan'` 派生:删目录阶段失败时执行器要交回 `{removed, freedBytes, failedSessionId}`。
|
|
91
|
+
*/
|
|
92
|
+
export declare function planOrphanRemoval(deps: CoreDeps, members: readonly {
|
|
93
|
+
readonly sessionId: string;
|
|
94
|
+
readonly projectDir: string;
|
|
95
|
+
}[]): Promise<RemovalPlan>;
|
|
96
|
+
/**
|
|
97
|
+
* 按计划执行一次永久删除:只写、不再量、不再判活体(计划就是执行前的闸)。回执的字节念计划
|
|
98
|
+
* (`plan.bytes`),执行期不再累加一遍。
|
|
99
|
+
*
|
|
100
|
+
* 顺序(与迁移前的 purge / orphan-clean 一致):整批摘归档集 → 摘有账本的成员的账本 → 逐条回收站行
|
|
101
|
+
* (在才删目录 → 摘清册行 → 通告成员离场)→ 逐条盘上成员(删目录 → 通告成员离场)。目录删掉就没有
|
|
102
|
+
* "退回去"这回事,中途失败**不回滚**;已经删掉的由下面的计数器如实交回。
|
|
103
|
+
*
|
|
104
|
+
* 失败形状沿用调用方原来的措辞与结构:
|
|
105
|
+
* - `kind:'orphan'`(orphan-clean)⇒ `io` + `{removed, freedBytes, failedSessionId}`,不伪装成整组没动;
|
|
106
|
+
* - `kind:'trash'`(purge)⇒ 同一个标签、不带 details。
|
|
107
|
+
* - orphan-clean 的 unarchive / detach 在旧实现里不包装,这里原样抛出(不改变现有错误形状)。
|
|
108
|
+
*
|
|
109
|
+
* 通告只经 `member-presence`(摘活体三态 + `api-session/removed`),不在这里另写一份。
|
|
110
|
+
*/
|
|
111
|
+
export declare function executeRemoval(deps: CoreDeps, plan: RemovalPlan): Promise<{
|
|
112
|
+
removed: number;
|
|
113
|
+
freedBytes: number;
|
|
114
|
+
}>;
|
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import type { TrashEntry } from '../trash/entry.js';
|
|
2
|
+
import type { CoreDeps } from '../deps.js';
|
|
3
3
|
/**
|
|
4
4
|
* 彻底删除:从回收站里**真的**抹掉一条(或整组、或整站),并连带那些"还留在盘上、没被带走"的后代。
|
|
5
5
|
*
|
|
6
|
-
* 与另外两个动词的关系(ADR-0002):它的 interface
|
|
7
|
-
|
|
6
|
+
* 与另外两个动词的关系(ADR-0002):它的 interface 是"这一组目标 + 一份只读清册快照
|
|
7
|
+
*(targets 缺席 = 清空)";目标由 `verbs/batch-carried.ts` 按组语义算好,批量的"已被同批前一条
|
|
8
|
+
* 带走"判据不在这里。`requestedId` 是唯一的例外参数,但它**不参与任何判据**:只是让
|
|
9
|
+
* `trash-empty` 的原文点得出用户点的是哪一条(原文要上屏,点名承重),批量语义仍在批量层。
|
|
8
10
|
*
|
|
9
|
-
*
|
|
11
|
+
* **这条动词只做两件事:拿目标、把计划交给执行器**。量目录、筛盘上后代、活体准入与"先退归档集、
|
|
12
|
+
* 再摘账本、最后删目录"的顺序都住 `verbs/permanent-removal.ts`——计划只读,执行器只写,计划就是
|
|
13
|
+
* 执行前的闸(别在这里再长出一段手写步骤)。
|
|
14
|
+
*
|
|
15
|
+
* **这条动词不做回滚**:目录删掉就没有"退回去"这回事(2026-10-19 `f2a7757`:删到一半失败时已删目录不回滚;
|
|
10
16
|
* 带部分计数的 `details` 回执只属于 `orphan-clean`,purge 的 `removed` / `freedBytes` 仍只在成功回执里给)。
|
|
11
|
-
*
|
|
17
|
+
* 对应地,`kind:'trash'` 的计划失败时执行器只抛那一步的 `io` 与原文、不带 details。
|
|
12
18
|
*/
|
|
13
|
-
export declare function purgeTrash(deps:
|
|
14
|
-
/**
|
|
15
|
-
|
|
19
|
+
export declare function purgeTrash(deps: CoreDeps): (
|
|
20
|
+
/** 这一组(只读);`undefined` = 清空。批量层由 `batch-carried.ts` 的 `claimOf` 给出,直调者
|
|
21
|
+
* 按 `trash/groups.ts` 的 `groupOf` 自己算;ghost 父的组只有孩子(不含 `requestedId`),照样清
|
|
22
|
+
* 这一组。 */
|
|
23
|
+
targets: readonly TrashEntry[] | undefined,
|
|
24
|
+
/** **只读**清册快照:批量层已经读过一次就递下来;收缩归 `batch-carried.ts`,动词不碰它。 */
|
|
25
|
+
rows: readonly TrashEntry[],
|
|
26
|
+
/** 只用于 `trash-empty` 的原文要点名用户点的那一条;不参与任何判据(批量语义归 `batch-carried.ts`)。 */
|
|
27
|
+
requestedId?: string) => Promise<{
|
|
16
28
|
removed: number;
|
|
17
29
|
freedBytes: number;
|
|
18
30
|
}>;
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { type TrashEntry } from '../trash/entry.js';
|
|
2
|
-
import { type
|
|
3
|
-
import type {
|
|
2
|
+
import { type HostIdentityPort } from '../ports/host-port.js';
|
|
3
|
+
import type { CoreDeps } from '../deps.js';
|
|
4
4
|
/**
|
|
5
5
|
* 恢复 = 把删除那次**按原路退回**,而且按组走:当年是按组一起搬进来的(父 + 它在盘上的孩子),
|
|
6
6
|
* 只回父就会在回收站里留下一堆点不到的孩子行——清单只列父行,那些行既看不见也清不掉。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* 三段与删除对称(成员循环与整组回滚的实现住 `verbs/move-group.ts`):
|
|
9
9
|
* ① **预检整组**(一个字节都不写):每个成员的回收站目录与项目目录都得在。缺席的判据与原来一致
|
|
10
10
|
* (回收站目录不在 = `trash-empty`;原项目目录不在 = `project-missing`,且绝不静默建目录)。
|
|
11
11
|
* 预检在写之前,是为了不让"父已经回到 `sessions/`、孩子因为项目目录没了而回不去"这种半组留在外面。
|
|
12
12
|
* **工作区账本不在这条硬前提里**:宿主的「删除工作区」只删注册、保留日志,记下的工作区没了就按
|
|
13
13
|
* 「未分组」收(判据在 {@link attachableWorkspaceId}),照旧回盘——恢复是加法,不该因为一个可选的账目而失败。
|
|
14
|
-
* ② **逐个恢复**,顺序与删除相反:文件先回来 → 挂账本
|
|
14
|
+
* ② **逐个恢复**,顺序与删除相反:文件先回来 → 挂账本 →(旧清单迁移)取消归档 → 摘清册行。
|
|
15
15
|
* ③ 中途失败时 journal 把这个成员自己退回回收站;**已经恢复完的**兄弟由整组 catch 按相反顺序一起退回。
|
|
16
16
|
*
|
|
17
17
|
* **通告不在这里**:一次批量只该拉一遍宿主会话表、只发一次 `api-session/added`(2026-10-17 那次
|
|
18
|
-
* "恢复很慢"就是逐条发)。通告归 `verbs/batch.ts`(ADR-0002),所以这个动词的 interface
|
|
19
|
-
* "一个条目 id
|
|
18
|
+
* "恢复很慢"就是逐条发)。通告归 `verbs/batch.ts`(ADR-0002),所以这个动词的 interface 是
|
|
19
|
+
* "一个条目 id + 这一组进、回收站那一行出",没有批量专用的开关。
|
|
20
20
|
*/
|
|
21
|
-
export declare function restoreSession(deps:
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
21
|
+
export declare function restoreSession(deps: CoreDeps): (entryId: string,
|
|
22
|
+
/** 这一组(只读):批量层由 `batch-carried.ts` 的 `claimOf` 给出,直调者按 `trash/groups.ts`
|
|
23
|
+
* 的 `groupOf` 自己算。ghost 父(父行已摘、只剩孩子)的组只有孩子、不含 `entryId`,这里照旧
|
|
24
|
+
* 报 `trash-empty`;其余调用按这一组整体恢复。动词**不收缩**它——收缩与同批已带走集都归
|
|
25
|
+
* `verbs/batch-carried.ts`,组语义归 `trash/groups.ts`。 */
|
|
26
|
+
group: readonly TrashEntry[]) => Promise<TrashEntry>;
|
|
25
27
|
/**
|
|
26
28
|
* 这一次恢复**到底挂不挂账本**:交出能挂的那个 `workspaceId`,挂不了就 `undefined`。
|
|
27
29
|
*
|
|
@@ -32,8 +34,8 @@ snapshot?: readonly TrashEntry[]) => Promise<TrashEntry>;
|
|
|
32
34
|
* 判据**只有这一处**:`memberRestore` 拿它决定挂不挂,`GET /list` 拿它决定行上那句「原工作区已不存在,
|
|
33
35
|
* 恢复后归入未分组」说不说。两边各问一遍就会有一天一个说、一个不挂。
|
|
34
36
|
*/
|
|
35
|
-
export declare function attachableWorkspaceId(host: Pick<
|
|
37
|
+
export declare function attachableWorkspaceId(host: Pick<HostIdentityPort, 'workspaceExists'>, entry: TrashEntry): Promise<string | undefined>;
|
|
36
38
|
/** 这一条记着的工作区**是不是已经没了**(区别于"删除时它就没有账目"的空串)——清单行据此决定
|
|
37
39
|
* 要不要在点之前说那句「原工作区已不存在,恢复后归入未分组」。由 {@link attachableWorkspaceId}
|
|
38
40
|
* 派生,不另起一份判据:那样会有一天一个说、一个不挂。 */
|
|
39
|
-
export declare function isWorkspaceMissing(host: Pick<
|
|
41
|
+
export declare function isWorkspaceMissing(host: Pick<HostIdentityPort, 'workspaceExists'>, entry: TrashEntry): Promise<boolean>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xrn1997/dsh-session-delete",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "在 DeepSeek Harness 里删除会话:删除=移入回收站(可恢复),另设显式彻底删除",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
"dshReleases": {
|
|
74
74
|
"0.2.0-rc.2": "compatible",
|
|
75
75
|
"0.2.1-rc.1": "compatible",
|
|
76
|
-
"0.2.1-rc.2": "compatible"
|
|
76
|
+
"0.2.1-rc.2": "compatible",
|
|
77
|
+
"0.2.1-rc.3": "compatible"
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
},
|