@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { RemoteOutcome } from './remote.js';
|
|
2
|
+
/** 一段在飞的动作:检查只读,清理会写。状态行靠它分开说「检查中…」与「清理中…」;`null` = 空闲。 */
|
|
3
|
+
export type MaintenancePhase = 'scanning' | 'cleaning';
|
|
4
|
+
/** 一段维护交给界面的全部:三份读数(报告 / 失败 / 中性提示)、一个确认框、一档忙碌,加四个动作。 */
|
|
5
|
+
export interface MaintenanceSectionView<R> {
|
|
6
|
+
/** `null` = 还没检查(或上一次检查失败——失败在 `failure` 单独一格,不折成零条)。 */
|
|
7
|
+
readonly report: R | null;
|
|
8
|
+
readonly failure: string | null;
|
|
9
|
+
/** 「这一组已经不在」/ 上一趟清理的回执——不是失败:单独一条中性提示。 */
|
|
10
|
+
readonly notice: string | null;
|
|
11
|
+
readonly confirm: boolean;
|
|
12
|
+
readonly phase: MaintenancePhase | null;
|
|
13
|
+
/** 段自己给的忙碌(在飞即忙),不再作为 prop 传递。 */
|
|
14
|
+
readonly busy: boolean;
|
|
15
|
+
/** 手动检查(也是失败块的重试)。 */
|
|
16
|
+
scan: () => Promise<void>;
|
|
17
|
+
/** 唯一开门:先清旧失败与旧提示再开确认框。 */
|
|
18
|
+
openPreview: () => void;
|
|
19
|
+
/** 清完必重查;回执句子由 config 拼。 */
|
|
20
|
+
clean: () => Promise<void>;
|
|
21
|
+
/** 只收确认框,不碰提示 / 失败。 */
|
|
22
|
+
cancel: () => void;
|
|
23
|
+
}
|
|
24
|
+
/** `settle` 拼出的这一趟要摆的句子;**缺席的格子表示"不碰"**(重查刚摆上的状态保持原样)。 */
|
|
25
|
+
export interface MaintenanceSettlePlan {
|
|
26
|
+
readonly notice?: string | null;
|
|
27
|
+
readonly failure?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface MaintenanceSectionConfig<R, C> {
|
|
30
|
+
/** 手动检查(也是失败块的重试)。交回的已是落地面——glue 用 `callRemote` 包装、不会抛,module 不再包一层。 */
|
|
31
|
+
scan(): Promise<RemoteOutcome<R>>;
|
|
32
|
+
/** 清理写动作;入参是点击那一刻的报告(清理输入由各段自己从里面点名)。报告没活可干时
|
|
33
|
+
* 同步回 `null`:不发请求、只收确认框——"没活"的判据各段不同,是各段的业务。
|
|
34
|
+
* 同样交回落地面、不抛。`C` 是这一段的回执形状:状态机不认识它,只把它原样递给
|
|
35
|
+
* `beforeRescan` / `settle`,段那边就不必把 `outcome.value` 擦回 `unknown` 再 `as` 一次。 */
|
|
36
|
+
clean(report: R): Promise<RemoteOutcome<C>> | null;
|
|
37
|
+
/** 清理落地后、必发的重查起飞前先摆的读数;`null` = 不动。附件段用它把"已清空"先收成 0:
|
|
38
|
+
* 旧报告不再可信,别让旧横幅在重查飞着时冒充"没清"。 */
|
|
39
|
+
beforeRescan?(outcome: RemoteOutcome<C>): R | null;
|
|
40
|
+
/** 检查读不到时的上屏句;本地化前缀各段自己给。 */
|
|
41
|
+
scanFailureText(message: string): string;
|
|
42
|
+
/** 这一趟检查**赢了**(没被更晚的检查顶掉)之后顺手收掉的东西;输家不调用。它在赢家的读数
|
|
43
|
+
* 落地**之前**触发,故只许写自己那份不属于读数的状态。孤儿会话段用它清上一趟清理的附件回执
|
|
44
|
+
* ——「收掉旧回执」属于最新读数成立的一部分,放在发请求之前会让被顶掉的输家也清掉赢家的回执。 */
|
|
45
|
+
onScanWon?: () => void;
|
|
46
|
+
/** 清理回执(含顶层失败)→ 这一趟的句子。重查赢了才调用;段独有的第二回执(如孤儿会话段顺带
|
|
47
|
+
* 清掉的附件读数)由它顺手写自己的状态——那不是这台状态机的格子。
|
|
48
|
+
* `report` 是**这一趟**点击那一刻的报告:回执要按请求名单逐条判读,名单只能随这一趟交下来;
|
|
49
|
+
* 段自己存一份"最近一次清理的名单"会在两趟交错时把上一趟的回执按后一趟的名单判读。 */
|
|
50
|
+
settle(outcome: RemoteOutcome<C>, report: R): MaintenanceSettlePlan;
|
|
51
|
+
}
|
|
52
|
+
export declare function useMaintenanceSection<R, C>(config: MaintenanceSectionConfig<R, C>): MaintenanceSectionView<R>;
|
|
@@ -1,43 +1,15 @@
|
|
|
1
1
|
import type { Translate } from './copy.js';
|
|
2
2
|
import { type AttachmentOrphanReportWire, type OrphanReportWire, type SessionDeleteRemote } from './remote.js';
|
|
3
|
+
import { type MaintenanceSectionView } from './use-maintenance-section.js';
|
|
3
4
|
/** 维护卡只需要这四段:两份报告 + 孤儿会话的批量写 + 孤儿附件的聚合写。 */
|
|
4
5
|
export type MaintenanceDeps = Pick<SessionDeleteRemote, 'scanOrphans' | 'applyOutcomes' | 'scanAttachmentOrphans' | 'cleanAttachmentOrphans'>;
|
|
5
|
-
/** 一段在飞的动作:检查只读,清理会写。状态行靠它分开说「检查中…」与「清理中…」;`null` = 空闲。 */
|
|
6
|
-
export type MaintenancePhase = 'scanning' | 'cleaning';
|
|
7
6
|
export interface Maintenance {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
readonly orphanAttachmentNotice: string | null;
|
|
15
|
-
readonly orphanConfirm: boolean;
|
|
16
|
-
readonly orphanBusy: boolean;
|
|
17
|
-
/** 在飞的是检查还是清理;`null` = 空闲。写动作在飞时状态行据此说「清理中…」。 */
|
|
18
|
-
readonly orphanPhase: MaintenancePhase | null;
|
|
19
|
-
/** 手动检查(`GET /orphans`)。失败回 `null` + 原文,不折成零条。 */
|
|
20
|
-
scanOrphans(): Promise<void>;
|
|
21
|
-
/** 「查看并清理」:先收掉旧提示再开确认框。 */
|
|
22
|
-
openOrphanPreview(): void;
|
|
23
|
-
/** `/apply` 的 `orphan-clean`:清完必重查;中性 / 失败 / 部分计数按回执说。 */
|
|
24
|
-
cleanOrphans(): Promise<void>;
|
|
25
|
-
setOrphanConfirm(open: boolean): void;
|
|
26
|
-
readonly attachmentReport: AttachmentOrphanReportWire | null;
|
|
27
|
-
readonly attachmentFailure: string | null;
|
|
28
|
-
/** 上一趟清理的回执(已清 / 跳过 / 逐文件失败),重查读不出来。 */
|
|
29
|
-
readonly attachmentNotice: string | null;
|
|
30
|
-
readonly attachmentConfirm: boolean;
|
|
31
|
-
readonly attachmentBusy: boolean;
|
|
32
|
-
/** 在飞的是检查还是清理;`null` = 空闲。 */
|
|
33
|
-
readonly attachmentPhase: MaintenancePhase | null;
|
|
34
|
-
/** 手动检查(`GET /attachment-orphans`)。 */
|
|
35
|
-
scanAttachmentOrphans(): Promise<void>;
|
|
36
|
-
/** 「查看并清理」:先收掉旧提示再开确认框。 */
|
|
37
|
-
openAttachmentPreview(): void;
|
|
38
|
-
/** `POST /attachment-clean`:清完必重查;`removed` / `failed` / `skipped` 按回执说。 */
|
|
39
|
-
cleanAttachmentOrphans(): Promise<void>;
|
|
40
|
-
setAttachmentConfirm(open: boolean): void;
|
|
7
|
+
/** 孤儿会话段 + 它独有的第二回执:这一趟清理顺带清掉的附件读数(`.dsh` 内独占图片副本)。
|
|
8
|
+
* 重查读不出来,和段的 `notice` 一样是过去那一趟的回执;下一次检查 / 清理会被收掉。 */
|
|
9
|
+
readonly orphans: MaintenanceSectionView<OrphanReportWire> & {
|
|
10
|
+
readonly attachmentNotice: string | null;
|
|
11
|
+
};
|
|
12
|
+
readonly attachments: MaintenanceSectionView<AttachmentOrphanReportWire>;
|
|
41
13
|
}
|
|
42
14
|
export declare function useMaintenance({ deps, t }: {
|
|
43
15
|
deps: MaintenanceDeps;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BusyGate } from './busy-gate.js';
|
|
2
2
|
import { type SessionDeleteRemote, type TrashEntryLike } from './remote.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { TrashEntryWire } from '../shared/wire.js';
|
|
4
|
+
import type { DetailNav, TrashDetailState } from './trash-detail.js';
|
|
4
5
|
import { type TrashQuery } from './use-trash-query.js';
|
|
5
6
|
/** 待确认的动作:一条记录、整个回收站、或多选挑出来的一批(`ids` 已按清单顺序排好)。
|
|
6
7
|
* 类型归状态机,视图只 import 它来渲染。 */
|
|
@@ -17,15 +18,16 @@ export type PendingAction = {
|
|
|
17
18
|
ids: readonly string[];
|
|
18
19
|
};
|
|
19
20
|
/**
|
|
20
|
-
* 控制器要的面。`query`
|
|
21
|
-
*
|
|
21
|
+
* 控制器要的面。`query` 取四样:清单(选中从它里面找)、`reload`(挂载、显式重试、失效与写后
|
|
22
|
+
* 都是同一条读)、清空(面板的写动作那一半)、`noteAttachmentCleanup`(purge 回执里
|
|
22
23
|
* 附件那半句交给查询侧拼句子上屏)。`ordered` 是键盘/自动选中用的"屏幕上的顺序"(只含展开的组),
|
|
23
|
-
*
|
|
24
|
+
* 由面板算好传进来——布局常量不搬进控制器。输入的引用**不需稳定**:失效 effect 的判据只有
|
|
25
|
+
* `revision`,reload 回调经 `useLatest` 读最新那一份(判据住 `store.ts`)。
|
|
24
26
|
*/
|
|
25
27
|
export interface TrashPanelControllerInput {
|
|
26
28
|
deps: Pick<SessionDeleteRemote, 'detail' | 'applyOutcomes'>;
|
|
27
29
|
gate: BusyGate;
|
|
28
|
-
query: Pick<TrashQuery, 'list' | '
|
|
30
|
+
query: Pick<TrashQuery, 'list' | 'reload' | 'emptyTrash' | 'noteAttachmentCleanup'>;
|
|
29
31
|
ordered: readonly TrashEntryLike[];
|
|
30
32
|
canAutoSelect: boolean;
|
|
31
33
|
}
|
|
@@ -33,27 +35,46 @@ export interface TrashPanelController {
|
|
|
33
35
|
readonly busy: boolean;
|
|
34
36
|
readonly selected: TrashEntryLike | null;
|
|
35
37
|
readonly selectedId: string | null;
|
|
38
|
+
/** 正在看哪个孩子(离清单的详情导航态;null = 看清单选中那条)。单级是设计(ADR-0029):
|
|
39
|
+
* 只有一个槽,`openChild` 换槽不叠栈,返回交还清单选中那条。 */
|
|
40
|
+
readonly childView: TrashEntryWire | null;
|
|
41
|
+
/** 详情的导航面(开孩子 / 回父)。ADR-0029 单级判据的**唯一实现**:面板只转交它,
|
|
42
|
+
* 不再自己拼 openChild / parentContext 两个派生块(判据有第二个家,视图与测试就得各自复述语义)。 */
|
|
43
|
+
readonly detailNav: DetailNav;
|
|
44
|
+
/** 打开一个孩子(父详情子会话段点进来):读它的详情,但清单选中不动。 */
|
|
45
|
+
openChild(child: TrashEntryWire): Promise<void>;
|
|
46
|
+
/** 回到父详情(宽档头部那颗「返回父会话」;窄档 back/Esc 的第一程也走它)。 */
|
|
47
|
+
closeChild(): void;
|
|
36
48
|
readonly drilled: boolean;
|
|
37
49
|
readonly detail: TrashDetailState;
|
|
50
|
+
/** 更早一页正在读(只对当前详情主体有意义:孩子视图开着就是孩子)。 */
|
|
51
|
+
readonly loadingOlder: boolean;
|
|
52
|
+
/** 更早一页读失败的原文(只对当前详情主体有意义;正文本身仍是 ready)。 */
|
|
53
|
+
readonly olderFailure: string | null;
|
|
38
54
|
/** 待确认的那一批(确认框的开合);确认落地后由控制器收起。 */
|
|
39
55
|
readonly pending: PendingAction | null;
|
|
40
56
|
/** 恢复确认框要摆的这一批(按勾选顺序;清单里已找不到的 id 滤掉)。 */
|
|
41
57
|
readonly pendingEntries: readonly TrashEntryLike[];
|
|
42
58
|
/** 恢复/彻底删除/批量 的失败原文,按条目 id 存:切走再回来还在。 */
|
|
43
59
|
readonly actionFailure: Readonly<Record<string, string>>;
|
|
44
|
-
|
|
60
|
+
/** 下面是面板会**摘下来转交**的动作(导航的 `select`/`back`、确认框的 `cancelPending`):
|
|
61
|
+
* 属性声明而不是方法简写,实现体都不读 `this`。方法简写的隐式 `this` 会让 `unbound-method`
|
|
62
|
+
* 把转交报成可疑;改形状是纯类型面,零运行时改动。 */
|
|
63
|
+
select: (entry: TrashEntryLike, options?: {
|
|
45
64
|
drill?: boolean;
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
}) => Promise<void>;
|
|
66
|
+
/** 按当前详情的 `olderCursor` 读更早一页,拼到现有块前面。 */
|
|
67
|
+
loadOlder: () => Promise<void>;
|
|
68
|
+
retry: () => void;
|
|
69
|
+
back: () => void;
|
|
49
70
|
/** 构造待确认动作(唯一入口:新增一种确认只改 `PendingAction` 与确认框,不再加一串转调)。 */
|
|
50
|
-
request(action: PendingAction)
|
|
51
|
-
cancelPending()
|
|
71
|
+
request: (action: PendingAction) => void;
|
|
72
|
+
cancelPending: () => void;
|
|
52
73
|
/** 单条恢复(`/apply` 的 N=1):成功后丢缓存,写完无论成败都重读清单,失败原文落回这一行。 */
|
|
53
|
-
restore(entry: TrashEntryLike)
|
|
74
|
+
restore: (entry: TrashEntryLike) => Promise<void>;
|
|
54
75
|
/** 确认框上的「确认」:按 `pending.kind` 分派到空清或 `confirmApply`。 */
|
|
55
|
-
confirmPurge()
|
|
76
|
+
confirmPurge: () => Promise<void>;
|
|
56
77
|
/** 一组 id 上的同一个动作:逐条成败落成行上原文/缓存失效,整批只重读一次清单。 */
|
|
57
|
-
confirmApply(action: 'purge' | 'restore', ids: readonly string[])
|
|
78
|
+
confirmApply: (action: 'purge' | 'restore', ids: readonly string[]) => Promise<void>;
|
|
58
79
|
}
|
|
59
80
|
export declare function useTrashPanelController(input: TrashPanelControllerInput): TrashPanelController;
|
|
@@ -29,14 +29,16 @@ export interface TrashQuery {
|
|
|
29
29
|
* 读数冒充这一趟;删除与恢复不碰它。它是**过去那一趟的回执**、不是清单状态:清单重读读不出来。 */
|
|
30
30
|
readonly attachmentNotice: string | null;
|
|
31
31
|
/** 把一份附件读数摆上屏(`undefined` = 这一趟没有读数,旧提示收掉);句子怎么拼归这里,调用方只管转交。 */
|
|
32
|
-
noteAttachmentCleanup(a: AttachmentCleanupWire | undefined)
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
|
|
32
|
+
noteAttachmentCleanup: (a: AttachmentCleanupWire | undefined) => void;
|
|
33
|
+
/** 重读清单:挂载、显式「重试」、写后与失效通告都走这一条读——问的是同一件事("清单现在长什么样")。
|
|
34
|
+
* 曾经分作 `refreshAll` / `refreshAfterWrite` 两个名字,那是调用点问的两件事("用户要看现在的"与
|
|
35
|
+
* "别处动过了,跟上"),不是两条路径,合一为 `reload`。面板没有第二条扫描:孤儿检查是设置页
|
|
36
|
+
* 维护卡的手动动作(ADR-0021)。清单读失败时旧行进 `unknown`。 */
|
|
37
|
+
reload: () => Promise<void>;
|
|
38
|
+
/** 清空回收站(聚合回执):成功就熄掉撤销横幅并重读,失败就把清单置为未知并摆上原文。
|
|
39
|
+
* 三种形状各有意思:`true` = 清空落地了;`false` = 这一趟跑了但没清成(原文已在清单那块上屏);
|
|
40
|
+
* `undefined` = 闸把这一趟挡回来了、什么都没跑(调用方不收框,等在飞那一趟的结论)。 */
|
|
41
|
+
emptyTrash: () => Promise<boolean | undefined>;
|
|
40
42
|
}
|
|
41
43
|
export declare function useTrashQuery({ deps, t, gate, }: {
|
|
42
44
|
deps: TrashQueryDeps;
|
|
@@ -9,22 +9,25 @@ export interface TrashSelection {
|
|
|
9
9
|
/** 键盘 ↑↓ 与下钻走这个顺序:**只含展开的组**(折叠的行不在 DOM 里,方向键别停过去)。 */
|
|
10
10
|
readonly ordered: readonly TrashEntryLike[];
|
|
11
11
|
readonly allCollapsed: boolean;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/** 方法一律写成**属性声明**:面板会把它们摘下来传给导航/行组件,实现体也都不读 `this`。
|
|
13
|
+
* 方法简写带来的隐式 `this: TrashSelection` 会让 `unbound-method` 误报,且会诱导调用点
|
|
14
|
+
* 为了消警报去包箭头函数——那会让 `TrashSelection` 每次渲染换新身份(见 ADR 外的回归教训)。 */
|
|
15
|
+
isCollapsed: (projectDir: string) => boolean;
|
|
16
|
+
enterSelect: () => void;
|
|
17
|
+
exitSelect: () => void;
|
|
18
|
+
toggleGroup: (projectDir: string) => void;
|
|
19
|
+
toggleAllGroups: () => void;
|
|
20
|
+
toggleCheck: (entryId: string) => void;
|
|
18
21
|
/** 组头那颗:说的是"这个项目的全部会话",折叠起来的条目也算。勾满再点 = 整组取消。 */
|
|
19
|
-
toggleGroupCheck(group: {
|
|
22
|
+
toggleGroupCheck: (group: {
|
|
20
23
|
entries: readonly TrashEntryLike[];
|
|
21
|
-
})
|
|
22
|
-
groupFullyChecked(group: {
|
|
24
|
+
}) => void;
|
|
25
|
+
groupFullyChecked: (group: {
|
|
23
26
|
entries: readonly TrashEntryLike[];
|
|
24
|
-
})
|
|
25
|
-
groupPartiallyChecked(group: {
|
|
27
|
+
}) => boolean;
|
|
28
|
+
groupPartiallyChecked: (group: {
|
|
26
29
|
entries: readonly TrashEntryLike[];
|
|
27
|
-
})
|
|
30
|
+
}) => boolean;
|
|
28
31
|
}
|
|
29
32
|
export declare function useTrashSelection({ entries, groups, }: {
|
|
30
33
|
entries: readonly TrashEntryLike[] | null;
|
|
@@ -4,41 +4,57 @@
|
|
|
4
4
|
* 收尾顺序住 `write-settlement.ts`(ADR-0013),面板的重读策略住 `use-trash-query.ts`;
|
|
5
5
|
* 座位只拥有「这次是谁在写、交哪些材质、闸在哪」。
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* 边界:不自己判读回执(verdict 判读住 `receipt.ts`,失败表怎么上屏归各写入面——撤销那一趟的
|
|
8
|
+
* 横幅改写住 `restoreFromUndo`)、不决定收尾顺序、不拥有各面的本地状态;面板座位的闸由面板传入,
|
|
9
|
+
* 不另造第二枚;闸挡回来的那一趟**什么都不落地**——座位交回 `undefined`,不伪造 outcome,
|
|
10
|
+
* 收尾留给在飞的那一趟。
|
|
8
11
|
* 无主子会话清理**不是**座位:它只在插件设置页的维护卡里手动触发(ADR-0021),
|
|
9
12
|
* 读数、闸与重查都归 `use-maintenance.ts`。
|
|
10
13
|
*/
|
|
14
|
+
import type { Translate } from './copy.js';
|
|
11
15
|
import type { BusyGate } from './busy-gate.js';
|
|
12
16
|
import type { OneShotDeps, TrashEntryLike } from './remote.js';
|
|
17
|
+
import { type UndoNotice } from './undo-notices.js';
|
|
13
18
|
import { type WriteOutcome } from './write.js';
|
|
14
|
-
/** 会话行菜单的单删座位:一条会话、`single` 读法、成功发单条撤销横幅。交回这条会话的失败原文。
|
|
19
|
+
/** 会话行菜单的单删座位:一条会话、`single` 读法、成功发单条撤销横幅。交回这条会话的失败原文。
|
|
20
|
+
* 三种形状各有意思,不许互相冒充:字符串 = 这一条失败了(原文);`null` = 跑了、这一条没有失败要说;
|
|
21
|
+
* `undefined` = 闸把这一趟挡回来了、什么都没跑(框不收,等在飞那一趟的结论——先收就会把它的失败原文
|
|
22
|
+
* 落在已经卸载的确认框上)。 */
|
|
15
23
|
export declare function deleteSessionFromMenu(deps: OneShotDeps, input: {
|
|
16
24
|
sessionId: string;
|
|
17
25
|
title: string;
|
|
18
|
-
}, gate: BusyGate): Promise<string | null>;
|
|
26
|
+
}, gate: BusyGate): Promise<string | null | undefined>;
|
|
19
27
|
/** 批量删除座位(宿主会话列表的多选 → 批量确认框):一次 `/apply`,撤销名单只收 ok 支的条目 id(跟着组走的幂等条不单列)。
|
|
20
|
-
* 只收 id
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
* 只收 id:标题是浮条的呈现,不参与这一次写入。**被闸挡回时交回 `undefined`**——这一趟没跑,
|
|
29
|
+
* 浮条与确认框都不许照着它收缩名单或关框(那会吞掉在飞那一趟的结果)。 */
|
|
30
|
+
export declare function deleteSessionsFromBatch(deps: OneShotDeps, ids: readonly string[], gate: BusyGate): Promise<ReadonlyMap<string, string> | undefined>;
|
|
31
|
+
/** 外部恢复座位:拥有整趟——ids 组装、runWrite、三种回执形状各改写一次横幅、成功收自己。
|
|
32
|
+
* 闸在调用方(横幅组件每条通知一枚);座位不传闸(双套会让外层 busy 提前落)。
|
|
33
|
+
* 单条与批量都走**一次** `applyOutcomes('restore', ids)`(`runWrite` 判读;单条带 `single: true`,
|
|
34
|
+
* null 是协议错);失败就把原因换到同一条横幅上,不静默。多条逐条失败时横幅要说清**条数与各条
|
|
35
|
+
* 原因**(原因去重)——只显第一条会把条数与其余原因折没(issue #8 第 5 条);单条失败仍原文上屏。
|
|
36
|
+
* settlement 负责重开删除时被关掉的会话(只认 main-view 自己记过的)并让清单重读;
|
|
37
|
+
* 座位**不传 `dismissUndo`**——部分失败的回滚会让 settlement 先熄灭横幅,随后 `failNotice` 因
|
|
38
|
+
* `ownsNotice` 落空而吞掉失败原文。 */
|
|
39
|
+
export declare function restoreFromUndo(deps: OneShotDeps, notice: UndoNotice, t: Translate): Promise<void>;
|
|
27
40
|
/** 面板两个 `/apply` 座位共用的那一半:依赖、条目 id、闸、重读与缓存失效。 */
|
|
28
41
|
interface PanelWriteInput<A extends 'restore' | 'purge'> {
|
|
29
42
|
deps: OneShotDeps;
|
|
30
43
|
ids: readonly string[];
|
|
31
44
|
gate: BusyGate;
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
/** 属性声明:这两枚回调被摘下来交给 `runWrite`(转交而不是当方法调),实现不读 `this`。 */
|
|
46
|
+
refreshAfterWrite: (outcome: WriteOutcome<A>) => Promise<void>;
|
|
47
|
+
clearDetailCache: (ids: readonly string[]) => void;
|
|
34
48
|
}
|
|
35
49
|
/** 面板恢复另要两样:单条读法,以及 already-gone 回执缺行时反查 sessionId 的当前清单。 */
|
|
36
50
|
interface PanelRestoreInput extends PanelWriteInput<'restore'> {
|
|
37
51
|
single?: boolean;
|
|
38
52
|
entries: readonly TrashEntryLike[] | null;
|
|
39
53
|
}
|
|
40
|
-
/** 面板恢复座位:面板已替这条完成「回来」的结算 ⇒ `panelSeat`;撤销横幅与详情缓存都按落地条目收。
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
/** 面板恢复座位:面板已替这条完成「回来」的结算 ⇒ `panelSeat`;撤销横幅与详情缓存都按落地条目收。
|
|
55
|
+
* 闸被面板那一趟占着时交回 `undefined`(连点不发第二条),面板照它什么都不改。 */
|
|
56
|
+
export declare function restoreFromPanel(input: PanelRestoreInput): Promise<WriteOutcome<'restore'> | undefined>;
|
|
57
|
+
/** 面板彻底删除座位:永久删除也让指向这些条目的撤销横幅熄灭(settlement 的 purge 支负责)。
|
|
58
|
+
* 与恢复座位同一条:闸被占着时交回 `undefined`,这一趟不改任何界面后果。 */
|
|
59
|
+
export declare function purgeFromPanel(input: PanelWriteInput<'purge'>): Promise<WriteOutcome<'purge'> | undefined>;
|
|
44
60
|
export {};
|
|
@@ -37,7 +37,7 @@ export interface SettledFacts<A extends ApplyAction> {
|
|
|
37
37
|
*/
|
|
38
38
|
export type WritePresentationByAction = {
|
|
39
39
|
delete: {
|
|
40
|
-
/**
|
|
40
|
+
/** 外部写入座位:让回收站面板重读。面板写不传(自己的清单重读 `reload` 不走这条通告)。 */
|
|
41
41
|
invalidate?(): void;
|
|
42
42
|
/** 撤销发布:settlement 从回执摘要取出落地条目 id 与请求名单、组好形之后交来;名单**非空**——
|
|
43
43
|
* 全被同批前一条带走时没有落点,settlement 不发布没有可撤销动作的横幅。座位只决定发布哪一形与那一形带的标题。 */
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* - `single: true` 时把 N=1 模式交给 `receipt.ts` 的读法;单条 null 在那一处折成 failed,
|
|
15
15
|
* 这里不再重复守卫。
|
|
16
16
|
* - **闸包住整趟**:给了 `gate` 就把 apply → settle → refresh 一起包进去——refresh 落在闸外时,
|
|
17
|
-
* 旧清单还在屏上,同一个动作能被再点一次;调用方不再自己 `around
|
|
17
|
+
* 旧清单还在屏上,同一个动作能被再点一次;调用方不再自己 `around`。同一枚闸已有任务在飞时
|
|
18
|
+
* 这一趟**不跑**(连点不发第二条),交回 `undefined`:座位照它收「这一趟没跑」,不伪造 outcome。
|
|
18
19
|
*/
|
|
19
20
|
import type { BusyGate } from './busy-gate.js';
|
|
20
21
|
import { type OneShotDeps } from './remote.js';
|
|
@@ -40,7 +41,7 @@ export interface WriteInput<A extends ApplyAction> {
|
|
|
40
41
|
readonly action: A;
|
|
41
42
|
readonly ids: readonly string[];
|
|
42
43
|
/** 单条写入面:把 N=1 模式交给 receipt 读法(null 折成协议错)。 */
|
|
43
|
-
readonly single?: boolean;
|
|
44
|
+
readonly single?: boolean | undefined;
|
|
44
45
|
/** restore 的 already-gone 回执不带行,它的 sessionId 只能由调用方从当前清单里查。 */
|
|
45
46
|
readonly sessionIdOfEntry?: (entryId: string) => string | undefined;
|
|
46
47
|
/** 信封成功时交给 settlement 的呈现材质(摘要里的成功名单可能为空);按 action 的收尾顺序住 `write-settlement.ts`。 */
|
|
@@ -51,4 +52,13 @@ export interface WriteInput<A extends ApplyAction> {
|
|
|
51
52
|
* 是否真的重读由调用方决定。 */
|
|
52
53
|
readonly refresh?: (outcome: WriteOutcome<A>) => void | Promise<void>;
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
/** 签名上的分岔就是判据:**没传闸 ⇒ 这一趟一定跑**,回真结果;**传了闸 ⇒ 可能被同一枚闸的在飞那一趟
|
|
56
|
+
* 挡回**,那时这一趟什么都没做、回 `undefined`。`runWrite` 不替调用方编造成果——挡回来的那趟怎么收尾
|
|
57
|
+
* 归座位与界面(判据住 `busy-gate.ts`)。第二支把 `gate` 钉成 `undefined`:`BusyGate | undefined`
|
|
58
|
+
* 这类"可能真有闸"的输入不许落到「一定跑完」那一支,否则类型会把挡回漏成 `WriteOutcome`。 */
|
|
59
|
+
export declare function runWrite<A extends ApplyAction>(input: WriteInput<A> & {
|
|
60
|
+
readonly gate: BusyGate;
|
|
61
|
+
}): Promise<WriteOutcome<A> | undefined>;
|
|
62
|
+
export declare function runWrite<A extends ApplyAction>(input: WriteInput<A> & {
|
|
63
|
+
readonly gate?: undefined;
|
|
64
|
+
}): Promise<WriteOutcome<A>>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话闭包(session closure)的唯一实现:`session-` 前缀归一 + 传递闭包 + 首见去重。
|
|
3
|
+
*
|
|
4
|
+
* 为什么是零依赖纯部件:这三件事在仓里曾有两份闭包实现(端口的分层 BFS、无主子会话的整表 BFS)
|
|
5
|
+
* 与三处手写去重,同一口径靠注释担保——注释不会替代码挡漂移,所以把口径收进这里,一份实现;
|
|
6
|
+
* 端口与动词只做接线(逐层问宿主、SessionIdentity 映射留在各自那层,见各自的头注)。
|
|
7
|
+
* 不碰盘、不碰端口类型:本文件一个 import 都不许有。
|
|
8
|
+
*/
|
|
9
|
+
/** 闭包里的一个节点:本部件只认这两个字段;端口的后代记录与语料的 SessionIdentity 都满足它。 */
|
|
10
|
+
export interface ClosureNode {
|
|
11
|
+
readonly sessionId: string;
|
|
12
|
+
readonly projectDir: string;
|
|
13
|
+
}
|
|
14
|
+
/** 会话 id 去掉 `session-` 前缀后的形状:同一个会话在两代宿主里写法不同(v3 代 id 不带前缀、
|
|
15
|
+
* 现行代带),归堆、成组、去重一律按这个形状比——两边都归一,不赌调用方传的是哪一代写法。 */
|
|
16
|
+
export declare function bareSessionId(id: string): string;
|
|
17
|
+
/** 一个 id 的全部可能写法,原写法在前:`session-x` → `[session-x, x]`;`x` → `[x, session-x]`。
|
|
18
|
+
* 逐层问宿主时按这些写法问:宿主那支按 `header.parentSession` 原文比,不替我们归一。 */
|
|
19
|
+
export declare function sessionIdShapes(id: string): string[];
|
|
20
|
+
/** parent(bare) 到 children 索引上的传递闭包:深度不设上限、防环、首见去重(都按 bare 归一后比)。
|
|
21
|
+
* 根的两种写法都认(内部归一次);结果不含根自己。数据里真出现环时代价是「少数一条」,
|
|
22
|
+
* 不是把清单读挂死——环本来不该出现(parentSession 是一条有向边),出现就是手工改过日志或宿主 bug。 */
|
|
23
|
+
export declare function closureOf<N extends ClosureNode>(children: ReadonlyMap<string, readonly N[]>, root: string): N[];
|
|
24
|
+
/** 同一条记录两代写法只认第一次出现(保原序)。全局去重只有这一处实现:谁要按 bare 去重,
|
|
25
|
+
* 交一条列表进来,别在调用点再手写一个 seen 集合。 */
|
|
26
|
+
export declare function firstByBare<N extends ClosureNode>(members: readonly N[]): N[];
|
|
27
|
+
/**
|
|
28
|
+
* 整表语料 → 根闭包组:`parentSessionId` 不在 `known` 里的那些 subagent 各自成一组。`known` 两侧都
|
|
29
|
+
* 归一后再比(传原始 id 即可)。「已知父为什么排除在外」是调用方的领域判据(住
|
|
30
|
+
* `src/verbs/orphan-subagents.ts` 与 `CONTEXT.md` 的「无主子会话」),这一层不替它复述。
|
|
31
|
+
*
|
|
32
|
+
* 组内的后代仍走 `closureOf` 那一份闭包,只是多一条跨组的 `claimed`:一条记录只归第一个认领它的根。
|
|
33
|
+
* 已被前一组收走的那条,连同它整支子树都不再出现在这一组里——后代在那一组已被全部认领,所以
|
|
34
|
+
* 「丢掉已认领的」与「不走进已认领的那支」交回的是同一个集合。环上的代价是「少数一条」,
|
|
35
|
+
* 不是把一次扫描挂死。组序与成员序都按语料原序。
|
|
36
|
+
*/
|
|
37
|
+
export declare function orphanRootsOf<N extends ClosureNode & {
|
|
38
|
+
readonly parentSessionId?: string;
|
|
39
|
+
readonly subagent: boolean;
|
|
40
|
+
}>(corpus: readonly N[], known: Iterable<string>): {
|
|
41
|
+
root: N;
|
|
42
|
+
parentSessionId: string;
|
|
43
|
+
members: N[];
|
|
44
|
+
}[];
|
|
@@ -20,9 +20,8 @@
|
|
|
20
20
|
* 的 zstd 版本(明文一大、FCS 上到 4 字节的 `0xa0` 就会算错),帧就切歪,会话日志直接读不出来。
|
|
21
21
|
*
|
|
22
22
|
* **尾巴那 4 字节按实测判据收,不按 descriptor 的 bit2**:规则是「跳过去必须正好撞上下一帧的 magic
|
|
23
|
-
* 或到 EOF
|
|
24
|
-
*
|
|
25
|
-
* 用例覆盖这一条)。
|
|
23
|
+
* 或到 EOF」——猜错不会静默切歪:末尾的自证与块界护栏会抛(护栏是必需的:块头可能被文件截断,
|
|
24
|
+
* 不抛的话块循环每圈只 +3 字节却永不 break,整个进程会卡死;用例覆盖这一条)。
|
|
26
25
|
*/
|
|
27
26
|
export interface FrameRange {
|
|
28
27
|
start: number;
|
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
export interface FsPort {
|
|
2
|
-
|
|
2
|
+
/** 全部成员写成属性声明:端口方法会被**解构出来单独调**(`const { sizeOfDir } = port`,见下方
|
|
3
|
+
* 模块级递归的注释),实现体一律不读 `this`。方法简写的隐式 `this: FsPort` 会让
|
|
4
|
+
* `unbound-method` 把这种正常用法报成可疑;契约(返回的 Promise 形状)一字未动。 */
|
|
5
|
+
exists: (p: string) => Promise<boolean>;
|
|
3
6
|
/** 建目录(含中间层)。首删时 `~/.dsh/session-trash/` 还不存在,而 `rename` 不建父目录。 */
|
|
4
|
-
ensureDir(p: string)
|
|
7
|
+
ensureDir: (p: string) => Promise<void>;
|
|
5
8
|
/** 搬目录:**目标已存在一律失败**,跨卷(EXDEV)也失败并说清楚——不替换、也不偷偷改成复制。
|
|
6
9
|
* Linux 的 `rename` 会静默替代空目录而 Windows 不会,这个差别不能交给操作系统决定。 */
|
|
7
|
-
rename(from: string, to: string)
|
|
8
|
-
removeDir(p: string)
|
|
9
|
-
sizeOfDir(p: string)
|
|
10
|
+
rename: (from: string, to: string) => Promise<void>;
|
|
11
|
+
removeDir: (p: string) => Promise<void>;
|
|
12
|
+
sizeOfDir: (p: string) => Promise<number>;
|
|
10
13
|
/** 目录下的条目名(**不递归**)。回收站详情要用它在那条目录里挑 `*.jsonl.zstd`。 */
|
|
11
|
-
listFiles(p: string)
|
|
14
|
+
listFiles: (p: string) => Promise<string[]>;
|
|
12
15
|
/** 目录下的**子目录**名(不递归;**跟随软链**)。回收站对账用它找出"清单里还没有行"的条目目录——
|
|
13
16
|
* 与 `listFiles` 分开是为了让"拒收文件"发生在挑选阶段(回收站根里落进来的杂散文件当外人)。 */
|
|
14
|
-
listDirs(p: string)
|
|
17
|
+
listDirs: (p: string) => Promise<string[]>;
|
|
15
18
|
/** 解析符号链接后的真实路径。递归走目录时防环用:跟随软链之后,目录图不再是树。 */
|
|
16
|
-
realpath(p: string)
|
|
19
|
+
realpath: (p: string) => Promise<string>;
|
|
17
20
|
/** 整个文件读进内存。实测本机回收站里最大单文件 4.66 MB;**内存口径由逐帧解压把住**(见 `src/log/session-log.ts`),不在这里。 */
|
|
18
|
-
readFile(p: string)
|
|
21
|
+
readFile: (p: string) => Promise<Buffer>;
|
|
19
22
|
/** 只读的单文件读数:附件渲染与独占计划都用它(不递归、不跟随目录)。 */
|
|
20
|
-
statFile(p: string)
|
|
23
|
+
statFile: (p: string) => Promise<{
|
|
21
24
|
size: number;
|
|
22
25
|
}>;
|
|
23
26
|
/** 删单个文件(不是目录)。危险能力,只挂到 `RemovalDeps` 上。 */
|
|
24
|
-
removeFile(p: string)
|
|
27
|
+
removeFile: (p: string) => Promise<void>;
|
|
25
28
|
}
|
|
26
|
-
/** 动词的基础面:读与搬移都有,**不拿删除**。删目录与删单文件只挂在 `RemovalDeps` 上(ADR-
|
|
29
|
+
/** 动词的基础面:读与搬移都有,**不拿删除**。删目录与删单文件只挂在 `RemovalDeps` 上(ADR-0027)。 */
|
|
27
30
|
export type CoreFsPort = Omit<FsPort, 'removeDir' | 'removeFile'>;
|
|
28
31
|
export declare function createFsPort(): FsPort;
|
|
@@ -19,9 +19,9 @@ export interface HostIdentityPort {
|
|
|
19
19
|
unarchive(sessionId: string): Promise<void>;
|
|
20
20
|
detach(sessionId: string, workspaceId: string): Promise<void>;
|
|
21
21
|
attach(sessionId: string, workspaceId: string): Promise<void>;
|
|
22
|
-
/** 归档是 registry **全局**的,与工作区账目无关 ⇒ `
|
|
23
|
-
*
|
|
24
|
-
archive(sessionId: string
|
|
22
|
+
/** 归档是 registry **全局**的,与工作区账目无关 ⇒ 只收 `sessionId`,没有工作区入参
|
|
23
|
+
* (实现从不读工作区;未分组的会话同样只有这一条归档路)。 */
|
|
24
|
+
archive(sessionId: string): Promise<void>;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* 角色二:**在场通告**——摘掉宿主内存里的活体,并如实通告客户端"这个会话没了 / 回来了"。
|
|
@@ -82,6 +82,7 @@ export interface SessionIdentity {
|
|
|
82
82
|
readonly projectDir: string;
|
|
83
83
|
readonly subagent: boolean;
|
|
84
84
|
}
|
|
85
|
+
/** 闭包里的一条后代:`sessionId` 是宿主交回的原写法(身份只认它,归一比对交给闭包部件)。 */
|
|
85
86
|
export interface SubagentMember {
|
|
86
87
|
readonly sessionId: string;
|
|
87
88
|
readonly projectDir: string;
|
|
@@ -95,8 +96,6 @@ export declare const NO_CWD_PROJECT_DIR = "_no-cwd";
|
|
|
95
96
|
* → `--D-develop-GitHub-dsh-session-delete--`。`projectDirOf` 与回收站对账重建共用这一份;
|
|
96
97
|
* 宿主换代先复核算法。 */
|
|
97
98
|
export declare function projectKey(cwd: string): string;
|
|
98
|
-
/** 会话 id 去掉 `session-` 前缀后的形状(同一个父在两代里写法不同,见 {@link HostCorpusPort.subagentMembersOf})。 */
|
|
99
|
-
export declare function bareSessionId(id: string): string;
|
|
100
99
|
/** 这次失败是不是"账本里没那个工作区"(而不是另有故障)。恢复侧靠它把两种失败分开:
|
|
101
100
|
* 前者按「未分组」收,后者照旧回滚报 `io`——不给真故障静默改归属的机会。 */
|
|
102
101
|
export declare function isWorkspaceMissingError(error: unknown): boolean;
|
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 生产实现住 `src/verbs/member-presence.ts`;组合根装配一次,挂在 `CoreDeps.presence`。这样
|
|
5
5
|
* “谁可以摘活体、谁可以发通告”由依赖面回答,而不是靠源码扫描维持。
|
|
6
|
+
*
|
|
7
|
+
* 能力面另带一枚**不导出的品牌字段**:形状对了但没盖章的对象满足不了 `PresenceCapability`
|
|
8
|
+
* (`tests/deps.test.ts` 钉住)。品牌是类型层的(`declare` 出来的 unique symbol 没有运行时值),
|
|
9
|
+
* 所以仓内不存在任何能"顺手带上这格"的出口——唯一的盖章处是 `createMemberPresence` 里那一处显式断言。
|
|
6
10
|
*/
|
|
11
|
+
declare const PRESENCE_BRAND: unique symbol;
|
|
7
12
|
export interface MemberBackFacts {
|
|
8
13
|
readonly diskBack: boolean;
|
|
9
14
|
readonly workspaceBack: boolean;
|
|
10
15
|
}
|
|
11
16
|
export interface PresenceCapability {
|
|
17
|
+
/** 品牌字段:值不承载语义,只为让结构相似的手拼对象满足不了这一面——键名是不导出的 unique symbol,
|
|
18
|
+
* 别的模块连键都写不出来,唯一的盖章处住 `src/verbs/member-presence.ts`。 */
|
|
19
|
+
readonly [PRESENCE_BRAND]: true;
|
|
12
20
|
/** 成员已经不在宿主可见列表:摘活体;三态不是 unknown 时发 removed。
|
|
13
21
|
* `backInTrash: false` 表示目录没退回回收站(恢复回滚失败),此时什么都不做;`facts` 缺席按
|
|
14
22
|
* 「正常退回」处理(`createMemberPresence` 的缺省),不再留第三种含义。 */
|
|
@@ -20,3 +28,4 @@ export interface PresenceCapability {
|
|
|
20
28
|
/** 批量恢复的「回来了」通告;单条就是长度为 1 的批。 */
|
|
21
29
|
announceRestored(ids: readonly string[]): Promise<void>;
|
|
22
30
|
}
|
|
31
|
+
export {};
|