@xrn1997/dsh-session-delete 0.2.1 → 0.3.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/README.md +46 -124
- package/icon.svg +18 -18
- package/lib/client.js +1749 -962
- package/lib/index.js +1841 -985
- package/lib/types/api/dispatch.d.ts +24 -44
- package/lib/types/api/route-table.d.ts +35 -0
- package/lib/types/api/trash-rows.d.ts +31 -0
- package/lib/types/client/batch-bar.d.ts +6 -6
- package/lib/types/client/batch-confirm.d.ts +5 -3
- 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/copy.d.ts +16 -1
- package/lib/types/client/delete-confirm.d.ts +2 -0
- package/lib/types/client/host-rows.d.ts +35 -0
- package/lib/types/client/main-view.d.ts +37 -0
- package/lib/types/client/receipt.d.ts +43 -0
- package/lib/types/client/remote.d.ts +62 -76
- package/lib/types/client/store.d.ts +17 -0
- package/lib/types/client/trash-confirm.d.ts +39 -0
- package/lib/types/client/trash-panel.d.ts +4 -2
- package/lib/types/client/trash-store.d.ts +2 -0
- package/lib/types/client/trash-view.d.ts +38 -0
- package/lib/types/client/ui/icons.d.ts +3 -3
- package/lib/types/client/undo-toast.d.ts +21 -5
- 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 +55 -0
- package/lib/types/client/use-trash-query.d.ts +32 -0
- package/lib/types/client/use-trash-selection.d.ts +32 -0
- package/lib/types/client/write-commit.d.ts +10 -0
- package/lib/types/deps.d.ts +14 -0
- package/lib/types/index.d.ts +9 -15
- 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 +70 -20
- package/lib/types/remote/map-error.d.ts +3 -3
- package/lib/types/shared/wire.d.ts +107 -11
- package/lib/types/trash/detail.d.ts +6 -24
- package/lib/types/trash/entry.d.ts +29 -6
- package/lib/types/trash/groups.d.ts +45 -0
- package/lib/types/trash/manifest.d.ts +2 -1
- package/lib/types/trash/reconcile.d.ts +2 -2
- package/lib/types/trash/storage-manifest.d.ts +12 -0
- package/lib/types/verbs/{apply.d.ts → batch.d.ts} +1 -1
- package/lib/types/verbs/delete-session.d.ts +12 -18
- package/lib/types/verbs/errors.d.ts +17 -6
- package/lib/types/verbs/member-journal.d.ts +27 -0
- package/lib/types/verbs/member-presence.d.ts +35 -0
- package/lib/types/verbs/orphan-subagents.d.ts +58 -0
- package/lib/types/verbs/permanent-removal.d.ts +113 -0
- package/lib/types/verbs/purge-trash.d.ts +19 -3
- package/lib/types/verbs/restore-session.d.ts +15 -13
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +105 -105
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
1
|
+
/** 五个码 / 三个路由自检码的**家都在 `shared/wire.ts`**(ADR-0003):浏览器半也要认它们(比如
|
|
2
|
+
* `not-found` 在界面上是中性提示而不是失败),所以它们属于两半共同的契约,不属于 Node 半。
|
|
3
|
+
* 这里只转出旧名,免得调用点全改一遍。 */
|
|
4
|
+
import { type RouteCode, type VerbCode } from '../shared/wire.js';
|
|
3
5
|
export declare const VERB_CODES: readonly ["live", "not-found", "trash-empty", "project-missing", "io"];
|
|
4
|
-
export type VerbCode = (typeof VERB_CODES)[number];
|
|
5
|
-
/** 路由层的自检(不是五个动词码之一):**请求本身不对**——方法、路径、body 形状。
|
|
6
|
-
* 它上的是 HTTP 状态那条口(404/405),信封里也带着它,好让只看信封的调用方拿到同一句原文。 */
|
|
7
6
|
export declare const ROUTE_CODES: readonly ["bad-method", "not-found-route", "bad-request"];
|
|
8
|
-
export type RouteCode
|
|
7
|
+
export type { RouteCode, VerbCode };
|
|
8
|
+
/** `error.code` 是不是本仓的动词码(`sessiondelete/<码>`)。**唯一判据**:`journalFailure` 与永久删除
|
|
9
|
+
* 执行器要"已经是动词码就原样抛"时都问这一句。同文件的 `withRollbackFailures` 还要码的后缀,所以它
|
|
10
|
+
* 就地 `slice` 一次——前缀仍只住 `VERB_CODE_PREFIX` 这一处,别在调用点另写一份字面量。 */
|
|
11
|
+
export declare function isVerbCodeError(error: unknown): boolean;
|
|
9
12
|
/** 一次失败的**结构**:码、上屏原文、可选 cause(`map-error` 沿 cause 链认宿主类型化错误)、
|
|
10
13
|
* 可选的结构化附带物(对齐官方 `rpcErrorSchema` 的第三件 `details`)。 */
|
|
11
14
|
export interface VerbError extends Error {
|
|
@@ -18,3 +21,11 @@ export declare function verbError(code: VerbCode, message: string, cause?: unkno
|
|
|
18
21
|
export declare function routeError(code: RouteCode, httpStatus: number, message: string): VerbError & {
|
|
19
22
|
httpStatus: number;
|
|
20
23
|
};
|
|
24
|
+
/** 一条回滚步骤失败的**标签句**:label 说的是"这一步在退什么",原文带出**那一步抛的东西**
|
|
25
|
+
* (`Error` 取 message,其余一律 `String()`;对象带 `message` 的差异归 `map-error.messageOf` 管)。
|
|
26
|
+
* 调用方不许另抄这份格式——那样 details 里的句子会随各处 catch 变形,界面上就变成"同一个失败三个说法"。 */
|
|
27
|
+
export declare function rollbackReason(label: string, error: unknown): string;
|
|
28
|
+
/** 原错误保留,回滚失败作为 `details.rollbackFailures` 上线;码仍按原错误的动词码,
|
|
29
|
+
* 没有可继承的就落 io。**内层的回滚失败要并进来**:一个成员自己退失败、兄弟也退失败时,
|
|
30
|
+
* 两份失败都得在 details 里,消息也不能出现第二个「回滚未完成」。 */
|
|
31
|
+
export declare function withRollbackFailures(error: unknown, failures: readonly string[]): VerbError;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface JournalStep {
|
|
2
|
+
/** 这一步在做什么。失败原文按「`<label>:<原错误>`」拼——与迁移前那 22 处 `rollbackReason` 的措辞同形。 */
|
|
3
|
+
readonly label: string;
|
|
4
|
+
/** 做这一步。抛什么都由 journal 折成 `sessiondelete/io`(除非它自己已经是一个动词码)。 */
|
|
5
|
+
run(): Promise<void>;
|
|
6
|
+
/** 反向动作。**`run` 成功之后**才会被登记;返回**这一步自己**没能退干净的原因列表(空 = 完全退回)。 */
|
|
7
|
+
undo?(): Promise<readonly string[]>;
|
|
8
|
+
/** 这一步**自己**失败时也跑 `undo`:给"可能已经半做完"的非原子步骤用(取消归档是唯一的例子)。 */
|
|
9
|
+
readonly undoOnFailure?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface Journal {
|
|
12
|
+
/** 按顺序跑完。中途失败 ⇒ 已做成的按相反顺序退回,然后把原错误(带 `rollbackFailures`)抛出去。 */
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
/** 把**已经做成**的步骤按相反顺序退回去(整组回滚用:组里后面那个成员搬不动时叫这一句)。 */
|
|
15
|
+
undo(): Promise<readonly string[]>;
|
|
16
|
+
}
|
|
17
|
+
/** 已经把某个前缀做成了的 journal——`undo()` 就退那一个前缀。 */
|
|
18
|
+
export declare function createJournal(steps: readonly JournalStep[]): Journal;
|
|
19
|
+
/** 跑一步反向动作并把它自己的失败收成一条原因(不抛:这些失败由调用方汇总成 `rollbackFailures`)。 */
|
|
20
|
+
export declare function attempt(label: string, run: () => Promise<unknown>): Promise<readonly string[]>;
|
|
21
|
+
/**
|
|
22
|
+
* **组级回滚**:把已经做成的成员按相反顺序退回;某一步退不干净时把失败并进原错误的
|
|
23
|
+
* `details.rollbackFailures`,绝不让原错误独走(2026-10-19 `f2a7757`;那些失败句子的合并与措辞住
|
|
24
|
+
* `src/verbs/errors.ts`,这里只是唯一的调用方)。删除与恢复两个按组走的动词共用这一处,
|
|
25
|
+
* "相反顺序 + 失败合并"不再各写一遍。
|
|
26
|
+
*/
|
|
27
|
+
export declare function rollbackJournals(journals: readonly Journal[], error: unknown): Promise<never>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 成员离场 / 归位的通告协议:摘活体、三态判断、成对事件只在这里做。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有它:delete(成功与回滚)/ restore 回滚 / purge(两个分支)/ orphan-clean
|
|
5
|
+
* 六处调用曾经各写一遍
|
|
6
|
+
* `evictLive → !== 'unknown' → announceRemoved`;删除回滚还要自己拼
|
|
7
|
+
* `movedBack && workspaceBack` 才发 `announceRestored`。
|
|
8
|
+
* 这条协议说错一次就是客户端留着假行、或删掉真行,所以只留一个家。
|
|
9
|
+
*
|
|
10
|
+
* 边界:
|
|
11
|
+
* - 批量恢复的 `announceRestoredBatch` 不进来:一次预取、一批一次通告归 `verbs/batch.ts`(ADR-0002)。
|
|
12
|
+
* - 不读 journal 的失败串:调用方交事实(目录 / 账本是否回去),真值表住这里。
|
|
13
|
+
* - 本模块用到的三条(`evictLive` / `announceRemoved` / `announceRestored`)按契约不抛(evictLive 三态、
|
|
14
|
+
* announce* 内部吞异常),这里不再叠 catch。
|
|
15
|
+
*/
|
|
16
|
+
import type { HostPresencePort } from '../ports/host-port.js';
|
|
17
|
+
/** 本模块实际用到的三条宿主能力。取窄面而不是整个 `HostPresencePort`:那条协议里还有批量通告与
|
|
18
|
+
* 摘要预取,它们各有自己的家(`verbs/batch.ts`),这里不该因为共用同一颗 adapter 就声明依赖。 */
|
|
19
|
+
export type MemberPresenceHost = Pick<HostPresencePort, 'evictLive' | 'announceRemoved' | 'announceRestored'>;
|
|
20
|
+
export interface MemberBackFacts {
|
|
21
|
+
/** 目录搬回原处了吗。 */
|
|
22
|
+
readonly diskBack: boolean;
|
|
23
|
+
/** 账本挂回去了吗(本来没有账目时算 true)。 */
|
|
24
|
+
readonly workspaceBack: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface MemberPresence {
|
|
27
|
+
/** 成员已经不在宿主可见列表:摘活体;三态不是 unknown 时发 removed。
|
|
28
|
+
* `backInTrash: false` 表示目录没退回回收站(恢复回滚失败),此时什么都不做。 */
|
|
29
|
+
memberGone(sessionId: string, facts?: {
|
|
30
|
+
readonly backInTrash?: boolean;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
/** 成员已经回到原来的可见状态:目录与账本两件写都回去才发 added。 */
|
|
33
|
+
memberBack(sessionId: string, facts: MemberBackFacts): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export declare function createMemberPresence(host: MemberPresenceHost): MemberPresence;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type SessionIdentity } from '../ports/host-port.js';
|
|
2
|
+
import type { OrphanCleanStatsWire, OrphanReportWire } from '../shared/wire.js';
|
|
3
|
+
import type { CoreDeps } from '../deps.js';
|
|
4
|
+
/** 组里一个人的盘上身份(字节是**扫描那一刻**量的,清理时按目录重量一次,不拿这份快照冒充准数)。 */
|
|
5
|
+
export interface OrphanMember {
|
|
6
|
+
readonly sessionId: string;
|
|
7
|
+
readonly projectDir: string;
|
|
8
|
+
readonly bytes: number;
|
|
9
|
+
}
|
|
10
|
+
/** 一组无主子会话:根是那"父没了"的 subagent,`members` 是它的**传递闭包**(含根自己)。 */
|
|
11
|
+
export interface OrphanGroup {
|
|
12
|
+
readonly root: OrphanMember;
|
|
13
|
+
readonly parentSessionId: string;
|
|
14
|
+
readonly members: readonly OrphanMember[];
|
|
15
|
+
}
|
|
16
|
+
/** {@link orphanGroupsOf} 的中间形状:还没量字节的那一组(纯函数不碰盘)。 */
|
|
17
|
+
interface RawGroup {
|
|
18
|
+
readonly root: SessionIdentity;
|
|
19
|
+
readonly parentSessionId: string;
|
|
20
|
+
readonly members: readonly SessionIdentity[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 无主子会话 = `origin:"subagent"`、父**既不在当前语料(live ∪ persisted)也不在回收站清册里**的会话。
|
|
24
|
+
*
|
|
25
|
+
* 为什么清册里的父不算孤儿:那种是 **stranded**——父还在回收站里等着,孩子本该跟着父一起恢复或一起
|
|
26
|
+
* 彻底删除(组语义见 `src/trash/groups.ts`),把它的根当孤儿删掉就是**替用户做了一个没人点过的决定**。孤儿只
|
|
27
|
+
* 有一种来路:父被彻底删掉了(或被手工从盘上清掉了),清册里再也没有这一行。
|
|
28
|
+
*
|
|
29
|
+
* 根按**传递闭包**收全部后代(与 `HostCorpusPort.subagentMembersOf` 同一口径:孩子与孙代都算,深度不设上限),
|
|
30
|
+
* 同一条记录只归一组(`claimed`)——数据里真出现环时结果是"少数一条",不是把一个会话删两遍。
|
|
31
|
+
* 归堆与去重**两边都去 `session-` 前缀**:同一个父在两代宿主里写法不同(见 `bareSessionId` 的来历)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function orphanGroupsOf(corpus: readonly SessionIdentity[], trashSessionIds: readonly string[]): RawGroup[];
|
|
34
|
+
/** 纯读:调用方应先走一次 `/list` 对账(面板的 refresh 就是这样);本函数不写清单。
|
|
35
|
+
* 写动作 `orphan-clean` 会自己先 reconcile,避免拿一份过期清册判"父没了"。
|
|
36
|
+
*
|
|
37
|
+
* `listSessions()` 回 `{ available: false }` 时**必须抛**:那是宿主的会话语料服务没挂,不是"这个家没有孤儿"。
|
|
38
|
+
* 拿缺席当空语料 ⇒ 报告说"零条孤儿"、界面上一句"没有要清理的",而盘上真有几百 MB——那是假话,
|
|
39
|
+
* 所以这里按 `io` 响亮失败(AGENTS.md:`available: false` 意味着服务缺席,扫描方不许冒充读数)。 */
|
|
40
|
+
export declare function loadOrphanGroups(deps: CoreDeps): Promise<readonly OrphanGroup[]>;
|
|
41
|
+
/** 把一组组收成那份报告:根清单 + 闭包总数/总字节 + 失踪父的**去重计数** + 按项目目录的分摊。
|
|
42
|
+
* `groups` 按字节从多到少排(界面上"哪家还剩得最多"要看第一行)。 */
|
|
43
|
+
export declare function reportOrphans(groups: readonly OrphanGroup[]): OrphanReportWire;
|
|
44
|
+
/**
|
|
45
|
+
* 清一组:`planOrphanRemoval`(只读计划,整组 live 准入在一切写之前)→ `executeRemoval`(只写)。
|
|
46
|
+
* 量目录、摘归档集与账本、删目录与成员通告的顺序和失败形状都住 `verbs/permanent-removal.ts`;
|
|
47
|
+
* 这里只把这一组接上去,别在这再长出一段手写步骤(purge 走的是同一条路)。
|
|
48
|
+
*
|
|
49
|
+
* 顺序与删除那一半是**对偶**的,但不是照抄:
|
|
50
|
+
* - 删除那一半不再用归档藏会话(ADR-0008):它摘账本、搬目录,最后才摘活体。这里目录是**删掉**
|
|
51
|
+
* 不是搬走,藏不藏都不影响可见性 ⇒ 归档集反而**在写之前整批摘掉**:失败时目录还在,下一次还能重试,
|
|
52
|
+
* 不会留下"会话没了但还在归档集里挂着"的幽灵 id。
|
|
53
|
+
* - `unarchive` 对未归档的会话是官方幂等 no-op(`registry.unarchiveSession`),所以这里不需要那次
|
|
54
|
+
* `wasArchived` 读——这里没有"还原到删除前"的义务。
|
|
55
|
+
* - 摘账本只在账本真记着它时做(`workspaceOf` 未命中 = 本来就是「未分组」,不是故障)。
|
|
56
|
+
*/
|
|
57
|
+
export declare function cleanOrphanGroup(deps: CoreDeps, group: OrphanGroup): Promise<OrphanCleanStatsWire>;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
readonly bytes: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* purge 的删除计划:`targets` 由调用方按组语义(`trash/groups.ts`)选好,这里只量、只筛、只查活体。
|
|
71
|
+
*
|
|
72
|
+
* - `trash` 按 targets 原序逐个实测(目录缺席的行 `present:false` / 0 字节,但清册行仍要摘)。
|
|
73
|
+
* - 盘上后代经 `selectStrandedMembers` 选出(一次问全部 targets、排除全部清册 id、bare id 全局只计划一次)。
|
|
74
|
+
* - `members` 只收盘上还有目录的成员(目录缺席没有可删的字节);`unarchive` = targets + 这些成员:
|
|
75
|
+
* 只摘这一次真要碰的 id。
|
|
76
|
+
* - `admission:'live'` 在量目录与任何写之前逐个查 targets 与**全部枚举到的**盘上后代;
|
|
77
|
+
* 任一在跑就整条拒绝,零副作用。
|
|
78
|
+
* - `sessionQuery` 缺席时端口回 `{ available: false }` ⇒ members 为空、不抛(盘上残留留给 `GET /orphans` 兜底)。
|
|
79
|
+
*/
|
|
80
|
+
export declare function planTrashRemoval(deps: CoreDeps, rows: readonly TrashEntry[], targets: readonly TrashEntry[], options: {
|
|
81
|
+
readonly admission: 'none' | 'live';
|
|
82
|
+
}): 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
|
+
*
|
|
99
|
+
* 顺序(与迁移前的 purge / orphan-clean 一致):整批摘归档集 → 摘有账本的成员的账本 → 逐条回收站行
|
|
100
|
+
* (在才删目录 → 摘清册行 → 通告成员离场)→ 逐条盘上成员(删目录 → 通告成员离场)。目录删掉就没有
|
|
101
|
+
* "退回去"这回事,中途失败**不回滚**;已经删掉的由下面的计数器如实交回。
|
|
102
|
+
*
|
|
103
|
+
* 失败形状沿用调用方原来的措辞与结构:
|
|
104
|
+
* - `kind:'orphan'`(orphan-clean)⇒ `io` + `{removed, freedBytes, failedSessionId}`,不伪装成整组没动;
|
|
105
|
+
* - `kind:'trash'`(purge)⇒ 同一个标签、不带 details。
|
|
106
|
+
* - orphan-clean 的 unarchive / detach 在旧实现里不包装,这里原样抛出(不改变现有错误形状)。
|
|
107
|
+
*
|
|
108
|
+
* 通告只经 `member-presence`(摘活体三态 + `api-session/removed`),不在这里另写一份。
|
|
109
|
+
*/
|
|
110
|
+
export declare function executeRemoval(deps: CoreDeps, plan: RemovalPlan): Promise<{
|
|
111
|
+
removed: number;
|
|
112
|
+
freedBytes: number;
|
|
113
|
+
}>;
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
1
|
+
import type { TrashEntry } from '../trash/entry.js';
|
|
2
|
+
import type { CoreDeps } from '../deps.js';
|
|
3
|
+
/**
|
|
4
|
+
* 彻底删除:从回收站里**真的**抹掉一条(或整组、或整站),并连带那些"还留在盘上、没被带走"的后代。
|
|
5
|
+
*
|
|
6
|
+
* 与另外两个动词的关系(ADR-0002):它的 interface 只有"一个条目 id(或缺席 = 清空)",
|
|
7
|
+
* 批量的快照与"已被同批前一条带走"的判据不在这里(归 `verbs/batch.ts`)。
|
|
8
|
+
*
|
|
9
|
+
* **这条动词只做两件事:选目标、拿计划交给执行器**。量目录、筛盘上后代、活体准入与"先退归档集、
|
|
10
|
+
* 再摘账本、最后删目录"的顺序都住 `verbs/permanent-removal.ts`——计划只读,执行器只写,计划就是
|
|
11
|
+
* 执行前的闸(别在这里再长出一段手写步骤)。
|
|
12
|
+
*
|
|
13
|
+
* **这条动词不做回滚**:目录删掉就没有"退回去"这回事(2026-10-19 `f2a7757`:删到一半失败时已删目录不回滚;
|
|
14
|
+
* 带部分计数的 `details` 回执只属于 `orphan-clean`,purge 的 `removed` / `freedBytes` 仍只在成功回执里给)。
|
|
15
|
+
* 对应地,`kind:'trash'` 的计划失败时执行器只抛那一步的 `io` 与原文、不带 details。
|
|
16
|
+
*/
|
|
17
|
+
export declare function purgeTrash(deps: CoreDeps): (entryId?: string,
|
|
18
|
+
/** **只读**清册快照:批量层已经读过一次就递下来(ADR-0002);收缩归批量层,动词不碰它。 */
|
|
19
|
+
snapshot?: readonly TrashEntry[]) => Promise<{
|
|
4
20
|
removed: number;
|
|
5
21
|
freedBytes: number;
|
|
6
22
|
}>;
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
* 只回父就会在回收站里留下一堆点不到的孩子行——清单只列父行,那些行既看不见也清不掉。
|
|
@@ -11,15 +11,17 @@ import type { DeleteDeps } from './delete-session.js';
|
|
|
11
11
|
* 预检在写之前,是为了不让"父已经回到 `sessions/`、孩子因为项目目录没了而回不去"这种半组留在外面。
|
|
12
12
|
* **工作区账本不在这条硬前提里**:宿主的「删除工作区」只删注册、保留日志,记下的工作区没了就按
|
|
13
13
|
* 「未分组」收(判据在 {@link attachableWorkspaceId}),照旧回盘——恢复是加法,不该因为一个可选的账目而失败。
|
|
14
|
-
* ② **逐个恢复**,顺序与删除相反:文件先回来 → 挂账本
|
|
15
|
-
* ③
|
|
14
|
+
* ② **逐个恢复**,顺序与删除相反:文件先回来 → 挂账本 →(旧清单迁移)取消归档 → 摘清册行。
|
|
15
|
+
* ③ 中途失败时 journal 把这个成员自己退回回收站;**已经恢复完的**兄弟由整组 catch 按相反顺序一起退回。
|
|
16
|
+
*
|
|
17
|
+
* **通告不在这里**:一次批量只该拉一遍宿主会话表、只发一次 `api-session/added`(2026-10-17 那次
|
|
18
|
+
* "恢复很慢"就是逐条发)。通告归 `verbs/batch.ts`(ADR-0002),所以这个动词的 interface 只有
|
|
19
|
+
* "一个条目 id 进、回收站那一行出",没有批量专用的开关。
|
|
16
20
|
*/
|
|
17
|
-
export declare function restoreSession(deps:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
readonly suppressAnnounce?: boolean;
|
|
22
|
-
}) => Promise<TrashEntry>;
|
|
21
|
+
export declare function restoreSession(deps: CoreDeps): (entryId: string,
|
|
22
|
+
/** **只读**清册快照:批量层已经读过一次就递下来,整批因此只读一次(ADR-0002)。
|
|
23
|
+
* 动词**不收缩**它——收缩与"已被同批前一条带走"的判据都归 `verbs/batch.ts`。 */
|
|
24
|
+
snapshot?: readonly TrashEntry[]) => Promise<TrashEntry>;
|
|
23
25
|
/**
|
|
24
26
|
* 这一次恢复**到底挂不挂账本**:交出能挂的那个 `workspaceId`,挂不了就 `undefined`。
|
|
25
27
|
*
|
|
@@ -27,11 +29,11 @@ export declare function restoreSession(deps: DeleteDeps): (entryId: string, opti
|
|
|
27
29
|
* 宿主的「删除工作区」只删注册、保留目录与每一份日志,那些会话之后本来就在「未分组」下。
|
|
28
30
|
* 恢复不该为了挂一个不存在的账本把整条退回回收站。
|
|
29
31
|
*
|
|
30
|
-
* 判据**只有这一处**:`
|
|
32
|
+
* 判据**只有这一处**:`memberRestore` 拿它决定挂不挂,`GET /list` 拿它决定行上那句「原工作区已不存在,
|
|
31
33
|
* 恢复后归入未分组」说不说。两边各问一遍就会有一天一个说、一个不挂。
|
|
32
34
|
*/
|
|
33
|
-
export declare function attachableWorkspaceId(host: Pick<
|
|
35
|
+
export declare function attachableWorkspaceId(host: Pick<HostIdentityPort, 'workspaceExists'>, entry: TrashEntry): Promise<string | undefined>;
|
|
34
36
|
/** 这一条记着的工作区**是不是已经没了**(区别于"删除时它就没有账目"的空串)——清单行据此决定
|
|
35
37
|
* 要不要在点之前说那句「原工作区已不存在,恢复后归入未分组」。由 {@link attachableWorkspaceId}
|
|
36
38
|
* 派生,不另起一份判据:那样会有一天一个说、一个不挂。 */
|
|
37
|
-
export declare function isWorkspaceMissing(host: Pick<
|
|
39
|
+
export declare function isWorkspaceMissing(host: Pick<HostIdentityPort, 'workspaceExists'>, entry: TrashEntry): Promise<boolean>;
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"meta": {
|
|
3
|
-
"title": "Session Delete",
|
|
4
|
-
"description": "Delete sessions: delete moves them to a recoverable trash, with an explicit permanent delete."
|
|
5
|
-
}
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "Session Delete",
|
|
4
|
+
"description": "Delete sessions: delete moves them to a recoverable trash, with an explicit permanent delete."
|
|
5
|
+
}
|
|
6
|
+
}
|
package/locale/zh.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"meta": {
|
|
3
|
-
"title": "会话删除",
|
|
4
|
-
"description": "删除会话:删除 = 移入回收站(可恢复),另设显式的彻底删除。"
|
|
5
|
-
}
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "会话删除",
|
|
4
|
+
"description": "删除会话:删除 = 移入回收站(可恢复),另设显式的彻底删除。"
|
|
5
|
+
}
|
|
6
|
+
}
|
package/package.json
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@xrn1997/dsh-session-delete",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "在 DeepSeek Harness 里删除会话:删除=移入回收站(可恢复),另设显式彻底删除",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/types/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/types/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./client": {
|
|
14
|
-
"types": "./lib/types/client/index.d.ts",
|
|
15
|
-
"default": "./lib/client.js"
|
|
16
|
-
},
|
|
17
|
-
"./locale/*.json": "./locale/*.json",
|
|
18
|
-
"./package.json": "./package.json"
|
|
19
|
-
},
|
|
20
|
-
"icon": "./icon.svg",
|
|
21
|
-
"files": [
|
|
22
|
-
"lib/index.js",
|
|
23
|
-
"lib/client.js",
|
|
24
|
-
"lib/types/**/*.d.ts",
|
|
25
|
-
"locale",
|
|
26
|
-
"icon.svg",
|
|
27
|
-
"cordis.patch.yml",
|
|
28
|
-
"README.md",
|
|
29
|
-
"LICENSE"
|
|
30
|
-
],
|
|
31
|
-
"engines": {
|
|
32
|
-
"node": ">=24",
|
|
33
|
-
"dsh": ">=0.1.7-rc.2 <0.3.0-0"
|
|
34
|
-
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "tsdown && tsc -p tsconfig.build.json",
|
|
37
|
-
"typecheck": "tsc --noEmit",
|
|
38
|
-
"test": "vitest run",
|
|
39
|
-
"prepack": "npm run build"
|
|
40
|
-
},
|
|
41
|
-
"peerDependencies": {
|
|
42
|
-
"@deepseek-ai/cordis": "^4.0.4",
|
|
43
|
-
"react": "^18.3.1"
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"@deepseek-ai/cordis": "^4.0.4",
|
|
47
|
-
"@testing-library/dom": "^10.4.2",
|
|
48
|
-
"@testing-library/jest-dom": "^6.10.0",
|
|
49
|
-
"@testing-library/react": "^16.3.3",
|
|
50
|
-
"@types/node": "24.19.0",
|
|
51
|
-
"@types/react": "18.3.31",
|
|
52
|
-
"@types/react-dom": "18.3.5",
|
|
53
|
-
"@types/semver": "^7.8.0",
|
|
54
|
-
"jsdom": "^26.1.0",
|
|
55
|
-
"react": "^18.3.1",
|
|
56
|
-
"react-dom": "18.3.1",
|
|
57
|
-
"semver": "^7.8.5",
|
|
58
|
-
"tsdown": "0.23.0",
|
|
59
|
-
"typescript": "5.9.3",
|
|
60
|
-
"vitest": "3.2.7"
|
|
61
|
-
},
|
|
62
|
-
"dsh": {
|
|
63
|
-
"bundle": {
|
|
64
|
-
"patch": "./cordis.patch.yml"
|
|
65
|
-
},
|
|
66
|
-
"client": {
|
|
67
|
-
"inject": [
|
|
68
|
-
"@deepseek-ai/dsh-client-locale"
|
|
69
|
-
],
|
|
70
|
-
"platform": "web"
|
|
71
|
-
},
|
|
72
|
-
"compatibility": {
|
|
73
|
-
"dshReleases": {
|
|
74
|
-
"0.2.0-rc.2": "compatible",
|
|
75
|
-
"0.2.1-rc.1": "compatible",
|
|
76
|
-
"0.2.1-rc.2": "compatible"
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
"publishConfig": {
|
|
81
|
-
"access": "public"
|
|
82
|
-
},
|
|
83
|
-
"repository": {
|
|
84
|
-
"type": "git",
|
|
85
|
-
"url": "git+https://github.com/xrn1997/dsh-session-delete.git"
|
|
86
|
-
},
|
|
87
|
-
"homepage": "https://github.com/xrn1997/dsh-session-delete#readme",
|
|
88
|
-
"bugs": {
|
|
89
|
-
"url": "https://github.com/xrn1997/dsh-session-delete/issues"
|
|
90
|
-
},
|
|
91
|
-
"keywords": [
|
|
92
|
-
"dsh",
|
|
93
|
-
"deepseek-harness",
|
|
94
|
-
"plugin",
|
|
95
|
-
"session",
|
|
96
|
-
"trash",
|
|
97
|
-
"delete"
|
|
98
|
-
],
|
|
99
|
-
"author": "xrn1997",
|
|
100
|
-
"license": "Apache-2.0",
|
|
101
|
-
"dependencies": {
|
|
102
|
-
"react-markdown": "^10.1.0",
|
|
103
|
-
"remark-gfm": "^4.0.1"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@xrn1997/dsh-session-delete",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "在 DeepSeek Harness 里删除会话:删除=移入回收站(可恢复),另设显式彻底删除",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/types/client/index.d.ts",
|
|
15
|
+
"default": "./lib/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./locale/*.json": "./locale/*.json",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"icon": "./icon.svg",
|
|
21
|
+
"files": [
|
|
22
|
+
"lib/index.js",
|
|
23
|
+
"lib/client.js",
|
|
24
|
+
"lib/types/**/*.d.ts",
|
|
25
|
+
"locale",
|
|
26
|
+
"icon.svg",
|
|
27
|
+
"cordis.patch.yml",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24",
|
|
33
|
+
"dsh": ">=0.1.7-rc.2 <0.3.0-0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsdown && tsc -p tsconfig.build.json",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"prepack": "npm run build"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
43
|
+
"react": "^18.3.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
47
|
+
"@testing-library/dom": "^10.4.2",
|
|
48
|
+
"@testing-library/jest-dom": "^6.10.0",
|
|
49
|
+
"@testing-library/react": "^16.3.3",
|
|
50
|
+
"@types/node": "24.19.0",
|
|
51
|
+
"@types/react": "18.3.31",
|
|
52
|
+
"@types/react-dom": "18.3.5",
|
|
53
|
+
"@types/semver": "^7.8.0",
|
|
54
|
+
"jsdom": "^26.1.0",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-dom": "18.3.1",
|
|
57
|
+
"semver": "^7.8.5",
|
|
58
|
+
"tsdown": "0.23.0",
|
|
59
|
+
"typescript": "5.9.3",
|
|
60
|
+
"vitest": "3.2.7"
|
|
61
|
+
},
|
|
62
|
+
"dsh": {
|
|
63
|
+
"bundle": {
|
|
64
|
+
"patch": "./cordis.patch.yml"
|
|
65
|
+
},
|
|
66
|
+
"client": {
|
|
67
|
+
"inject": [
|
|
68
|
+
"@deepseek-ai/dsh-client-locale"
|
|
69
|
+
],
|
|
70
|
+
"platform": "web"
|
|
71
|
+
},
|
|
72
|
+
"compatibility": {
|
|
73
|
+
"dshReleases": {
|
|
74
|
+
"0.2.0-rc.2": "compatible",
|
|
75
|
+
"0.2.1-rc.1": "compatible",
|
|
76
|
+
"0.2.1-rc.2": "compatible"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public"
|
|
82
|
+
},
|
|
83
|
+
"repository": {
|
|
84
|
+
"type": "git",
|
|
85
|
+
"url": "git+https://github.com/xrn1997/dsh-session-delete.git"
|
|
86
|
+
},
|
|
87
|
+
"homepage": "https://github.com/xrn1997/dsh-session-delete#readme",
|
|
88
|
+
"bugs": {
|
|
89
|
+
"url": "https://github.com/xrn1997/dsh-session-delete/issues"
|
|
90
|
+
},
|
|
91
|
+
"keywords": [
|
|
92
|
+
"dsh",
|
|
93
|
+
"deepseek-harness",
|
|
94
|
+
"plugin",
|
|
95
|
+
"session",
|
|
96
|
+
"trash",
|
|
97
|
+
"delete"
|
|
98
|
+
],
|
|
99
|
+
"author": "xrn1997",
|
|
100
|
+
"license": "Apache-2.0",
|
|
101
|
+
"dependencies": {
|
|
102
|
+
"react-markdown": "^10.1.0",
|
|
103
|
+
"remark-gfm": "^4.0.1"
|
|
104
|
+
}
|
|
105
|
+
}
|