@xrn1997/dsh-session-delete 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +141 -129
- package/cordis.patch.yml +3 -3
- package/lib/client.js +2811 -1264
- package/lib/index.js +2368 -1277
- package/lib/types/api/attachment-orphans.d.ts +11 -0
- package/lib/types/api/attachment.d.ts +23 -0
- package/lib/types/api/dispatch.d.ts +13 -29
- package/lib/types/api/request-trust.d.ts +45 -0
- package/lib/types/api/route-table.d.ts +52 -18
- package/lib/types/api/trash-rows.d.ts +3 -3
- package/lib/types/client/attachment-view.d.ts +27 -0
- package/lib/types/client/batch-bar.d.ts +1 -1
- package/lib/types/client/batch-confirm.d.ts +2 -2
- package/lib/types/client/batch-menu-item.d.ts +1 -1
- package/lib/types/client/batch-select.d.ts +1 -1
- package/lib/types/client/busy-gate.d.ts +6 -2
- package/lib/types/client/confirm-close.d.ts +14 -0
- package/lib/types/client/context.d.ts +3 -2
- package/lib/types/client/copy.d.ts +102 -42
- package/lib/types/client/delete-confirm.d.ts +1 -3
- 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/main-view.d.ts +1 -1
- package/lib/types/client/maintenance-parts.d.ts +25 -0
- package/lib/types/client/maintenance-settings.d.ts +22 -0
- package/lib/types/client/markdown.d.ts +9 -2
- package/lib/types/client/orphan-maintenance.d.ts +16 -13
- package/lib/types/client/receipt.d.ts +44 -2
- package/lib/types/client/remote.d.ts +38 -50
- package/lib/types/client/store.d.ts +14 -3
- package/lib/types/client/trash-confirm.d.ts +2 -15
- 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/trash-panel.d.ts +4 -3
- package/lib/types/client/ui/Checkbox.d.ts +1 -1
- package/lib/types/client/ui/Modal.d.ts +2 -2
- package/lib/types/client/ui/Toast.d.ts +3 -1
- package/lib/types/client/ui/icons.d.ts +7 -12
- package/lib/types/client/ui/top-layer.d.ts +10 -0
- package/lib/types/client/undo-notices.d.ts +52 -0
- package/lib/types/client/undo-toast.d.ts +6 -59
- package/lib/types/client/use-column-resize.d.ts +9 -20
- package/lib/types/client/use-maintenance-section.d.ts +52 -0
- package/lib/types/client/use-maintenance.d.ts +17 -0
- package/lib/types/client/use-panel-width.d.ts +1 -1
- package/lib/types/client/use-trash-panel-controller.d.ts +54 -16
- package/lib/types/client/use-trash-query.d.ts +39 -34
- package/lib/types/client/use-trash-selection.d.ts +15 -12
- package/lib/types/client/write-seats.d.ts +36 -28
- package/lib/types/client/write-settlement.d.ts +11 -17
- package/lib/types/client/write.d.ts +21 -8
- package/lib/types/deps.d.ts +18 -7
- package/lib/types/domain/closure.d.ts +44 -0
- package/lib/types/index.d.ts +6 -2
- package/lib/types/log/session-log.d.ts +4 -3
- package/lib/types/log/zstd-frames.d.ts +13 -8
- package/lib/types/ports/agent-handles.d.ts +1 -1
- package/lib/types/ports/connection-port.d.ts +33 -0
- package/lib/types/ports/fs-port.d.ts +24 -9
- package/lib/types/ports/host-port.d.ts +21 -50
- package/lib/types/ports/presence-port.d.ts +31 -0
- package/lib/types/shared/wire.d.ts +175 -38
- package/lib/types/trash/attachments.d.ts +92 -0
- package/lib/types/trash/detail.d.ts +46 -16
- package/lib/types/trash/entry.d.ts +10 -4
- package/lib/types/trash/groups.d.ts +9 -11
- package/lib/types/trash/manifest.d.ts +4 -4
- package/lib/types/trash/storage-manifest.d.ts +1 -1
- package/lib/types/verbs/batch-carried.d.ts +1 -3
- package/lib/types/verbs/delete-session.d.ts +2 -2
- package/lib/types/verbs/errors.d.ts +2 -0
- package/lib/types/verbs/live-admission.d.ts +4 -7
- package/lib/types/verbs/member-journal.d.ts +3 -4
- package/lib/types/verbs/member-presence.d.ts +16 -37
- package/lib/types/verbs/move-group.d.ts +2 -1
- package/lib/types/verbs/orphan-subagents.d.ts +19 -12
- package/lib/types/verbs/permanent-removal.d.ts +60 -25
- package/lib/types/verbs/restore-session.d.ts +7 -13
- package/lib/types/verbs/trash-scope.d.ts +36 -0
- package/package.json +117 -106
- package/lib/types/verbs/purge-trash.d.ts +0 -30
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 孤儿附件的报告段:`GET /attachment-orphans`。
|
|
3
|
+
*
|
|
4
|
+
* 报告把扫描计划折成数量/字节 + 对象清单(`id`/`bytes`,供设置页预览缩略图;最多
|
|
5
|
+
* `ORPHAN_PREVIEW_LIMIT` 条,清理不受预览条数限制)。扫描跳过(引用源或对象列表读不出)时
|
|
6
|
+
* **报告必须失败**——回一份 0 会让界面说"没有孤儿",而这类失败恰恰是"不知道",不是"没有"。
|
|
7
|
+
* 清理是 `POST /attachment-clean`,由路由直接接到 `cleanOrphanImageAttachments`。
|
|
8
|
+
*/
|
|
9
|
+
import { type AttachmentReadDeps } from '../trash/attachments.js';
|
|
10
|
+
import type { AttachmentOrphanReportWire } from '../shared/wire.js';
|
|
11
|
+
export declare function reportAttachmentOrphans(deps: AttachmentReadDeps): Promise<AttachmentOrphanReportWire>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 附件原始字节(`GET /attachment`,全仓唯一的二进制响应段)。
|
|
3
|
+
*
|
|
4
|
+
* 四条硬口径:
|
|
5
|
+
* ① **寻址只认 sha256 id**:`id` 必须 `sha256:<64 位小写 hex>`,路径由 `attachmentPathOf(home, kind, id)`
|
|
6
|
+
* 自己推导——请求里的任何一截都不参与路径拼接(`name` 只做下载名)。
|
|
7
|
+
* ② **kind 只认 image / file**:分别对应宿主附件库的 `objects`(图片)与 `file-objects`(普通文件);
|
|
8
|
+
* 不认 v2、tmp、cache,也不给"读任意文件"留入口。
|
|
9
|
+
* ③ **单文件 ≤ 20 MiB**:超过不读进内存,回 200 + JSON 失败信封。图片的 `onError` 会换成占位;
|
|
10
|
+
* 普通文件走 `<a download>`,没有页面内失败面——浏览器可能把这份错误响应存成文件。
|
|
11
|
+
* ④ 图片按**魔数**给类型(PNG / JPEG / GIF / WebP);认不出就按下载处理(`application/octet-stream` +
|
|
12
|
+
* `attachment`),不猜、也不拿请求里的名字当类型来源。
|
|
13
|
+
*
|
|
14
|
+
* 请求本身不对(id/kind 形状)⇒ 400;对象不在 / 超限 / 读失败 ⇒ 200 + 信封(与其余段同一条错误面)。
|
|
15
|
+
*/
|
|
16
|
+
import type { FsPort } from '../ports/fs-port.js';
|
|
17
|
+
import type { BinaryResponse } from './route-table.js';
|
|
18
|
+
/** 读附件只需要 stat + read:与 `AttachmentRemovalDeps` 同一条收窄纪律,执行面不给。 */
|
|
19
|
+
export interface AttachmentReadPorts {
|
|
20
|
+
fs: Pick<FsPort, 'statFile' | 'readFile'>;
|
|
21
|
+
home: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function readAttachmentResponse(deps: AttachmentReadPorts, query: URLSearchParams): Promise<BinaryResponse>;
|
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* /dsh-session-delete-api 的前缀路由分发:只负责传输。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 只能自挂自(要生成物 + `zod` + 改包布局),代价与收益完全不成比例。改用宿主**自己的 web server**
|
|
8
|
-
* 开一条插件专属前缀路由:真相/账本/判据全在宿主那边,这一层只搬运。
|
|
4
|
+
* 四件事:请求栅栏 → 查路由表 → 按段注入窄面 → 回信封。纯度、body 预读与读面的判据都在
|
|
5
|
+
* src/api/route-table.ts(方法也从那张表读,字面量家在 `shared/wire.ts` 的段名表;这里只按判据执行);
|
|
6
|
+
* 行投影在 src/api/trash-rows.ts。
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - 前面四条口径(不依赖 Cordis / 信封与浏览器半同形 / 状态语义 / 同源栅栏)与到达序链(口径 ⑤)
|
|
15
|
-
* 仍住这里,因为它们是**传输**的事。
|
|
16
|
-
*
|
|
17
|
-
* 信封(口径 ②):`{ok:true,value}` / `{ok:false,error:{code,message,details?}}`。
|
|
18
|
-
* `error.code` 走 `src/remote/map-error.ts` 的**同一张表**(src/shared/wire.ts 的五个码),原文进
|
|
19
|
-
* `error.message`,结构化附带物进可选的 `error.details`。
|
|
20
|
-
*
|
|
21
|
-
* HTTP 状态不是第二套语义(口径 ③):**操作失败**时状态一律 200(含 `ok:false`)——我们与浏览器半之间的
|
|
22
|
-
* 契约是信封本身,判断成败只看 `ok`。**请求本身不对**才用 4xx(400 请求体 / 405 方法 / 404 未知路由),
|
|
23
|
-
* 同源栅栏拒的是**请求**而不是操作,回 403;浏览器半先解信封再看状态。
|
|
24
|
-
*
|
|
25
|
-
* 同源栅栏(口径 ④):本机回环上一条能删会话的写口不能被任意网页用 simple request CSRF 打——没有栅栏时
|
|
26
|
-
* `content-type: text/plain` 的跨源 POST 不触发预检、直接生效。放行的形状见 {@link isTrustedRequest}。
|
|
27
|
-
*
|
|
28
|
-
* 到达序链(口径 ⑤):`list`(含对账)与写动作都是"读清单 + 动文件 + 写清单"的多步,交错跑会互相踩
|
|
29
|
-
* ——删除刚把目录搬进回收站、行还没写上时,对账抢先把这行按目录"重建"出来,再被真实行覆盖成降级
|
|
30
|
-
* 元数据。串行的代价只是排队(单进程单用户,毫秒级);纯读不占这条链(哪几个段名纯、哪个动作纯,
|
|
31
|
-
* 由路由表说,不在这里列)。
|
|
8
|
+
* - 请求本身不对用 4xx(400 请求体 / 405 方法 / 404 未知路由 / 403 栅栏拒绝);
|
|
9
|
+
* 操作失败回 200 + {ok:false,error}。浏览器半先解信封,再看状态。
|
|
10
|
+
* - 请求栅栏(归属 / 身份)归 `src/api/request-trust.ts`,这里只按它的裁决写 403。
|
|
11
|
+
* - purity 为 chained 的请求串行执行,避免读清册/动文件/写清册交错。
|
|
32
12
|
*/
|
|
33
13
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
34
14
|
import type { DeleteDeps } from '../deps.js';
|
|
35
|
-
|
|
15
|
+
import { type RequestTrustFacts } from './request-trust.js';
|
|
16
|
+
import { type SliceDeps } from './route-table.js';
|
|
17
|
+
/** 三格只建一次:收窄对象在 handler 构造期固定,请求路径只做引用选择。 */
|
|
18
|
+
export declare function slicesOf(full: DeleteDeps): SliceDeps;
|
|
19
|
+
export declare function createApiHandler(deps: DeleteDeps, trust: RequestTrustFacts): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求栅栏:判一条打到 `/dsh-session-delete-api` 的请求归不归本插件办。
|
|
3
|
+
*
|
|
4
|
+
* 两件事分开:**归属**(Host / Origin / Referer 是不是本次部署自己的)与**身份**(远程客户端是不是
|
|
5
|
+
* 宿主的已认证 Web 客户端)。绑定策略归宿主 webserver 配置——它只接受具体接口 IP 字面量、拒绝
|
|
6
|
+
* wildcard——所以「谁能到达」不是本插件能替它决定的事;栅栏本身只防 browser confused deputy,
|
|
7
|
+
* 身份留给宿主(见 ②)。口径与宿主 `/api` 的 `isTrustedApiRequest`(asar
|
|
8
|
+
* `dsh-client-connection/lib/index.js`)对齐,差别只有一条:本模块多查 Referer,因为
|
|
9
|
+
* `GET /attachment` 是二进制段(见末段)。
|
|
10
|
+
*
|
|
11
|
+
* 两条放行支,外加一条缺席规则:
|
|
12
|
+
* ① 头栅栏全过 ∧ 源地址回环 ∧ Host 属于本次部署(回环名 / 监听 bind 字面量)——本机工具与转发落在
|
|
13
|
+
* 回环的桌面壳;
|
|
14
|
+
* ② 宿主 `connection` 服务的 `requestRejection` 放行——远程浏览器。它自带宿主的 Host 白名单
|
|
15
|
+
* (含部署声明的 `trustedHosts`)与「已认证 live peer」,身份就是那次 `dsh web` 打印的 URL 换来的
|
|
16
|
+
* 签名 cookie,不在本插件里复刻;
|
|
17
|
+
* ③ 身份层缺席(`src/ports/connection-port.ts` 报 `absent`,宿主无 `connection` 服务或成员改名)
|
|
18
|
+
* ⇒ 远程一律拒(fail-closed:退回「只有本机」,LAN 上表现为 403 而不是静默放行)。
|
|
19
|
+
*
|
|
20
|
+
* **DNS rebinding 由 Host 归属判据挡**:`Origin === Host` 只证两者同名,不证这个名字属于本部署;
|
|
21
|
+
* 回环 socket 也不构成豁免(重绑到 `127.0.0.1` 的攻击正是回环源地址)。对浏览器来说 Host 是重绑伪造
|
|
22
|
+
* 不了的那个头——它按连接目标填,而 `Origin` 是页面自己的名字——所以归属判定先于任何本机 / 远程
|
|
23
|
+
* 分支,别在它前面加 fast-path。
|
|
24
|
+
*
|
|
25
|
+
* 末段:`GET /attachment` 的响应不是 opaque 的「读不出内容」——页面若已知某个 `sha256:…` 内容哈希,
|
|
26
|
+
* 就能把它当 `<img src>` 跨源渲染出来。那条请求不带 Origin,靠 Referer 判同源挡;宿主 `/api` 没有
|
|
27
|
+
* 二进制段,所以它的栅栏不需要这一条。
|
|
28
|
+
*/
|
|
29
|
+
import type { IncomingMessage } from 'node:http';
|
|
30
|
+
import type { ConnectionPort } from '../ports/connection-port.js';
|
|
31
|
+
export type RequestTrustVerdict = 'allowed' | 'untrusted' | 'unauthenticated';
|
|
32
|
+
export interface RequestTrustFacts {
|
|
33
|
+
/** 监听器绑定的那个具体接口 IP 字面量;老代次没有这个 getter 时为 `undefined`(bind 支退化,仍 fail-closed)。 */
|
|
34
|
+
readonly bindHost: string | undefined;
|
|
35
|
+
/** 宿主的 Web 身份面;端口每次请求惰性解析服务,缺席由它报 `absent`。 */
|
|
36
|
+
readonly connection: ConnectionPort;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 这条请求归不归本插件办。`untrusted` = 归属不对(跨源 / 重绑 / 头不成形);`unauthenticated` =
|
|
40
|
+
* 归属对但远程客户端没带宿主身份(原文会提示用 `dsh web` 打印的 URL 打开)。
|
|
41
|
+
*
|
|
42
|
+
* 远程支的两个失败裁决由端口翻译宿主的区分(401 = 缺身份、403 = 归属不对)。身份层缺席(端口报
|
|
43
|
+
* `absent`)一律按归属不对处理:那时没有可换的身份,不能让原文提示用户去补一个不存在的东西。
|
|
44
|
+
*/
|
|
45
|
+
export declare function judgeRequest(req: IncomingMessage, facts: RequestTrustFacts): RequestTrustVerdict;
|
|
@@ -1,35 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 路由表(ADR-0004;deps 切片见 ADR-0032):段名 → { 方法, 纯度, 读面, body, deps 切片, 处理 }。一条记录,两个消费者。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* - body 该读没读 ⇒ 下游拿到 `null`;读了两遍 ⇒ 请求流已被消费(Node 的流只能消费一次)。
|
|
4
|
+
* 一致性要求:
|
|
5
|
+
* - 该纯的判成写 ⇒ 探测/正文读/扫描被堵在串行链后;
|
|
6
|
+
* - 该写的判成纯 ⇒ 写动作绕开到达序链,与对账交错;
|
|
7
|
+
* - body 该读没读 ⇒ 下游拿到 null;读两遍 ⇒ 流已被消费;
|
|
8
|
+
* - 切片声明宽了 ⇒ 危险能力(删目录 / 时钟)漏进读段的 run;窄了 ⇒ 写段的处理编译不过。
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* 这张表不放 shared/wire.ts:处理函数会 import 动词与 node:fs,而浏览器半要 import wire.ts。
|
|
11
|
+
* 段名、方法与动作仍住 wire.ts(`ROUTES` 每项 `{ path, method }`,本表的 method 只投影、不写第二份字面量);
|
|
12
|
+
* 这张表只回答“怎么处理”。
|
|
14
13
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import type { DeleteDeps } from '../deps.js';
|
|
14
|
+
import { type ApplyAction, type RouteName } from '../shared/wire.js';
|
|
15
|
+
import type { CoreDeps, DeleteDeps, RemovalDeps } from '../deps.js';
|
|
16
|
+
import type { TrashEntry } from '../trash/entry.js';
|
|
17
17
|
/** 请求体:`json` 才读下来(GET 一律不读;读两遍会把流消费掉)。 */
|
|
18
18
|
export type BodyKind = 'none' | 'json';
|
|
19
19
|
/** 到达序:`pure` 不进链;`chained` 进链;函数形式给 `/apply` 用(`count` 是纯读)。 */
|
|
20
20
|
export type Purity = 'pure' | 'chained' | ((body: JsonBody) => 'pure' | 'chained');
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/** 清册读面:`none` = 不读;`list` = 真相读(先对账);`raw` = 索引快照(不对账)。
|
|
22
|
+
* 它与纯度是两条独立的轴:`count` 是 pure/不读,`detail` 是 pure/raw,`ping` 是 pure/不读。 */
|
|
23
|
+
export type ReadKind = 'none' | 'list' | 'raw';
|
|
24
|
+
export type JsonBody = Record<string, unknown> | null;
|
|
25
|
+
/** 附件段的二进制落地:处理函数交回字节与安全头,分发器按它写 200(失败仍是 JSON 信封)。 */
|
|
26
|
+
export interface BinaryResponse {
|
|
27
|
+
readonly body: Buffer;
|
|
28
|
+
readonly contentType: string;
|
|
29
|
+
readonly disposition: 'inline' | 'attachment';
|
|
30
|
+
readonly filename?: string;
|
|
31
|
+
}
|
|
32
|
+
/** 路由交给处理函数的读能力:读段与 `/empty` 只能从这一面取行,不能自己挑 `list()` / `raw()`(`/apply` 的写动词读面归动词自己)。 */
|
|
33
|
+
export interface RouteRead {
|
|
34
|
+
rows(): Promise<readonly TrashEntry[]>;
|
|
35
|
+
}
|
|
36
|
+
/** deps 切片(ADR-0032):每一段只声明自己要的那一面。读与探测六段拿基础面,两个删盘段多删除能力,
|
|
37
|
+
* `/apply` 是唯一拿全量面的行——段的 run 里长出超面调用就是编译错,dispatch 也只按这一格注入
|
|
38
|
+
* (运行期收窄住 `src/api/dispatch.ts` 的 `slicesOf`)。 */
|
|
39
|
+
export type RouteSlice = 'core' | 'removal' | 'full';
|
|
40
|
+
/** 切片名 → 依赖面:{@link RouteSpec.run} 的 deps 参数类型与 `depsFor` 的返回类型都从这一格取。 */
|
|
41
|
+
export type SliceDeps = {
|
|
42
|
+
core: CoreDeps;
|
|
43
|
+
removal: RemovalDeps;
|
|
44
|
+
full: DeleteDeps;
|
|
45
|
+
};
|
|
46
|
+
export interface RouteSpec<S extends RouteSlice = RouteSlice> {
|
|
47
|
+
/** 这一行吃哪一格 deps:dispatch 按它注入,`run` 的 deps 参数类型也跟着它走。 */
|
|
48
|
+
readonly slice: S;
|
|
23
49
|
readonly method: 'GET' | 'POST';
|
|
24
50
|
readonly body: BodyKind;
|
|
25
51
|
readonly purity: Purity;
|
|
26
|
-
|
|
52
|
+
readonly read: ReadKind;
|
|
53
|
+
/** 响应形态:`json` 走信封(`{ok:true,value}`);`binary` 直接把处理函数的 `BinaryResponse` 写出去。 */
|
|
54
|
+
readonly response: 'json' | 'binary';
|
|
55
|
+
/** 声明成函数属性而不是方法:方法签名是双变的,显式标注更宽的 deps 能绕过切片闸;属性按逆变校验。 */
|
|
56
|
+
readonly run: (deps: SliceDeps[S], body: JsonBody, read: RouteRead, query: URLSearchParams) => Promise<unknown>;
|
|
27
57
|
}
|
|
58
|
+
/** 九行的并集(表的值类型):每行由自己的 `slice` 字面量定型,`run` 的 deps 面随之收窄。 */
|
|
59
|
+
export type AnyRouteSpec = {
|
|
60
|
+
[S in RouteSlice]: RouteSpec<S>;
|
|
61
|
+
}[RouteSlice];
|
|
28
62
|
export declare const APPLY_ACTIONS: readonly ApplyAction[];
|
|
29
63
|
/**
|
|
30
|
-
*
|
|
64
|
+
* 九个段名各一行。`Record<段名, AnyRouteSpec>` 是闸:`wire.ts` 加了段名而这里没加,编译不过;
|
|
65
|
+
* 每行的 `slice` 又把该行的 `run` 钉在自己那一格 deps 上(ADR-0032)。
|
|
31
66
|
*/
|
|
32
|
-
export declare const ROUTE_TABLE: Record<
|
|
67
|
+
export declare const ROUTE_TABLE: Record<RouteName, AnyRouteSpec>;
|
|
33
68
|
/** 段名 → 那一行;不认识的段名回 `undefined`(调用方抛 404,并带出原路径)。 */
|
|
34
69
|
export declare function routeOf(rest: string): RouteSpec | undefined;
|
|
35
|
-
export {};
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 归 `verbs/permanent-removal.ts` 的只读计划 —— 这里只做装配,不再自己枚举盘上后代。
|
|
7
7
|
*/
|
|
8
8
|
import type { CoreDeps } from '../deps.js';
|
|
9
|
-
import type
|
|
9
|
+
import { type TrashEntry } from '../trash/entry.js';
|
|
10
10
|
import type { TrashRowWire } from '../shared/wire.js';
|
|
11
11
|
/** 组视图那几个只读数的容器(行投影的第二个入参)。`releaseBytes` 必填(计划里实测好的那一格);
|
|
12
12
|
* 其余缺席 = 这次没算,不是 0。 */
|
|
@@ -14,8 +14,8 @@ type TrashRowReadouts = {
|
|
|
14
14
|
readonly releaseBytes: number;
|
|
15
15
|
} & Partial<Pick<TrashRowWire, 'subagentCount' | 'subagentBytes' | 'strandedSubagents' | 'strandedBytes' | 'workspaceMissing'>>;
|
|
16
16
|
/**
|
|
17
|
-
* 清册那一行 →
|
|
18
|
-
*
|
|
17
|
+
* 清册那一行 → 线上那一行。清册字段走 `entryWire` 那份显式字面量(线上形状多一个必填字段时编译不过,
|
|
18
|
+
* `...row` 会把它带过去),这里只补 `/list` 的只读数。
|
|
19
19
|
*/
|
|
20
20
|
export declare function toWire(row: TrashEntry, readouts: TrashRowReadouts): TrashRowWire;
|
|
21
21
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type TrashDetailAttachmentWire } from '../shared/wire.js';
|
|
2
|
+
import type { Translate } from './copy.js';
|
|
3
|
+
/**
|
|
4
|
+
* 一张附件的取数地址。**只从校验过的 id 与 kind 派生**:不接受路径、不接受任意查询串,
|
|
5
|
+
* 所以调用面把 URL 交给 `<img src>` / `<a href>` 时不存在"读任意文件"的入口。
|
|
6
|
+
*/
|
|
7
|
+
export declare function attachmentUrl(a: TrashDetailAttachmentWire): string;
|
|
8
|
+
/**
|
|
9
|
+
* 一条消息的全部附件。顺序照日志,不重排:用户先发图后发文件,读起来应是他当时的顺序。
|
|
10
|
+
*
|
|
11
|
+
* `align` 是这一行的对齐(默认 `end`,与用户气泡同侧):用户行靠右、助手行靠左,条跟着自己那条消息走。
|
|
12
|
+
* `failed` 按 `attachmentId` 记:同一个内容哈希在一段会话里可能被引用多次,裂一次就都不再画破图。
|
|
13
|
+
* 四个 `useState` 都在早退之前调用——附件从有到无时 hook 数量不变。
|
|
14
|
+
*/
|
|
15
|
+
export declare function AttachmentStrip({ attachments, t, align, }: {
|
|
16
|
+
attachments: readonly TrashDetailAttachmentWire[];
|
|
17
|
+
t: Translate;
|
|
18
|
+
/** `end`(默认)= 靠右,用户气泡那一侧;`start` = 靠左,助手正文那一侧。 */
|
|
19
|
+
align?: 'start' | 'end';
|
|
20
|
+
}): import("react").JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* 附件清理回执那一行提示(「N 个附件未能清理」/「附件未清理:原因」)。表现层只收一句已经拼好的话;
|
|
23
|
+
* 什么时候该有这句话、失败与跳过怎么拼,归接线的那一侧(判据住 `receipt.ts` 与 `use-trash-query.ts`)。
|
|
24
|
+
*/
|
|
25
|
+
export declare function AttachmentNotice({ message }: {
|
|
26
|
+
message: string | null;
|
|
27
|
+
}): import("react").JSX.Element | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 批量删除的浮条:**多选模式内常驻**(0 条也渲染,既显示模式又给退出入口),
|
|
3
3
|
* 汇总「已选 N 条」+(读得到时)「含 K 个子会话」;「查看已选」展开限高可滚动的标题清单
|
|
4
4
|
* (逐条可移除),回答"到底选了哪些"——清单默认收起,不把底部输入区盖住。
|
|
5
5
|
* 模式内的整行点击守卫也挂在这棵常驻宿主上(见 `host-rows.ts` 的 `useSelectionRowClickGuard`)。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 批量删除的确认框——挂在 `shell.overlay` 上的宿主,加它读的那个模块级现场。
|
|
3
3
|
*
|
|
4
4
|
* 为什么又开一个文件而不是把 `delete-confirm.tsx` 撑大:单删那张框是**已交付的面**(docs/ui-spec.md 状态 ②,
|
|
5
5
|
* 用例钉着),批量形与它只有三件不同——标题随条数、目标卡逐条列、执行是一次批量 `apply`。共用一个壳
|
|
6
6
|
* 意味着单删的 `busy` / 失败面要跟着学批量形,那是拿已交付的行为去换省一个文件。卡片样式(`TARGET_CARD`)
|
|
7
7
|
* 与 hint、子会话那两句**复用**单删的,一句不造第二个键。
|
|
8
8
|
*
|
|
9
|
-
* 现场是模块级的(同 `delete-confirm.tsx` / `undo-
|
|
9
|
+
* 现场是模块级的(同 `delete-confirm.tsx` / `undo-notices.ts`):请求从浮条那颗按钮发来,确认框住在
|
|
10
10
|
* `shell.overlay` 上——它必须活在行开关那棵子树之外(槽位行随宿主重排而卸载,见 `delete-confirm.tsx` 头注)。
|
|
11
11
|
*/
|
|
12
12
|
import { type ReactElement } from 'react';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface BusyGate {
|
|
2
2
|
readonly busy: boolean;
|
|
3
|
-
/** 把一次任务整个包进「这一趟还算忙」;落地与抛错都放回 false。
|
|
4
|
-
|
|
3
|
+
/** 把一次任务整个包进「这一趟还算忙」;落地与抛错都放回 false。
|
|
4
|
+
* 同一枚闸已有任务在飞时这一次**不执行**(同步判重),立即回 `undefined`。类型里带上 `undefined`
|
|
5
|
+
* 不是将就,是判据:挡回的那一趟什么都没跑,把它伪装成一枚 `T` 就是把「没跑」当成「跑完且结果是
|
|
6
|
+
* 那个值」——消费返回值的调用方(写入座位、清空)会照着编出失败原文或宣布成功。怎么收尾由调用方
|
|
7
|
+
* 定:这一趟的界面后果归在飞的那一趟,挡回来的那一份什么都不该改。 */
|
|
8
|
+
around: <T>(task: () => Promise<T>) => Promise<T | undefined>;
|
|
5
9
|
}
|
|
6
10
|
export declare function useBusyGate(): BusyGate;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** 写入在飞时,确认框的关闭/取消都拦住:一次批量在飞时关掉框,用户会以为"没动",而它其实正在动。
|
|
2
|
+
* 各张确认框(单删 / 批量删除 / 回收站恢复与彻底删除 / 孤儿会话清理 / 孤儿附件清理)共用这一条规则;
|
|
3
|
+
* `Modal` 的遮罩 / X / Esc 都走同一个 `onClose`。
|
|
4
|
+
*
|
|
5
|
+
* 返回的 `closeDisabled` 与 `close` 同源:取消键的置灰读数也从这里取,各组件不再各写一遍
|
|
6
|
+
* `disabled={busy}`——否则"拦住"这件事会有两个判据。 */
|
|
7
|
+
export interface IdleClose {
|
|
8
|
+
/** 属性声明而不是方法简写:实现体不读 `this`,调用方会把 `close` 摘下来直接交给 `Modal`/
|
|
9
|
+
* `Button`(`onClose={close}`);方法简写会给它隐式 `this: IdleClose`,`unbound-method`
|
|
10
|
+
* 就会把这种正常交接报成可疑。形状仍是同一个函数,零运行时改动。 */
|
|
11
|
+
close: () => void;
|
|
12
|
+
readonly closeDisabled: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function useIdleClose(busy: boolean, onClose: () => void): IdleClose;
|
|
@@ -26,6 +26,7 @@ export interface ClientContextLike {
|
|
|
26
26
|
slots: SlotsLike;
|
|
27
27
|
/** 可选服务的读面(宿主自己的写法:`ctx.get('…')?.…`)。**缺省调用**:老宿主上没有这个方法。 */
|
|
28
28
|
get?(name: string): unknown;
|
|
29
|
-
/** 挂一个随本插件卸载而撤销的副作用(返回的清理函数由框架接管)。
|
|
30
|
-
|
|
29
|
+
/** 挂一个随本插件卸载而撤销的副作用(返回的清理函数由框架接管)。
|
|
30
|
+
* 属性声明:测试会把它当成员读(`expect(ctx.effect).not.toHaveBeenCalled()`),实现不读 `this`。 */
|
|
31
|
+
effect: (execute: () => unknown, label?: string) => unknown;
|
|
31
32
|
}
|
|
@@ -1,39 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 界面文案与宿主 locale 服务的接入。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (宿主技能 `cordis-plugin-development/references/ui-plugin.md`)。宿主自己那套就是这么做的:
|
|
11
|
-
* `ui-sidebar` 声明 `inject = ['slots','layout','uiWorkspace','locale','shortcuts']`,并在
|
|
12
|
-
* `ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar: dictionaries')` 里注册字典
|
|
13
|
-
* (真产物原文)。所以本插件也把文案收成一份带**命名空间**的字典,`zh` 是完整真源、`en` 逐键镜像
|
|
14
|
-
* ——宿主 `register` 的**对象形式要求两套内置语言都齐**(缺一个键就是运行期报错)。
|
|
15
|
-
*
|
|
16
|
-
* ## 服务是**可选读**的,不进 `inject`
|
|
17
|
-
*
|
|
18
|
-
* `ctx.get('locale')` 而不是把 `locale` 写进 `inject`。理由是本仓那条硬约束的另一面:**客户端半的
|
|
19
|
-
* 条目激活失败 = 宿主进程死**(前端启动自检 `web boot: N entry did not activate` ⇒ 桌面壳
|
|
20
|
-
* `reportFatal('web-boot')`)。宿主自己的 `ui-sidebar` 敢硬注入 `locale`(它是宿主的一部分,知道
|
|
21
|
-
* 那一行一定在),而本插件要跨宿主代数活着 ⇒ 服务缺席时**降级到内置 zh 文案继续工作**,
|
|
22
|
-
* 好过整个进程死。这与宿主对可选服务的官方写法同形(`ctx.get('…')?.…`)。
|
|
23
|
-
*
|
|
24
|
-
* 取数面:[`dsh-client-locale`] 的 `LocaleRuntime.register(ns, { zh, en })` 返回一个 disposer
|
|
25
|
-
* (必须挂进 `ctx.effect`,否则卸载后字典留在注册表里,下次挂载会撞 "already has locale"),
|
|
26
|
-
* `bind(ns)` 交回一个**按命名空间记忆化**的 `t(key, params)`(官方明说它"retain stable identity …
|
|
27
|
-
* so they can ride inject surfaces without breaking memoization"——所以我们把它塞进槽位的
|
|
28
|
-
* `inject` 载荷是正当用法)。
|
|
29
|
-
*
|
|
30
|
-
* ## 面板行那一行字为什么要用 thunk
|
|
31
|
-
*
|
|
32
|
-
* `sidebar.panellist` 的行名字由宿主 `syncPanels` 逐字 `label: resolveSlotLabel(options.label) ?? id`
|
|
33
|
-
* 解析,而 `resolveSlotLabel` 的实现是 `typeof label === 'function' ? label() : label`,
|
|
34
|
-
* 其 JSDoc 原文即 "thunks follow the active locale";宿主自己还挂了
|
|
35
|
-
* `ctx.effect(() => ctx.locale.subscribe(syncPanels), 'ui-sidebar: panel labels')` 在语言切换时重读。
|
|
36
|
-
* ⇒ 面板名传 `() => t('trash.title')` 就随语言走;**换成活计数就会 stale**(只在条目或语言变化时重读)。
|
|
4
|
+
* - zh 是完整真源,en 逐键镜像。
|
|
5
|
+
* - locale 服务通过 ctx.get('locale') 可选读,服务缺席时退回内置 zh;为什么不进 inject 见
|
|
6
|
+
* docs/architecture.md §3。
|
|
7
|
+
* - 面板行名传 thunk,语言切换时宿主会重读;其余文案走 t(key, params)。
|
|
8
|
+
* - 渠道错误原文(src/client/receipt.ts 的 missing-id/幂等空值、src/client/remote.ts 的取数通道错误)
|
|
9
|
+
* 不在此字典,它们是协议诊断,固定中文。
|
|
37
10
|
*/
|
|
38
11
|
import { type RelativeTimeUnit } from './ui/format.js';
|
|
39
12
|
import type { ClientContextLike } from './context.js';
|
|
@@ -50,7 +23,7 @@ declare const ZH: {
|
|
|
50
23
|
readonly 'trash.readFailed': "回收站读不到";
|
|
51
24
|
readonly 'trash.empty.title': "回收站是空的";
|
|
52
25
|
readonly 'trash.empty.hint': "在会话条目的「…」菜单里选「删除」,会话会先进到这里,随时可以恢复。";
|
|
53
|
-
readonly 'trash.summary': "{count} 个会话 ·
|
|
26
|
+
readonly 'trash.summary': "{count} 个会话 · 可释放 {size}";
|
|
54
27
|
readonly 'trash.group.count': "{count} 个会话";
|
|
55
28
|
readonly 'trash.project.unknown': "未记录项目";
|
|
56
29
|
readonly 'trash.row.deletedNow': "刚刚删除";
|
|
@@ -70,7 +43,7 @@ declare const ZH: {
|
|
|
70
43
|
readonly 'trash.confirm.all': "清空回收站?";
|
|
71
44
|
readonly 'trash.confirm.one': "彻底删除 1 个会话?";
|
|
72
45
|
readonly 'trash.confirm.one.meta': "{project} · 删除于 {at} · {size}";
|
|
73
|
-
readonly 'trash.confirm.body': "这会从回收站移除文件,{strong}
|
|
46
|
+
readonly 'trash.confirm.body': "这会从回收站移除文件,{strong}。只清理会话独占的图片附件副本(共享副本保留);宿主投影缓存与你磁盘上的原图不受影响。";
|
|
74
47
|
readonly 'trash.confirm.body.strong': "之后没有任何副本可以恢复";
|
|
75
48
|
readonly 'trash.confirm.purge': "彻底删除";
|
|
76
49
|
readonly 'trash.toolbar.collapseAll': "全部折叠";
|
|
@@ -92,27 +65,55 @@ declare const ZH: {
|
|
|
92
65
|
readonly 'trash.orphans.meta': "共 {size},来自 {parents} 个失踪的父会话";
|
|
93
66
|
readonly 'trash.orphans.open': "查看并清理";
|
|
94
67
|
readonly 'trash.orphans.confirm': "清理 {count} 个无主子会话?";
|
|
95
|
-
readonly 'trash.orphans.body': "
|
|
68
|
+
readonly 'trash.orphans.body': "这些子会话的父会话已经不在会话列表或回收站里,清理后不可恢复。正在运行的子会话会被拒绝,不会被删除。只清理会话独占的图片附件副本(共享副本保留);宿主投影缓存与你磁盘上的原图不受影响。若剩余引用源读不出,本次附件清理会跳过(会话目录仍会删除)。";
|
|
96
69
|
readonly 'trash.orphans.group': "{project} · {count} 个 · {size}";
|
|
97
|
-
readonly 'trash.orphans.cacheNote': "
|
|
70
|
+
readonly 'trash.orphans.cacheNote': "本次会一并清理会话独占的图片附件副本;宿主投影缓存不包含在内。若剩余引用源读不出,本次附件清理会跳过(会话目录仍会删除)。";
|
|
98
71
|
readonly 'trash.orphans.clean': "清理";
|
|
99
72
|
readonly 'trash.orphans.failure': "残留检查/清理失败:{message}";
|
|
100
73
|
readonly 'trash.orphans.cleanFailed': "{count} 组未清理:{message}";
|
|
101
74
|
readonly 'trash.orphans.cleanPartial': "已先删掉 {removed} 个会话目录({size})";
|
|
102
75
|
readonly 'trash.orphans.cleanGone': "有 {count} 组已不在了(可能已被别处清理,或父会话已恢复)";
|
|
76
|
+
readonly 'trash.attachments.failed': "已删除会话,但 {count} 个附件未能清理";
|
|
77
|
+
readonly 'trash.attachments.skipped': "附件未清理:{reason}";
|
|
78
|
+
readonly 'trash.attachments.skippedPartial': "部分附件未清理:{reason}(已清理 {removed} 个)";
|
|
79
|
+
readonly 'trash.attachments.alsoFailed': ";另有 {count} 个附件删除失败";
|
|
80
|
+
readonly 'maintenance.title': "维护";
|
|
81
|
+
readonly 'maintenance.orphans.title': "孤儿会话";
|
|
82
|
+
readonly 'maintenance.orphans.hint': "父会话已不在会话列表或回收站里的子会话。检查只读,不会自动运行。";
|
|
83
|
+
readonly 'maintenance.attachments.title': "孤儿附件";
|
|
84
|
+
readonly 'maintenance.attachments.hint': "在 .dsh 里没有任何引用的图片副本。检查只读,清理只动这些副本。";
|
|
85
|
+
readonly 'maintenance.scan': "检查";
|
|
86
|
+
readonly 'maintenance.scanning': "检查中…";
|
|
87
|
+
readonly 'maintenance.cleaning': "清理中…";
|
|
88
|
+
readonly 'maintenance.checkFailed': "检查失败";
|
|
89
|
+
readonly 'maintenance.notScanned': "尚未检查";
|
|
90
|
+
readonly 'maintenance.none': "没有发现";
|
|
91
|
+
readonly 'maintenance.orphans.count': "{count} 个";
|
|
92
|
+
readonly 'maintenance.attachments.count': "{count} 个";
|
|
93
|
+
readonly 'maintenance.attachments.scanFailed': "检查失败:{message}";
|
|
94
|
+
readonly 'maintenance.attachments.cleaned': "已清理 {count} 个({size})";
|
|
95
|
+
readonly 'maintenance.attachments.cleanFailed': "{count} 个附件未能清理(已清理 {removed} 个)";
|
|
96
|
+
readonly 'maintenance.attachments.cleanError': "附件清理失败:{message}";
|
|
97
|
+
readonly 'maintenance.attachments.confirm': "查看并清理 {count} 个孤儿附件?";
|
|
98
|
+
readonly 'maintenance.attachments.body': "只清理 .dsh 内无引用的图片副本;源文件不受影响;共享引用保留。";
|
|
99
|
+
readonly 'maintenance.attachments.previewTruncated': "仅预览前 {shown} 个;清理会处理全部 {total} 个。";
|
|
100
|
+
readonly 'maintenance.attachments.previewHint': "点图片可放大;关闭这个框不会删除任何东西。";
|
|
101
|
+
readonly 'maintenance.attachments.clean': "清理这些附件";
|
|
103
102
|
readonly 'detail.notSelected': "选中一条会话,看它讲了什么";
|
|
104
103
|
readonly 'detail.loading': "正在读这条会话…";
|
|
105
104
|
readonly 'detail.readFailed': "这条会话的正文读不出来";
|
|
106
|
-
readonly 'detail.noBody': "
|
|
105
|
+
readonly 'detail.noBody': "这条会话没有可显示的内容";
|
|
107
106
|
readonly 'detail.field.created': "创建";
|
|
108
107
|
readonly 'detail.field.lastActivity': "最后活动";
|
|
109
108
|
readonly 'detail.field.deletedAt': "删除于";
|
|
109
|
+
readonly 'detail.deletedAt.combined': "{relative}({absolute})";
|
|
110
110
|
readonly 'detail.field.span': "活跃跨度";
|
|
111
111
|
readonly 'detail.field.size': "大小";
|
|
112
112
|
readonly 'detail.field.turns': "轮数";
|
|
113
113
|
readonly 'detail.field.userMessages': "提问";
|
|
114
|
-
readonly 'detail.field.assistantMessages': "
|
|
114
|
+
readonly 'detail.field.assistantMessages': "助手消息";
|
|
115
115
|
readonly 'detail.field.toolCalls': "工具调用";
|
|
116
|
+
readonly 'detail.field.injections': "注入上下文";
|
|
116
117
|
readonly 'detail.field.models': "模型";
|
|
117
118
|
readonly 'detail.field.project': "项目";
|
|
118
119
|
readonly 'detail.field.sessionId': "会话 id";
|
|
@@ -120,11 +121,50 @@ declare const ZH: {
|
|
|
120
121
|
readonly 'detail.field.wasArchived': "原本归档";
|
|
121
122
|
readonly 'detail.value.yes': "是";
|
|
122
123
|
readonly 'detail.value.no': "否";
|
|
124
|
+
readonly 'detail.value.spanLessThanHour': "不足 1 小时";
|
|
123
125
|
readonly 'detail.value.spanHours': "{n} 小时";
|
|
124
126
|
readonly 'detail.value.spanDays': "{n} 天";
|
|
125
|
-
readonly 'detail.
|
|
126
|
-
readonly 'detail.
|
|
127
|
+
readonly 'detail.block.reasoning': "思考";
|
|
128
|
+
readonly 'detail.block.tool': "工具调用";
|
|
129
|
+
readonly 'detail.block.injection': "上下文注入";
|
|
130
|
+
readonly 'detail.block.system': "系统消息";
|
|
131
|
+
readonly 'detail.block.compaction': "上下文已压缩";
|
|
132
|
+
readonly 'detail.block.args': "参数";
|
|
133
|
+
readonly 'detail.block.result': "结果";
|
|
134
|
+
readonly 'detail.block.noResult': "日志在调用结束前结束,没有结果";
|
|
135
|
+
readonly 'detail.block.status.ok': "成功";
|
|
136
|
+
readonly 'detail.block.status.error': "失败";
|
|
137
|
+
readonly 'detail.block.status.unfinished': "未完成";
|
|
138
|
+
readonly 'detail.block.truncated': "已截断";
|
|
139
|
+
readonly 'detail.loadOlder': "加载更早";
|
|
140
|
+
readonly 'detail.loadOlder.loading': "正在加载更早…";
|
|
141
|
+
readonly 'detail.expandAll': "全部展开";
|
|
142
|
+
readonly 'detail.collapseAll': "全部折叠";
|
|
143
|
+
readonly 'detail.injection.runtimeContext': "运行上下文";
|
|
144
|
+
readonly 'detail.injection.timeContext': "时间上下文";
|
|
145
|
+
readonly 'detail.injection.agentInstructions': "Agent 指令";
|
|
146
|
+
readonly 'detail.injection.skillCatalog': "技能目录";
|
|
147
|
+
readonly 'detail.injection.agentMessage': "子会话消息";
|
|
148
|
+
readonly 'detail.injection.subagentSettled': "子会话结束";
|
|
149
|
+
readonly 'detail.injection.toolJobs': "后台任务";
|
|
150
|
+
readonly 'detail.injection.skillInvocation': "技能调用";
|
|
151
|
+
readonly 'detail.injection.userApproval': "用户批准";
|
|
152
|
+
readonly 'detail.injection.goal': "目标";
|
|
153
|
+
readonly 'detail.injection.other': "其他上下文";
|
|
154
|
+
readonly 'detail.image': "[图片]";
|
|
155
|
+
readonly 'detail.image.alt': "[图片 · {alt}]";
|
|
156
|
+
readonly 'detail.attachment.file': "文件";
|
|
157
|
+
readonly 'detail.attachment.preview': "附件预览";
|
|
158
|
+
readonly 'detail.attachment.alt': "附件图片";
|
|
159
|
+
readonly 'detail.attachment.open': "打开图片「{name}」";
|
|
160
|
+
readonly 'detail.attachment.unavailable': "附件不可用";
|
|
161
|
+
readonly 'detail.attachment.save': "另存为";
|
|
162
|
+
readonly 'detail.attachment.saving': "保存中…";
|
|
163
|
+
readonly 'detail.attachment.saveFailed': "保存失败:{message}";
|
|
127
164
|
readonly 'detail.back': "← 返回清单";
|
|
165
|
+
readonly 'detail.subagents.header': "子会话({n}) · {size}";
|
|
166
|
+
readonly 'detail.subagents.truncated': "仅列出前 {shown} 条,共 {total} 条";
|
|
167
|
+
readonly 'detail.backToParent': "← 返回父会话";
|
|
128
168
|
readonly 'detail.list.label': "回收站清单";
|
|
129
169
|
readonly 'detail.role.user': "你";
|
|
130
170
|
readonly 'detail.role.assistant': "助手";
|
|
@@ -150,6 +190,7 @@ declare const ZH: {
|
|
|
150
190
|
readonly 'undo.moved': "{title} 已移入回收站";
|
|
151
191
|
readonly 'undo.session': "会话";
|
|
152
192
|
readonly 'undo.undo': "撤销";
|
|
193
|
+
readonly 'undo.failedBatch': "{n} 条会话未能撤销:{reasons}";
|
|
153
194
|
};
|
|
154
195
|
export type CopyKey = keyof typeof ZH;
|
|
155
196
|
export type CopyParams = Readonly<Record<string, string | number>>;
|
|
@@ -164,6 +205,25 @@ export declare const zhTranslate: Translate;
|
|
|
164
205
|
* 而且同一命名空间的同一语言**只能注册一次**——effect 没挂,HMR 重挂就撞 "already has locale"。
|
|
165
206
|
*/
|
|
166
207
|
export declare function createTranslate(ctx: ClientContextLike): Translate;
|
|
208
|
+
/**
|
|
209
|
+
* `MarkdownBody`(`markdown.tsx`)memo comparator 的**语言探针**:借一句固定键的译文,当
|
|
210
|
+
* 「当前是哪种语言」的读数。为什么借译文而不问 locale 服务:宿主 locale 服务的窄镜像不暴露
|
|
211
|
+
* 当前语言(受窄镜像形状约束,不做 `locale()` 探针),当前语言只能从 `t` 的输出里读出来。
|
|
212
|
+
*
|
|
213
|
+
* 为什么必须在**渲染时**现算、由调用方存进 props:宿主 `t` 读的是**当前** locale,若在
|
|
214
|
+
* comparator 里现调,prev/next 那一拍都取到新值、恒等,换语言就漏了——只有渲染那一刻算好的
|
|
215
|
+
* 那份才是换语言那一拍的读数。
|
|
216
|
+
*
|
|
217
|
+
* 为什么拿 `t` 的输出当读数、而不是拿 `t` 的身份:宿主 `t` 是 `bind` 出来的**稳定身份**
|
|
218
|
+
* (`createTranslate` 一次客户端激活只造一个),换语言后同一个 `t` 吐另一套词——身份不变、
|
|
219
|
+
* 词变了,所以判据必须是这个词(探针值)而不是身份本身。
|
|
220
|
+
*
|
|
221
|
+
* 「借哪句」的知识只住这里:调用方与测试一律传 `probe={localeProbe(t)}`,不各自记得借哪句。
|
|
222
|
+
*
|
|
223
|
+
* **失效模式**:借的那句若在两种语言下译成同一个字面,探针就分不出换语言——memo 恒等、旧语言滞留。
|
|
224
|
+
* 这条由 `tests/client/markdown.test.tsx` 的换语言用例兜住;换借句或改那句译文时先看它红不红。
|
|
225
|
+
*/
|
|
226
|
+
export declare function localeProbe(t: Translate): string;
|
|
167
227
|
/**
|
|
168
228
|
* 把一句话里内嵌的那一段拆出来:`'…文件,之后没有任何副本可以恢复。'` 交给调用面用 `<b>` 包中间那段。
|
|
169
229
|
*
|
|
@@ -16,7 +16,7 @@ export declare const TARGET_CARD: {
|
|
|
16
16
|
export interface DeleteRequest {
|
|
17
17
|
/** 每次发布递增:换一个会话就重挂载确认框,busy / failure 不带着上一条请求的记忆。 */
|
|
18
18
|
seq: number;
|
|
19
|
-
/** 要删的会话 id
|
|
19
|
+
/** 要删的会话 id——原样透给删除写入(`apply` 的 `delete` 动作;段名与动作住 `shared/wire.ts`)。 */
|
|
20
20
|
sessionId: string;
|
|
21
21
|
/** 删除那一刻的会话名(槽位投影的 `displayTitle`):只用于确认框文案与撤销提示。 */
|
|
22
22
|
title: string;
|
|
@@ -26,8 +26,6 @@ export declare function requestDelete(input: {
|
|
|
26
26
|
sessionId: string;
|
|
27
27
|
title: string;
|
|
28
28
|
}): void;
|
|
29
|
-
/** 收起确认框(用户点「取消」/「关闭」、或删除成功后收工)。 */
|
|
30
|
-
export declare function clearDeleteRequest(): void;
|
|
31
29
|
/** 供宿主组件订阅。 */
|
|
32
30
|
export declare function useDeleteRequest(): DeleteRequest | null;
|
|
33
31
|
/** 测试专用:现场与 seq 归零,用例之间互不串味。 */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Translate } from './copy.js';
|
|
2
2
|
export interface DeleteMenuItemProps {
|
|
3
|
-
/** 宿主菜单槽投影的会话 id
|
|
3
|
+
/** 宿主菜单槽投影的会话 id,原样透给删除写入(`apply` 的 `delete` 动作;段名与动作住 `shared/wire.ts`,浏览器半不自己拼 id)。 */
|
|
4
4
|
sessionId: string;
|
|
5
5
|
/** 宿主菜单槽投影的 `displayTitle`:只用于确认框与提示文案,不参与任何请求。 */
|
|
6
6
|
title: string;
|
|
@@ -7,9 +7,13 @@ export interface HostRowInfo {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* 行尾兜底勾选开关登记一行:模式内与模式外都要登记(守卫进模式的那一帧就得有标题)。
|
|
10
|
-
*
|
|
10
|
+
* 每次登记算**多一个挂载实例**;只有标题/运行态**真的变了**才标脏:重渲染时的同值登记
|
|
11
|
+
* 不该把快照翻新一遍。
|
|
11
12
|
*/
|
|
12
13
|
export declare function registerHostRowInfo(sessionId: string, info: HostRowInfo): void;
|
|
14
|
+
/** 行尾开关卸载时调用:那一行已经离开渲染树,登记跟着走。
|
|
15
|
+
* 还有别的挂载实例在用同一行(重复行)就只减计数,不摘登记。 */
|
|
16
|
+
export declare function unregisterHostRowInfo(sessionId: string): void;
|
|
13
17
|
/**
|
|
14
18
|
* 「当前可见」的可选行:侧栏里**已渲染**的会话行(`data-row-key="session:…"`)——折叠组、
|
|
15
19
|
* 归档过滤外的行不在 DOM 里,宿主隐藏的 subagent 也不在;在跑的行跳过(与置灰勾选框同口径)。
|