dsh-recall-plugin 2.5.0 → 2.5.2
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/CHANGELOG.md +43 -0
- package/README.en.md +35 -10
- package/README.md +35 -10
- package/lib/client.js +42 -12
- package/lib/config.js +2 -2
- package/lib/scripts.posix.js +33 -11
- package/lib/scripts.pwsh.js +33 -20
- package/lib/snapshots.js +1 -1
- package/package.json +12 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [2.5.2] - 2026-10-28
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- **编辑重发带图片时「草稿填了却不发」(活体回归发现,仅 0.2.2-alpha.2 触发)**:`createDrafts` 第三参传 `true` 会让图片进输入态即后台上传,而官方 `sendSession` 会**等上传回执**才发 prompt——刚 fork 的子会话上传迟迟不结算时,草稿留在输入框、消息不发、控制台无错(真机复现两次;同场景纯文本编辑重发一次成功)。改为按场景分流:撤回回填(只填不发)仍传 `true` 保留即时上传;编辑重发(自动发送)传 `false`,图片字节随 prompt 内联,发送一步到位。
|
|
12
|
+
|
|
13
|
+
### 兼容
|
|
14
|
+
|
|
15
|
+
- **dsh 0.2.2-alpha.2(一处破坏,已修)**:官方把 `readAttachment` 的入参从 `attachmentId` 字符串换成完整 `ImageAttachmentRef`,宿主不再回会话日志反查授权,裸 id 直接 `INVALID_ATTACHMENT_REF`。插件原先传裸 id → **撤回回填与编辑重发的图片会被逐项 `try/catch` 静默丢弃**。现按「完整引用 → 裸 id」探测回退并把命中代缓存在闭包,两代同时可用(0.1.x / 0.2.0 / 0.2.1 行为不变)。另补 `createDrafts` 第三参 `uploadImages=true`(旧版忽略),恢复「图片进输入态即上传」的官方语义;三参形态抛错时自动退回两参(图片改随 prompt 内联),不让整批附件丢失。
|
|
16
|
+
- **`sessions.fork` 停写 `parentSession`**:普通分支改由 header `origin` 判定,插件只读 `origin` 且血缘自存 `lineage.json`,无需改码;fork 子会话不再有被当作子代理的残余风险。
|
|
17
|
+
- **cordis 配置访问统一**:loader 的 ref 来源 `fiber.config` → `fiber.effectiveConfig`,volatile 提交与 `loader/volatile-update` 派发链未变,设置面热更不受影响。
|
|
18
|
+
|
|
19
|
+
### 变更
|
|
20
|
+
|
|
21
|
+
- **`test:probe` 58 条**:新增「附件回读链(B1)」钉住 `readAttachment` 单参形状与 `{ attachment, data }` 返回;`createDrafts` 探针并入第三参(新旧两形态都放行);loader 探针按 `fiber.effectiveConfig` 同步改名并注明两种拼写。
|
|
22
|
+
- **client 单测 121 → 129**:新增 `tests/client/draft-fill.test.ts`(对象口径 / 字符串口径 / 命中代缓存 / 双失败降级 / 仅裸 id 的旧调用方形状 / 回填第三参 true / 自动发送第三参 false / 三参抛错退回两参);`attachmentRefsFromBlocks` 的形状断言并入既有 unit 用例(原始引用随行)。
|
|
23
|
+
|
|
24
|
+
### 文档
|
|
25
|
+
|
|
26
|
+
- 台账同步 dsh 0.2.2-alpha.2:`dshReleases` 补 `0.2.2-alpha.2`、六条 `@deepseek-ai/dsh-*` peer 追加 `>=0.2.2-alpha.1 <0.3.0`(cordis `>=4.0.6-alpha.1 <4.0.7`、schemastery `>=3.18.6-alpha.1 <3.18.7`)、`docs/reference/` 第十一次重拉(13 源中 3 份有差异,均不落插件消费面)、`docs/dsh-contract.md` 对应版本与新增 §1.2「输入区写入链与附件回读」+ 沿革⑫、`docs/compat-audit.md` 新核验段、新增 [upgrade-assessments/dsh-0.2.2-alpha.2.md](docs/upgrade-assessments/dsh-0.2.2-alpha.2.md)、README 双语徽章与版本范围。
|
|
27
|
+
|
|
28
|
+
## [2.5.1] - 2026-10-27
|
|
29
|
+
|
|
30
|
+
### 兼容
|
|
31
|
+
|
|
32
|
+
- **dsh 0.2.1-rc.3 零破坏(无需改码)**:6 个 commit 全是纯加法与内部修复——新增客户端包 `dsh-client-ui-entry-customization`(左/右侧栏与会话视图的「更多」菜单)只声明并注册三个 root scope list 槽,与插件四个注册 key 零交集;新会话草稿修复落在 `ui-workspace.startSession` / `createInWorkspace` 的 blank 复用,插件不调用该入口;API Key 路由模型列表改跟官方 `/models`,客户端目录契约 `dsh-client-ui-model-selection` 零漂移。`sessions.fork`、`setDraft`/`createDrafts`/`addAttachments`、`openSession`/`unarchiveSession`、slot 定义方(`ui-chat`)、兼容门禁(`dsh-app-boot`)逐字节零变更;`test:probe` 58/58、`verify:host` 全过、六条 peer 对 rc.3 全满足,宿主启停冒烟零 compat/skip。评估见 [docs/upgrade-assessments/dsh-0.2.1-rc.3.md](./docs/upgrade-assessments/dsh-0.2.1-rc.3.md)。
|
|
33
|
+
|
|
34
|
+
### 变更
|
|
35
|
+
|
|
36
|
+
- **`check:dsh` 改按 `dist-tags` 判定「npm 最新版」**:官方 rc 线发在 `next` 上、`latest` 停在上一版(0.2.1-rc.3 实测 `latest=rc.2` / `next=rc.3`),原实现只读 `version`(= latest),会把已发布的候选版误报成「无新版本」。现在两标签取较新者作 peer 覆盖与升级提示的基准,并在该行注明判定依据;`dist-tags` 读取失败时回退单版本探测(fail-open)。新增 5 条单测(报告判定 2 + 取较新者 3)。
|
|
37
|
+
- **`test:probe` 补输入区写入链 4 条(54 → 58)**:回填与编辑重发依赖的官方入口此前只有运行时能力探测兜底、探针零覆盖——官方若把 `setDraft` 并入新的 `whenDraftExists` 策略模型或改签名,没有任何红灯。现钉住 `InputActions.setDraft/addAttachments/submit`、`InputState.draft`、`SessionInputShell.actions` + `get snapshot()`、`createDrafts`/`releaseDraftAttachments`。
|
|
38
|
+
|
|
39
|
+
### 修复
|
|
40
|
+
|
|
41
|
+
- **影子仓库写 index 的调用补 `--work-tree`(既有缺陷,与 issue #28 同批发现)**:影子仓库用 `git init --bare` 建,裸 `--git-dir` 下 git 认为没有工作树,`update-index` 直接 fatal `this operation must be run in a work tree`。后果按平台分叉:win32 上 git 非零退出不抛,**超大文件剔除(`maxFileBytes`)与 gitlink 剔除静默失效**——10 MB 文件照样进快照;POSIX 上 `set -e` 让「工作区里有带 `.git` 的子项目」这类场景**每条消息都快照失败**。三处调用(`dropGitlinks` / `oversize` / `excludeSync` 的已跟踪条目清理)统一补上,`excludeSync` 因建仓路径没有 `$root` 而走条件分支。契约单测钉住「写 index 的 `update-index` 一律带 `--work-tree`」。
|
|
42
|
+
- **快照总量护栏不再把 `.gitignore` 排除的目录算进去(issue #28)**:`maxWorkspaceBytes` 的前置统计改为按 `git ls-files -z -co --exclude-standard` 的清单求和,与 `add -A` 共用同一套排除引擎——`.gitignore`(每层继承)、`.git/info/exclude`、全局 excludesFile、`!` 反选与嵌套仓库的判定从此与真正进快照的口径一致。此前只认基础排除表与 `exclude.txt`,实测把 130 MB 的项目量成 297 MB 并整区停用快照(用户直接失去撤回能力),提示还会把人引向「调大上限」这个反向处置。顺带修好「父级工作区把带自己 `.git` 的子项目算进总量」。`ls-files` 失败时 fail-open 不判定(不回落目录遍历,避免两套口径随 git 状态跳变);停用提示与设置页 hint 补注「已按 .gitignore 与排除表过滤」。
|
|
43
|
+
|
|
44
|
+
### 文档
|
|
45
|
+
|
|
46
|
+
- 台账同步 dsh 0.2.1-rc.3:`dshReleases` 补 `0.2.1-rc.3`、`docs/reference/` 第十次重拉(13 源逐字节相同,连续第二轮零差异)、`docs/dsh-contract.md` 对应版本与沿革⑪、`docs/compat-audit.md` rc.3 核验段、README 双语徽章与版本范围。
|
|
47
|
+
|
|
5
48
|
## [2.5.0] - 2026-10-24
|
|
6
49
|
|
|
7
50
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.2-alpha.2)
|
|
11
11
|

|
|
12
12
|
|
|
13
13
|
Under any message you've sent, click "↶ Recall" — **your workspace files and the conversation history roll back to just before that message was sent**.
|
|
14
14
|
|
|
15
|
-
As each message is sent, the workspace is first snapshotted into an independent shadow git repository (your project's own git is never touched); a recall restores files from that snapshot and rewinds the conversation through DSH's official `sessions.fork` to the turn boundary before the message, with the original session archived and recoverable. The recalled message's text and attachments are placed back into the input box, ready to edit and resend. Snapshot storage lives under `$DSH_HOME` by default. The main boundary: snapshots are created only **when a message is sent** — messages from before the plugin was enabled have no snapshot and show no recall button.
|
|
15
|
+
As each message is sent, the workspace is first snapshotted into an independent shadow git repository (your project's own git is never touched); a recall restores files from that snapshot and rewinds the conversation through DSH's official `sessions.fork` to the turn boundary before the message, with the original session archived and recoverable. The recalled message's text and attachments are placed back into the input box, ready to edit and resend; you can also click "✎ Edit" next to a message to revise and resend it in place (files untouched), and flip between versions of the same stretch of conversation with the `‹ X/N ›` pager. Snapshot storage lives under `$DSH_HOME` by default. The main boundary: snapshots are created only **when a message is sent** — messages from before the plugin was enabled have no snapshot and show no recall button.
|
|
16
16
|
|
|
17
17
|
[Changelog](CHANGELOG.md)
|
|
18
18
|
|
|
@@ -20,11 +20,13 @@ As each message is sent, the workspace is first snapshotted into an independent
|
|
|
20
20
|
|
|
21
21
|
- [UI Preview](#ui-preview)
|
|
22
22
|
- [Highlights](#highlights)
|
|
23
|
+
- [Tech Stack & Dependencies](#tech-stack--dependencies)
|
|
23
24
|
- [Known Limitations](#known-limitations)
|
|
24
25
|
- [Installation](#installation)
|
|
25
26
|
- [Usage](#usage)
|
|
26
27
|
- [Configuration](#configuration)
|
|
27
28
|
- [Snapshot Maintenance & Cleanup](#snapshot-maintenance--cleanup)
|
|
29
|
+
- [FAQ](#faq)
|
|
28
30
|
- [How It Works](#how-it-works)
|
|
29
31
|
- [Event Contract (for same-host plugins)](#event-contract-for-same-host-plugins)
|
|
30
32
|
- [Contributing](#contributing)
|
|
@@ -55,6 +57,15 @@ Abilities with a version in parentheses require that version or later; the rest
|
|
|
55
57
|
- **Disk-friendly, self-maintaining**: snapshots use git delta compression and large files are skipped automatically (threshold configurable); periodic lossless `git gc`, session-deletion cleanup, and cleanup by cap or retention age — plus a **workspace → session → snapshot** tree manager on the settings page with search and per-level deletion.
|
|
56
58
|
- **Failures speak up and heal** (self-healing 2.1+): failures are classified by root cause (git missing / disk full / permission denied / lock conflict / directory conflict) with actionable guidance (the same fault only bothers you once per 10 minutes; reasons land in the settings card's "Recent errors"); leftover objects are pruned, 3 consecutive failures trigger exponential backoff, concurrent instances yield via heartbeats, and unindexable paths are skipped with a notice instead of failing the whole snapshot (they are neither restored nor deleted on recall).
|
|
57
59
|
|
|
60
|
+
## Tech Stack & Dependencies
|
|
61
|
+
|
|
62
|
+
This plugin is a DSH bundle: the host half runs inside the DSH process (Node ESM) and the client half inside the browser session page — both ship from the same npm package, which has zero runtime dependencies of its own.
|
|
63
|
+
|
|
64
|
+
- **Runtime**: Node.js ≥ 20 and git CLI; PowerShell 5.1/7 on Windows, bash on Linux/macOS (selected automatically by platform).
|
|
65
|
+
- **Language & build**: TypeScript + ESM; `npm run build` produces `lib/` via esbuild (per-file host transpilation, single-file client bundle), and the artifacts are committed with the source.
|
|
66
|
+
- **Tests & gates**: vitest (pure-logic unit tests, jsdom client component tests, official-API field probes) plus a `tsc --noEmit` type gate and an assembly-gate script — see "Contributing → Tests".
|
|
67
|
+
- **Host dependencies** (peerDependencies, provided by the DSH installation — nothing extra to install): `@deepseek-ai/cordis` 4, `@deepseek-ai/dsh-*` (host-webserver / sandbox-policy / session / session-query / settings / shell), `@deepseek-ai/schemastery` 3.18; `react` 18 is an optional peer (injected by the host for the client).
|
|
68
|
+
|
|
58
69
|
## Known Limitations
|
|
59
70
|
|
|
60
71
|
Boundaries accepted by design and edge cases not yet covered — worth checking before you rely on them.
|
|
@@ -76,7 +87,7 @@ Prerequisites:
|
|
|
76
87
|
|
|
77
88
|
- git CLI: without it the recall button won't appear (a notice shows at the top of the page); DSH itself keeps running.
|
|
78
89
|
- Shell: PowerShell 5.1 / 7 on Windows; bash on Linux/macOS.
|
|
79
|
-
- DSH version: `0.1.2-alpha.1` through `0.2.
|
|
90
|
+
- DSH version: `0.1.2-alpha.1` through `0.2.2-alpha.2`; later versions within a verified minor line are admitted automatically, while unverified new minor lines are blocked by the startup compatibility gate (window mechanics in the fold below).
|
|
80
91
|
- **0.1.7-alpha.1 is a breaking release** (the shell execution and settings interfaces changed); the plugin ships both seams side by side — the same release works on every 0.1.2–0.1.6 line and on 0.1.7+, with unchanged behaviour on older DSH versions.
|
|
81
92
|
- `0.1.1-rc.2` and earlier are not supported: their client runtime lacks the `sessions`/`workspaces`/`uiWorkspace` services, so the plugin UI silently fails to render.
|
|
82
93
|
|
|
@@ -86,10 +97,10 @@ Prerequisites:
|
|
|
86
97
|
peerDependencies open a window per minor line, consistent with the `dsh.compatibility.dshReleases` declaration:
|
|
87
98
|
|
|
88
99
|
```
|
|
89
|
-
>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0
|
|
100
|
+
>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0
|
|
90
101
|
```
|
|
91
102
|
|
|
92
|
-
Each line is anchored at its first verified version with an upper bound at the next minor (the 0.2.1
|
|
103
|
+
Each line is anchored at its first verified version with an upper bound at the next minor (the 0.2.1 and 0.2.2 windows widen their upper bound to `<0.3.0`, admitting every 0.2 release), so later prereleases/releases within a verified line are admitted without touching the peer declaration. The 0.2.0, 0.2.1 and 0.2.2 lines each need their own tuple: npm's prerelease gate only admits a prerelease from a comparator with the same tuple, so the existing `<0.1.8` segment cannot admit `0.2.0-rc.1`, `<0.2.1` cannot admit `0.2.1-alpha.1`, and `>=0.2.1-alpha.1 <0.3.0` cannot admit `0.2.2-alpha.2` (first verified 0.2.2 release, 2026-10-28).
|
|
93
104
|
|
|
94
105
|
</details>
|
|
95
106
|
|
|
@@ -145,8 +156,8 @@ All options can be edited visually in the "**Settings → Plugin Config → Reca
|
|
|
145
156
|
| `gcSnaps` | 50 | Run `git gc` after this many snapshots accumulate (env `DSH_RECALL_GC_SNAPS` force-overrides) |
|
|
146
157
|
| `gcHours` | 24 | Run gc when this many hours have passed since the last one (whichever trigger fires first; env `DSH_RECALL_GC_HOURS`) |
|
|
147
158
|
| `maxFileBytes` | 8388608 (8MB) | Files larger than this are neither snapshotted nor touched by recalls |
|
|
148
|
-
| `maxWorkspaceBytes` | 268435456 (256MB) | Total snapshot-eligible file size per workspace. Exceeding it **disables snapshots for that workspace** (no truncation — a truncated snapshot would make rollback delete the skipped files) and shows a notice on recent messages; raise the limit or add exclusions to recover automatically. 0 = unlimited |
|
|
149
|
-
| `scanBudgetMs` | 10000 (10s) | Time budget for the
|
|
159
|
+
| `maxWorkspaceBytes` | 268435456 (256MB) | Total snapshot-eligible file size per workspace, measured exactly as `git add` sees it (`.gitignore`, `.git/info/exclude` and the global excludesFile all apply). Exceeding it **disables snapshots for that workspace** (no truncation — a truncated snapshot would make rollback delete the skipped files) and shows a notice on recent messages; raise the limit or add exclusions to recover automatically. 0 = unlimited |
|
|
160
|
+
| `scanBudgetMs` | 10000 (10s) | Time budget for measuring the snapshot-eligible total. Exceeding it skips the snapshot with a notice (guards against pathological directories stalling startup). 0 = unlimited |
|
|
150
161
|
| `maxSnapshotsPerWorkspace` | 500 | Maximum snapshots kept per workspace; oldest pruned beyond the cap. 0 = unlimited |
|
|
151
162
|
| `retentionDays` | 0 | Keep snapshots for this many days; older ones are deleted. 0 = disabled (works independently of the cap) |
|
|
152
163
|
| `baseExcludes` | `.git`, `node_modules/`, `.dsh-recall-snapshots/`, `dsh-recall-snapshots/`, `target/`, `dist/`, `build/`, `out/`, `coverage/`, `.next/`, `.nuxt/`, `.output/`, `.cache/`, `.gradle/`, `*.exe`, `*.dll`, `*.pdb`, `*.so`, `*.dylib`, `*.msi`, `*.zip`, `*.7z`, `*.rar`, `*.tar`, `*.tar.gz`, `*.iso` | Base exclusion list (gitignore syntax, lower priority than exclude.txt); excluded large directories are skipped as whole subtrees during snapshot scans. Directory-form entries (e.g. `target/`) mean one more thing: **when a path segment of the workspace root itself matches, snapshots are disabled for that workspace** — patterns are relative to the root, so they can never exclude the root itself (i.e. a session opened inside a build-output directory), and such directories have no rollback value anyway; remove the entry to re-enable |
|
|
@@ -183,6 +194,20 @@ The plugin manages disk usage automatically — no manual housekeeping needed:
|
|
|
183
194
|
This applies to all projects (when home is unwritable and a workspace falls back to in-project storage, it gets its own independent exclusion config, listed as a separate card in the settings tab). New exclusions only affect future snapshots; **when recalling to an earlier snapshot, files that weren't excluded at that time are still restored** (returning to the state as it was — that's exactly what recall means). To fully purge a directory that already made it into snapshots, manually delete the corresponding hash directory under `dsh-recall-snapshots/` in home.
|
|
184
195
|
- **Tree-view snapshot manager**: open "**Settings → Plugin Config → Recall Plugin → Snapshot Manager**" for a three-level tree — workspace (folder name) → session (session title, recall chains grouped into version families) → snapshot (time + message content summary, hover for the full content). Search and "load more" are supported; workspace and session nodes expand/collapse; every level has a delete button on its right, with a confirmation before deletion — deleting a workspace clears all of its snapshots, deleting a session clears all of that session's snapshots, deleting a leaf removes just that single snapshot; a confirmed "Delete all" button sits at the top.
|
|
185
196
|
|
|
197
|
+
## FAQ
|
|
198
|
+
|
|
199
|
+
The most common questions and their troubleshooting paths; for the full set of design boundaries see "Known Limitations" above, and environment-class failures (git missing / disk full / permission denied / …) are explained in the settings card's "Recent errors".
|
|
200
|
+
|
|
201
|
+
**No "↶ Recall" button next to a message?** Check in order of likelihood: (1) the DSH process wasn't restarted after installing — restart it and hard-refresh (Ctrl+Shift+R); (2) git CLI isn't on PATH — a notice shows at the top of the page; (3) the message predates the plugin — snapshots are only created when a message is sent; (4) snapshots are disabled for that workspace — it sits under a directory-form exclusion (e.g. opened inside `dist/`) or exceeded the `maxWorkspaceBytes` / `scanBudgetMs` guardrails, in which case recent messages carry a recovery notice (raise the limit or extend the exclusion list and it recovers automatically).
|
|
202
|
+
|
|
203
|
+
**Some files weren't restored by a recall?** They weren't in the snapshot: larger than `maxFileBytes` (8MB by default), matched by the exclusion list, or inside a nested git repository / a win32 reparse point (see "Known Limitations"). The confirmation panel's file list before a recall is the exact rollback scope — trust it.
|
|
204
|
+
|
|
205
|
+
**Snapshots eating too much disk?** Open "Settings → Plugin Config → Recall Plugin → Snapshot Manager" to see per-workspace usage, run "gc now", or delete by workspace / session / single snapshot; longer-term, lower `maxSnapshotsPerWorkspace`, set `retentionDays`, or add big directories to the exclusion list.
|
|
206
|
+
|
|
207
|
+
**How do I delete all snapshot data for good?** After uninstalling the plugin, manually delete `dsh-recall-snapshots/` under home (`$DSH_HOME/dsh-recall-snapshots/`, or `~/.dsh/dsh-recall-snapshots/` when unset); for workspaces that fell back to in-project storage, also delete `.dsh-recall-snapshots/` inside the project.
|
|
208
|
+
|
|
209
|
+
**Can the session's first message be recalled or edited?** Files yes, conversation no — fork needs an earlier turn boundary; "Edit and resend" on the first message is refused outright (the original message is still there, so resending would duplicate it).
|
|
210
|
+
|
|
186
211
|
## How It Works
|
|
187
212
|
|
|
188
213
|
When each user message is sent (before the agent touches any files), the workspace is snapshotted into an independent shadow git repository; on recall, a "pre-rollback" safety snapshot is taken first, then files are restored via `git archive` and the conversation is rewound through DSH's official `sessions.fork` mechanism. Binary- and line-ending-safe, and your project's own git state is never touched.
|
|
@@ -292,9 +317,9 @@ All source lives in `src/`: the host (Node ESM TypeScript) in `src/host/`, the b
|
|
|
292
317
|
|
|
293
318
|
### Tests
|
|
294
319
|
|
|
295
|
-
- `npm test`: pure-logic unit tests (vitest,
|
|
296
|
-
- `npm run test:client`: client component tests (vitest + jsdom,
|
|
297
|
-
- `npm run test:probe`: official-API field probes (2 files /
|
|
320
|
+
- `npm test`: pure-logic unit tests (vitest, 41 files / 524 cases, no DSH dependency, runs identically in CI and locally) — config parsing, snapshot parsers, rescue orchestration, error classification, the disk-format guard, the recall intent journal, recall terminal-event payload assembly, i18n dictionaries plus a missing-key scan, script-template same-name-export contract, shell execution dual-channel split and failure grading, settings bridge across both generations, artifact-root and snapshot-volume guardrails, recall-scope dispatch, client pure functions, published-package layout, snapshot index persistence, storage caps and retention, etc.;
|
|
321
|
+
- `npm run test:client`: client component tests (vitest + jsdom, 9 files / 121 cases, runs in CI) — the recall node main chain (preview→execute→fork→refill) and terminal-event reporting, the editor-card submit chain and official-content projection, the version-family registry and pager, snapshot-manager tree and delete flows, config/exclude card error paths, the logger switch matrix, and the zh/en rendering chains; assertions pin behaviour and structure only (className / aria / request payloads), never literal copy;
|
|
322
|
+
- `npm run test:probe`: official-API field probes (2 files / 58 cases, requires a local dsh installation; **must run after any dsh upgrade**) — pins fields like `renderMessageImages`/`node`/`cwd`, `atSeq`/`increaseTitle` and the fork-cut anchors of `sessions.fork`, `listSessions` record shape, `AgentRegistry`, the shell execution seam (`execute`/`ShellExecution.result`), the settings surface (`SettingsForms`/profile entry id/volatile gate), the composer write chain (`setDraft`/`addAttachments`/`submit`/`snapshot`/`createDrafts`) plus `loader/volatile-update` and the `Fiber.entry` shape, and goes red on violation;
|
|
298
323
|
- `npm run verify:host`: assembly gate (requires a local dsh installation) — boots the plugin with a real cordis context in two passes (legacy settings stub and a modern-face-only stub), asserting inject declarations, endpoint registration, Config schema, teardown cleanliness and settings-face dispatch, catching assembly regressions before release;
|
|
299
324
|
- `npm run typecheck`: full `tsc --noEmit` type check (includes the `tests/types` compile-time contract assertions; CI runs it before the unit suite);
|
|
300
325
|
- `npm run build`: full host+client build (mandatory after any `src/` change); `npm run check:dsh`: dsh version inspection (pre-release); `npm run check:upgrade`: one-shot dsh-upgrade verification gate (chains check:dsh + test:probe + verify:host; mandatory after a dsh upgrade).
|
package/README.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-recall-plugin)
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-recall-plugin)
|
|
7
7
|

|
|
8
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.2-alpha.2)
|
|
9
9
|
|
|
10
10
|
在任意一条你发过的消息下方点「↶ 撤回」——**工作区文件和对话历史一起回到那条消息发出之前的状态**。
|
|
11
11
|
|
|
12
|
-
每条消息发送时,工作区先被快照进一个独立的影子 git 仓库(不碰项目自身的 git);撤回时按快照恢复文件,并经 DSH 官方 `sessions.fork` 把对话切回该消息之前的 turn
|
|
12
|
+
每条消息发送时,工作区先被快照进一个独立的影子 git 仓库(不碰项目自身的 git);撤回时按快照恢复文件,并经 DSH 官方 `sessions.fork` 把对话切回该消息之前的 turn 边界,原会话归档保留、随时可找回。被撤回消息的文本与附件自动放回输入框,改完即可重发;也可以点消息旁的「✎ 编辑」就地改完直接重发(不动文件),同一段对话的多个版本经 `‹ X/N ›` 翻页器来回切换。快照存储默认落在 `$DSH_HOME` 下。最主要的边界:快照只在**消息发送时**创建,插件启用前的历史消息没有快照、不显示撤回按钮。
|
|
13
13
|
|
|
14
14
|
[更新日志](CHANGELOG.md)
|
|
15
15
|
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
- [界面预览](#界面预览)
|
|
19
19
|
- [功能亮点](#功能亮点)
|
|
20
|
+
- [技术栈与依赖](#技术栈与依赖)
|
|
20
21
|
- [已知限制](#已知限制)
|
|
21
22
|
- [安装](#安装)
|
|
22
23
|
- [使用](#使用)
|
|
23
24
|
- [配置项](#配置项)
|
|
24
25
|
- [快照维护与清理](#快照维护与清理)
|
|
26
|
+
- [常见问题](#常见问题)
|
|
25
27
|
- [工作原理](#工作原理)
|
|
26
28
|
- [事件契约(供同宿主插件监听)](#事件契约供同宿主插件监听)
|
|
27
29
|
- [参与贡献](#参与贡献)
|
|
@@ -52,6 +54,15 @@
|
|
|
52
54
|
- **磁盘友好、自动维护**:快照走 git delta 增量压缩,大文件自动跳过(阈值可配);定期无损 `git gc`、会话删除联动清理、按条数与保留天数自动清理;设置页提供**工作区 → 会话 → 快照**三级树形管理,支持搜索与分级删除。
|
|
53
55
|
- **失败不静默、能自愈**(自愈 2.1+):失败按根因分类(git 缺失 / 磁盘满 / 无权限 / 锁冲突 / 目录冲突)给出可行动提示,同类故障 10 分钟只打扰一次,原因进设置卡片「最近错误」;自动清理残骸、连续 3 次失败指数退避、多实例按心跳互让;无法索引的路径跳过并告知,不中断整条快照(撤回时也不触碰它们)。
|
|
54
56
|
|
|
57
|
+
## 技术栈与依赖
|
|
58
|
+
|
|
59
|
+
本插件是 DSH bundle 形态:host 半跑在 DSH 进程内(Node ESM),client 半跑在浏览器会话页内,两侧同出一个 npm 包,包本体零运行时 dependencies。
|
|
60
|
+
|
|
61
|
+
- **运行时**:Node.js ≥ 20、git CLI;Windows 走 PowerShell 5.1/7,Linux/macOS 走 bash(按平台自动分叉)。
|
|
62
|
+
- **语言与构建**:TypeScript + ESM;`npm run build` 经 esbuild 产出 `lib/`(host 逐文件转译、client 打包为单文件),产物随源码提交。
|
|
63
|
+
- **测试与门禁**:vitest(纯逻辑单测、jsdom client 组件测试、官方 API 字段探针)+ `tsc --noEmit` 类型门禁 + 装配门禁脚本,详见「参与贡献 → 测试」。
|
|
64
|
+
- **宿主依赖**(peerDependencies,随 DSH 安装提供,无需单独安装):`@deepseek-ai/cordis` 4、`@deepseek-ai/dsh-*`(host-webserver / sandbox-policy / session / session-query / settings / shell)、`@deepseek-ai/schemastery` 3.18;`react` 18 为可选 peer(client 由宿主注入)。
|
|
65
|
+
|
|
55
66
|
## 已知限制
|
|
56
67
|
|
|
57
68
|
设计上接受的边界与尚未覆盖的极端情形,使用前值得先确认。
|
|
@@ -73,7 +84,7 @@
|
|
|
73
84
|
|
|
74
85
|
- git CLI:未安装时撤回按钮不出现(页面顶部会提示安装 git),不影响 DSH 运行。
|
|
75
86
|
- shell:Windows 上 PowerShell 5.1 / 7 均可;Linux/macOS 需 bash。
|
|
76
|
-
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.
|
|
87
|
+
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.2-alpha.2`;已核验 minor 线内的后续版本自动放行,未核验的新 minor 线会被启动期兼容门禁拦截(开窗机制见下方折叠块)。
|
|
77
88
|
- **0.1.7-alpha.1 是破坏性版本**(shell 执行与 settings 面接口更换),插件内置双分支共存适配:同一发布同时兼容 0.1.2–0.1.6 各线与 0.1.7+,老版本 DSH 上行为不变。
|
|
78
89
|
- `0.1.1-rc.2` 及更早不支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
|
|
79
90
|
|
|
@@ -83,10 +94,10 @@
|
|
|
83
94
|
peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases` 声明一致:
|
|
84
95
|
|
|
85
96
|
```
|
|
86
|
-
>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0
|
|
97
|
+
>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0
|
|
87
98
|
```
|
|
88
99
|
|
|
89
|
-
每条线以首个核验版本为下限、上界开到下一 minor(0.2.1
|
|
100
|
+
每条线以首个核验版本为下限、上界开到下一 minor(0.2.1 与 0.2.2 段上限均放宽到 `<0.3.0`,0.2 全线正式版自动放行),同线内后续 prerelease/正式版自动放行、无需改 peer 声明。0.2.0 / 0.2.1 / 0.2.2 每条线都必须显式开窗:npm 的 prerelease 门槛要求同 tuple 且带 prerelease 的比较器,既有 `<0.1.8` 段放行不了 `0.2.0-rc.1`、`<0.2.1` 段放行不了 `0.2.1-alpha.1`,`>=0.2.1-alpha.1 <0.3.0` 同样放行不了 `0.2.2-alpha.2`(首个 0.2.2 核验版本,2026-10-28)。
|
|
90
101
|
|
|
91
102
|
</details>
|
|
92
103
|
|
|
@@ -142,8 +153,8 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
142
153
|
| `gcSnaps` | 50 | 每积累多少条快照触发一次 `git gc`(env `DSH_RECALL_GC_SNAPS` 可强制覆盖) |
|
|
143
154
|
| `gcHours` | 24 | 距上次 gc 超过多少小时触发(与条数先到先触发;env `DSH_RECALL_GC_HOURS`) |
|
|
144
155
|
| `maxFileBytes` | 8388608(8MB) | 超过该大小的文件不进快照、不被回退触碰 |
|
|
145
|
-
| `maxWorkspaceBytes` | 268435456(256MB) |
|
|
146
|
-
| `scanBudgetMs` | 10000(10 秒) |
|
|
156
|
+
| `maxWorkspaceBytes` | 268435456(256MB) | 单工作区可纳入快照的文件总量上限,口径与 `git add` 一致(`.gitignore`、`.git/info/exclude`、全局 excludesFile 都算数)。超过时**停用该工作区快照**(不截断——截断快照回退时会误删未纳入的文件),并在最近消息上提示;调大该值或补充排除表后自动恢复;0 = 不限制 |
|
|
157
|
+
| `scanBudgetMs` | 10000(10 秒) | 快照前统计纳入总量的时间预算。超过时停用本次快照并提示(防病态大目录拖死启动);0 = 不限制 |
|
|
147
158
|
| `maxSnapshotsPerWorkspace` | 500 | 每个工作区保留的最大快照数,超限自动删除最旧的;0 = 不限制 |
|
|
148
159
|
| `retentionDays` | 0 | 按天数保留快照,超期自动删除;0 = 不启用(与条数上限各自独立生效) |
|
|
149
160
|
| `baseExcludes` | `.git`、`node_modules/`、`.dsh-recall-snapshots/`、`dsh-recall-snapshots/`、`target/`、`dist/`、`build/`、`out/`、`coverage/`、`.next/`、`.nuxt/`、`.output/`、`.cache/`、`.gradle/`、`*.exe`、`*.dll`、`*.pdb`、`*.so`、`*.dylib`、`*.msi`、`*.zip`、`*.7z`、`*.rar`、`*.tar`、`*.tar.gz`、`*.iso` | 基础排除表(gitignore 语法,优先级低于 exclude.txt);被排除的大目录整棵子树不进快照扫描。其中「目录形态」项(如 `target/`)另有一层含义:**工作区根自身的路径段命中时,该工作区不启用快照**——在构建产物目录里开会话时排除表本来就管不到它自己(模式是相对该 root 的),这类目录也没有回退价值;删掉对应项即恢复 |
|
|
@@ -180,6 +191,20 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
180
191
|
对所有项目生效(home 不可写而降级到项目内存储时,该工作区有独立的排除配置,设置页会分卡片列出)。新增排除只影响之后的快照;**回退到更早的快照时,当时尚未排除的文件仍会被恢复**(回到当时的状态,这正是回退语义)。想彻底清掉已进快照的目录,可手动删除 home 下 `dsh-recall-snapshots/` 里对应项目的哈希目录。
|
|
181
192
|
- **树形快照管理**:打开「**设置 → 插件配置 → 撤回插件 → 快照管理**」可看到三级树——工作区(文件夹名)→ 会话(会话标题,撤回链聚成版本家族)→ 快照(时间 + 消息内容摘要,悬停看完整内容)。支持搜索与「加载更多」,工作区与会话节点可展开/折叠;每一级右侧都有删除按钮,删除前二次确认——删工作区 = 清掉该工作区全部快照,删会话 = 清掉该会话全部快照,删叶子 = 只删那一条;顶部另有带确认的「全部删除」。
|
|
182
193
|
|
|
194
|
+
## 常见问题
|
|
195
|
+
|
|
196
|
+
使用中最常遇到的几类问题与排查路径;更完整的设计边界见上文「已知限制」,环境类失败(git 缺失 / 磁盘满 / 无权限等)的原因在设置卡片「最近错误」里可查。
|
|
197
|
+
|
|
198
|
+
**消息旁没有「↶ 撤回」按钮?** 按概率排查:① 装完没重启 DSH 进程——重启后硬刷新页面(Ctrl+Shift+R);② git CLI 不在 PATH——页面顶部会有安装提示;③ 该消息发送于插件启用之前——快照只在消息发送时创建,历史消息无快照;④ 该工作区被停用快照——工作区开在 `dist/`、`target/` 这类命中基础排除表目录形态的位置,或超出 `maxWorkspaceBytes` / `scanBudgetMs` 护栏,最近消息上会给出恢复提示(调大上限或补充排除表后自动恢复)。
|
|
199
|
+
|
|
200
|
+
**撤回后个别文件没有恢复?** 该文件不在快照里:超过 `maxFileBytes`(默认 8MB)、命中排除表,或位于嵌套 git 仓库 / win32 目录重解析点内(见「已知限制」)。撤回前确认面板的清单就是实际回退范围,以它为准。
|
|
201
|
+
|
|
202
|
+
**快照占用磁盘太多?** 打开「设置 → 插件配置 → 撤回插件 → 快照管理」查看各工作区占用,可「立即 gc」或按工作区 / 会话 / 单条分级删除;长期可压低 `maxSnapshotsPerWorkspace`、设置 `retentionDays`,或把大目录补进排除表。
|
|
203
|
+
|
|
204
|
+
**如何彻底删除全部快照数据?** 卸载插件后手动删除 home 下 `dsh-recall-snapshots/`(即 `$DSH_HOME/dsh-recall-snapshots/`,未设置时为 `~/.dsh/dsh-recall-snapshots/`);曾降级存储的工作区另删项目内 `.dsh-recall-snapshots/`。
|
|
205
|
+
|
|
206
|
+
**会话第一条消息能撤回或编辑吗?** 文件可以回退,对话不能——fork 需要该消息之前的 turn 边界;「编辑并重发」对首条消息会被明确拒绝(原消息还在,补发即成重复消息)。
|
|
207
|
+
|
|
183
208
|
## 工作原理
|
|
184
209
|
|
|
185
210
|
每条用户消息发送时(agent 动文件之前),工作区被快照进一个独立的影子 git 仓库;撤回时先打「回退前」安全快照、再用 `git archive` 恢复文件、通过 DSH 官方 `sessions.fork` 机制把会话切到该消息之前。二进制与换行符安全,全程不触碰项目自身的 git 状态。
|
|
@@ -289,9 +314,9 @@ pnpm install
|
|
|
289
314
|
|
|
290
315
|
### 测试
|
|
291
316
|
|
|
292
|
-
- `npm test`:纯逻辑单测(vitest,
|
|
293
|
-
- `npm run test:client`:client 组件测试(vitest + jsdom,
|
|
294
|
-
- `npm run test:probe`:官方 API 字段探针(2 个文件
|
|
317
|
+
- `npm test`:纯逻辑单测(vitest,41 个文件 524 例,无 DSH 依赖,CI 与本地同跑)——配置解析、快照解析器、救援编排、错误分类、磁盘格式守卫、操作意图 journal、撤回终态事件上报组装、i18n 词典与 key 漏配扫描、脚本模板同名导出契约、执行通道双分支与失败分级、settings 双代桥接、产物 root 与快照总量护栏、撤回 scope 分流、客户端纯函数、发布包内容布局、快照索引持久化、存储上限与保留天数等;
|
|
318
|
+
- `npm run test:client`:client 组件测试(vitest + jsdom,9 个文件 121 例,CI 同跑)——撤回节点主链(preview→execute→fork→回填)与终态事件上报、编辑卡提交链与官方内容投影、版本家族登记与翻页、快照管理树与删除流、配置/排除卡片错误路径、logger 开关矩阵、zh/en 双语渲染链;断言只锁行为与结构(className / aria / 请求载荷),不锁文案字面量;
|
|
319
|
+
- `npm run test:probe`:官方 API 字段探针(2 个文件 58 例,依赖本机 dsh 安装;dsh 升级后本地必跑)——钉住 `renderMessageImages`/`node`/`cwd`、`sessions.fork` 的 `atSeq`/`increaseTitle` 与切点锚点、`listSessions` 记录结构、`AgentRegistry`、shell 执行接缝(`execute`/`ShellExecution.result`)、settings 面(`SettingsForms`/profile 条目 id/volatile 门槛)、输入区写入链(`setDraft`/`addAttachments`/`submit`/`snapshot`/`createDrafts`)与 `loader/volatile-update`/`Fiber.entry` 形状等字段,违反即红;
|
|
295
320
|
- `npm run verify:host`:装配门禁(依赖本机 dsh 安装)——用真实 cordis 起插件跑两个 pass(旧面桩 + 只给新面的桩),断言 inject 声明、端点注册、Config schema、卸载清理与 settings 面分派,装配回归发版前即可拦截;
|
|
296
321
|
- `npm run typecheck`:`tsc --noEmit` 全量类型检查(含 `tests/types` 编译期契约断言,CI 中置于单测之前);
|
|
297
322
|
- `npm run build`:host+client 全量打包(改任何 `src/` 后必跑);`npm run check:dsh`:dsh 版本巡检(发布前);`npm run check:upgrade`:dsh 升级一键核验门禁(串联 check:dsh + test:probe + verify:host,dsh 升级后必跑)。
|
package/lib/client.js
CHANGED
|
@@ -587,10 +587,10 @@
|
|
|
587
587
|
"config.maxFileBytes.hint": "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0",
|
|
588
588
|
"config.maxFileBytes.suffix": "MB",
|
|
589
589
|
"config.maxWorkspaceBytes.label": "\u5DE5\u4F5C\u533A\u603B\u91CF\u4E0A\u9650",
|
|
590
|
-
"config.maxWorkspaceBytes.hint": "\u5355\u5DE5\u4F5C\u533A\u53EF\u7EB3\u5165\u5FEB\u7167\u7684\u6587\u4EF6\u603B\u91CF\uFF0C\u8D85\u8FC7\u5373\u505C\u7528\u8BE5\u5DE5\u4F5C\u533A\u5FEB\u7167\uFF08\u4E0D\u622A\u65AD\uFF0C\u907F\u514D\u56DE\u9000\u8BEF\u5220\uFF09\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236",
|
|
590
|
+
"config.maxWorkspaceBytes.hint": "\u5355\u5DE5\u4F5C\u533A\u53EF\u7EB3\u5165\u5FEB\u7167\u7684\u6587\u4EF6\u603B\u91CF\uFF0C\u53E3\u5F84\u4E0E git add \u4E00\u81F4\uFF08\u542B .gitignore\uFF09\uFF0C\u8D85\u8FC7\u5373\u505C\u7528\u8BE5\u5DE5\u4F5C\u533A\u5FEB\u7167\uFF08\u4E0D\u622A\u65AD\uFF0C\u907F\u514D\u56DE\u9000\u8BEF\u5220\uFF09\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236",
|
|
591
591
|
"config.maxWorkspaceBytes.suffix": "MB",
|
|
592
592
|
"config.scanBudgetMs.label": "\u626B\u63CF\u65F6\u95F4\u9884\u7B97",
|
|
593
|
-
"config.scanBudgetMs.hint": "\u5FEB\u7167\u524D\
|
|
593
|
+
"config.scanBudgetMs.hint": "\u5FEB\u7167\u524D\u7EDF\u8BA1\u7EB3\u5165\u603B\u91CF\u8D85\u8FC7\u8BE5\u65F6\u957F\u5373\u505C\u7528\u672C\u6B21\u5FEB\u7167\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236",
|
|
594
594
|
"config.scanBudgetMs.suffix": "\u79D2",
|
|
595
595
|
"config.maxSnapshotsPerWorkspace.label": "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650",
|
|
596
596
|
"config.maxSnapshotsPerWorkspace.hint": "\u6BCF\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236",
|
|
@@ -841,10 +841,10 @@
|
|
|
841
841
|
"config.maxFileBytes.hint": "Files larger than this are not snapshotted and never touched by rollback",
|
|
842
842
|
"config.maxFileBytes.suffix": "MB",
|
|
843
843
|
"config.maxWorkspaceBytes.label": "Workspace total limit",
|
|
844
|
-
"config.maxWorkspaceBytes.hint": "Total snapshot-eligible file size per workspace; beyond it snapshots are disabled for that workspace (no truncation, which would make rollback delete skipped files); 0 means unlimited",
|
|
844
|
+
"config.maxWorkspaceBytes.hint": "Total snapshot-eligible file size per workspace, measured the same way as git add (so .gitignore applies); beyond it snapshots are disabled for that workspace (no truncation, which would make rollback delete skipped files); 0 means unlimited",
|
|
845
845
|
"config.maxWorkspaceBytes.suffix": "MB",
|
|
846
846
|
"config.scanBudgetMs.label": "Scan time budget",
|
|
847
|
-
"config.scanBudgetMs.hint": "Skip this snapshot when the
|
|
847
|
+
"config.scanBudgetMs.hint": "Skip this snapshot when measuring the snapshot-eligible total exceeds this duration; 0 means unlimited",
|
|
848
848
|
"config.scanBudgetMs.suffix": "s",
|
|
849
849
|
"config.maxSnapshotsPerWorkspace.label": "Snapshot count limit",
|
|
850
850
|
"config.maxSnapshotsPerWorkspace.hint": "Maximum snapshots kept per workspace; the oldest are deleted beyond it; 0 means unlimited",
|
|
@@ -1695,7 +1695,7 @@
|
|
|
1695
1695
|
attachStarted = startAttachDrafts(conversation, shell, targetSessionId, attachmentFiles, () => {
|
|
1696
1696
|
attachDone = true;
|
|
1697
1697
|
submitIfReady();
|
|
1698
|
-
});
|
|
1698
|
+
}, opts === void 0 || opts.submit !== true);
|
|
1699
1699
|
}
|
|
1700
1700
|
}
|
|
1701
1701
|
}
|
|
@@ -1732,7 +1732,7 @@
|
|
|
1732
1732
|
};
|
|
1733
1733
|
setTimeout(tick, 0);
|
|
1734
1734
|
}
|
|
1735
|
-
function startAttachDrafts(conversation, shell, sessionId, files, onDone) {
|
|
1735
|
+
function startAttachDrafts(conversation, shell, sessionId, files, onDone, eagerUpload = true) {
|
|
1736
1736
|
if (typeof conversation.createDrafts !== "function") return false;
|
|
1737
1737
|
const actions = shell.actions;
|
|
1738
1738
|
let add = null;
|
|
@@ -1749,7 +1749,12 @@
|
|
|
1749
1749
|
}
|
|
1750
1750
|
if (!Array.isArray(list) || list.length === 0) return;
|
|
1751
1751
|
try {
|
|
1752
|
-
|
|
1752
|
+
let drafts;
|
|
1753
|
+
try {
|
|
1754
|
+
drafts = conversation.createDrafts ? conversation.createDrafts(sessionId, list, eagerUpload) : [];
|
|
1755
|
+
} catch (e) {
|
|
1756
|
+
drafts = conversation.createDrafts ? conversation.createDrafts(sessionId, list) : [];
|
|
1757
|
+
}
|
|
1753
1758
|
const descriptors = Array.isArray(drafts) ? drafts : [];
|
|
1754
1759
|
const ids = descriptors.map((draft) => draft && draft.id).filter((id) => typeof id === "string" && id !== "");
|
|
1755
1760
|
if (ids.length === 0) return;
|
|
@@ -1763,20 +1768,45 @@
|
|
|
1763
1768
|
})();
|
|
1764
1769
|
return true;
|
|
1765
1770
|
}
|
|
1771
|
+
let attachmentShape = null;
|
|
1772
|
+
async function readAttachmentBytes(read, ref) {
|
|
1773
|
+
const refArg = ref.attachment && typeof ref.attachment === "object" ? ref.attachment : null;
|
|
1774
|
+
const idArg = typeof ref.attachmentId === "string" && ref.attachmentId !== "" ? ref.attachmentId : null;
|
|
1775
|
+
const attempts = [];
|
|
1776
|
+
if (attachmentShape === "id") {
|
|
1777
|
+
if (idArg !== null) attempts.push({ shape: "id", arg: idArg });
|
|
1778
|
+
if (refArg !== null) attempts.push({ shape: "ref", arg: refArg });
|
|
1779
|
+
} else {
|
|
1780
|
+
if (refArg !== null) attempts.push({ shape: "ref", arg: refArg });
|
|
1781
|
+
if (idArg !== null) attempts.push({ shape: "id", arg: idArg });
|
|
1782
|
+
}
|
|
1783
|
+
for (const attempt of attempts) {
|
|
1784
|
+
try {
|
|
1785
|
+
const result = await read(attempt.arg);
|
|
1786
|
+
if (result && result.ok === true && result.value && result.value.data !== void 0 && result.value.data !== null) {
|
|
1787
|
+
attachmentShape = attempt.shape;
|
|
1788
|
+
return { attachment: result.value.attachment, data: result.value.data };
|
|
1789
|
+
}
|
|
1790
|
+
} catch (e) {
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1766
1795
|
function preloadAttachmentFiles(sourceSessionId, refs) {
|
|
1767
1796
|
return (async () => {
|
|
1768
1797
|
const sessions = ctx.sessions;
|
|
1769
1798
|
const binding = sessions && typeof sessions.binding === "function" ? sessions.binding(sourceSessionId) : void 0;
|
|
1770
1799
|
const session = binding && binding.session;
|
|
1771
1800
|
if (!session || typeof session.readAttachment !== "function") return [];
|
|
1801
|
+
const read = session.readAttachment.bind(session);
|
|
1772
1802
|
const out = [];
|
|
1773
1803
|
for (const ref of refs) {
|
|
1774
1804
|
try {
|
|
1775
|
-
const
|
|
1776
|
-
if (!
|
|
1777
|
-
const raw =
|
|
1805
|
+
const value = await readAttachmentBytes(read, ref);
|
|
1806
|
+
if (!value) continue;
|
|
1807
|
+
const raw = value.data;
|
|
1778
1808
|
const bytes = raw instanceof Uint8Array ? raw : Uint8Array.from(raw);
|
|
1779
|
-
const meta =
|
|
1809
|
+
const meta = value.attachment;
|
|
1780
1810
|
const mediaType = meta && typeof meta.mediaType === "string" && meta.mediaType !== "" ? meta.mediaType : "application/octet-stream";
|
|
1781
1811
|
const name = ref.name || (meta && typeof meta.name === "string" && meta.name !== "" ? meta.name : defaultAttachmentName(mediaType, out.length));
|
|
1782
1812
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
@@ -1859,7 +1889,7 @@
|
|
|
1859
1889
|
if (!block || block.type !== "image" && block.type !== "file") continue;
|
|
1860
1890
|
const ref = block.attachment;
|
|
1861
1891
|
if (!ref || typeof ref.attachmentId !== "string" || ref.attachmentId === "") continue;
|
|
1862
|
-
const entry = { attachmentId: ref.attachmentId, kind: block.type };
|
|
1892
|
+
const entry = { attachmentId: ref.attachmentId, kind: block.type, attachment: block.attachment };
|
|
1863
1893
|
if (typeof ref.name === "string" && ref.name !== "") entry.name = ref.name;
|
|
1864
1894
|
refs.push(entry);
|
|
1865
1895
|
}
|
package/lib/config.js
CHANGED
|
@@ -38,8 +38,8 @@ const Config = Schema.object({
|
|
|
38
38
|
// issue #24:单文件上限挡不住「大量小文件」——下载目录 321 个 ≤100MB 文件
|
|
39
39
|
// 合计 1.05GB 全部进快照。总量超限时**整体停用该工作区快照**而非截断:
|
|
40
40
|
// 截断快照回退时,未纳入的文件会命中回退脚本的「当前有、目标无」删除侧。
|
|
41
|
-
maxWorkspaceBytes: withVolatile(Schema.number().default(268435456).description("\u5355\u5DE5\u4F5C\u533A\u53EF\u7EB3\u5165\u5FEB\u7167\u7684\u6587\u4EF6\u603B\u91CF\u4E0A\u9650\uFF0C\u8D85\u8FC7\u5373\u505C\u7528\u8BE5\u5DE5\u4F5C\u533A\u5FEB\u7167\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236")),
|
|
42
|
-
scanBudgetMs: withVolatile(Schema.number().default(1e4).description("\u5FEB\u7167\u524D\
|
|
41
|
+
maxWorkspaceBytes: withVolatile(Schema.number().default(268435456).description("\u5355\u5DE5\u4F5C\u533A\u53EF\u7EB3\u5165\u5FEB\u7167\u7684\u6587\u4EF6\u603B\u91CF\u4E0A\u9650\uFF0C\u53E3\u5F84\u4E0E git add \u4E00\u81F4\uFF08\u542B .gitignore\uFF09\uFF0C\u8D85\u8FC7\u5373\u505C\u7528\u8BE5\u5DE5\u4F5C\u533A\u5FEB\u7167\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236")),
|
|
42
|
+
scanBudgetMs: withVolatile(Schema.number().default(1e4).description("\u5FEB\u7167\u524D\u7EDF\u8BA1\u7EB3\u5165\u603B\u91CF\u7684\u65F6\u95F4\u9884\u7B97\uFF0C\u8D85\u65F6\u5373\u505C\u7528\u672C\u6B21\u5FEB\u7167\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236")),
|
|
43
43
|
maxSnapshotsPerWorkspace: withVolatile(Schema.number().default(500).description("\u6BCF\u4E2A\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u5220\u9664\u6700\u65E7\u7684")),
|
|
44
44
|
baseExcludes: withVolatile(Schema.array(Schema.string()).default(BASE_EXCLUDES).description("\u57FA\u7840\u6392\u9664\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E exclude.txt\uFF09")),
|
|
45
45
|
refillDraft: withVolatile(Schema.boolean().default(true).description("\u64A4\u56DE\u540E\u628A\u88AB\u64A4\u56DE\u7684\u6D88\u606F\uFF08\u6587\u672C\u4E0E\u9644\u4EF6\uFF09\u56DE\u586B\u5230\u8F93\u5165\u6846")),
|
package/lib/scripts.posix.js
CHANGED
|
@@ -13,7 +13,9 @@ function dropGitlinksBlock() {
|
|
|
13
13
|
return [
|
|
14
14
|
`"$git" --git-dir="$g" ls-files -z --stage | while IFS= read -r -d '' e; do`,
|
|
15
15
|
' case "$e" in',
|
|
16
|
-
|
|
16
|
+
// --work-tree 不可省:影子仓库是 bare 仓库,裸 --git-dir 下 update-index 会
|
|
17
|
+
// fatal "this operation must be run in a work tree",set -e 直接终止整条快照
|
|
18
|
+
` 160000\\ *) p=\${e#*$'\\t'}; "$git" --literal-pathspecs --git-dir="$g" --work-tree="$root" update-index --force-remove -- "$p" ;;`,
|
|
17
19
|
" esac",
|
|
18
20
|
"done"
|
|
19
21
|
].join("\n");
|
|
@@ -39,7 +41,9 @@ function oversizeBlock(maxBytes) {
|
|
|
39
41
|
dirPruneArgsBlock(),
|
|
40
42
|
'find "$root" "${oversize_prune[@]}" -type f -size +' + String(maxBytes || MAX_FILE_BYTES) + "c -print0 2>/dev/null | while IFS= read -r -d '' f; do",
|
|
41
43
|
` printf '%s\\0' "\${f#"$root"/}"`,
|
|
42
|
-
|
|
44
|
+
// --work-tree 不可省:bare 影子仓库下裸 update-index fatal(被 || true 吞掉
|
|
45
|
+
// 后大文件会静默留在 index 里),maxFileBytes 的承诺会形同虚设
|
|
46
|
+
'done | xargs -0 "$git" --literal-pathspecs --git-dir="$g" --work-tree="$root" update-index --force-remove -- 2>/dev/null || true'
|
|
43
47
|
].join("\n");
|
|
44
48
|
}
|
|
45
49
|
function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
@@ -47,16 +51,27 @@ function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
|
47
51
|
const fileLimit = String(fileMaxBytes || MAX_FILE_BYTES);
|
|
48
52
|
const budgetS = budgetMs > 0 ? String(Math.ceil(budgetMs / 1e3)) : "0";
|
|
49
53
|
return [
|
|
50
|
-
dirPruneArgsBlock(),
|
|
51
54
|
"limit_start=$(date +%s)",
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'
|
|
55
|
+
'limit_tmp="$g/limit-scan.$$"',
|
|
56
|
+
"limit_scan_ok=0",
|
|
57
|
+
// set -e:条件位置豁免,ls-files 失败不终止脚本——fail-open 只放行不判定
|
|
58
|
+
'if "$git" -c core.quotePath=false --git-dir="$g" --work-tree="$root" ls-files -z -co --exclude-standard > "$limit_tmp" 2>/dev/null; then',
|
|
59
|
+
" limit_scan_ok=1",
|
|
60
|
+
"fi",
|
|
61
|
+
'limit_sizes=""',
|
|
62
|
+
'if [ "$limit_scan_ok" = 1 ] && [ -s "$limit_tmp" ]; then',
|
|
63
|
+
// ls-files 输出相对 root 的路径,而脚本 cwd 是宿主工作目录:必须 cd 进去再 stat
|
|
64
|
+
` if find "$root" -maxdepth 0 -printf '' >/dev/null 2>&1; then`,
|
|
65
|
+
` limit_sizes=$(cd "$root" && xargs -0 stat -c '%s %F' -- < "$limit_tmp" 2>/dev/null || true)`,
|
|
66
|
+
" else",
|
|
67
|
+
` limit_sizes=$(cd "$root" && xargs -0 stat -f '%z %HT' -- < "$limit_tmp" 2>/dev/null || true)`,
|
|
68
|
+
" fi",
|
|
56
69
|
"fi",
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
70
|
+
'rm -f "$limit_tmp"',
|
|
71
|
+
// 目录条目(嵌套仓库在 ls-files 里输出 `dir/`)不计入:add 侧同样不纳入它
|
|
72
|
+
// 的内容(gitlink 被剔除 / 无提交时 unable to index);越过单文件上限的也
|
|
73
|
+
// 不计入——oversizeBlock 随后会把它移出 index
|
|
74
|
+
`limit_total=$(printf '%s\\n' "$limit_sizes" | awk -v lim=` + limit + " -v flim=" + fileLimit + ` '{ if (tolower($0) ~ /directory/) next; if ($1 <= flim) { s += $1; if (lim > 0 && s > lim) { print "OVER " s; exit } } } END { if (lim <= 0 || s <= lim) print s }')`,
|
|
60
75
|
"limit_elapsed=$(( $(date +%s) - limit_start ))",
|
|
61
76
|
'case "$limit_total" in',
|
|
62
77
|
' OVER\\ *) echo "SNAP_LIMIT bytes ${limit_total#OVER } ' + limit + '"; exit 0 ;;',
|
|
@@ -86,7 +101,14 @@ function excludeSyncBlock(excludeFile, base) {
|
|
|
86
101
|
'old_exc=$(cat "$exc" 2>/dev/null || true)',
|
|
87
102
|
'if [ "$new_exc" != "$old_exc" ]; then',
|
|
88
103
|
` printf '%s\\n' "$new_exc" > "$exc"`,
|
|
89
|
-
|
|
104
|
+
// 影子仓库是 bare:写 index 必须显式 --work-tree,否则 update-index fatal
|
|
105
|
+
// 并被 || true 吞成「排除对已跟踪条目从未生效」。ensureGitScript 也走本函数
|
|
106
|
+
// 但没有 root(建仓不遍历工作区),那时退回原调用(命中通常为空,fail-open)
|
|
107
|
+
' if [ -n "${root:-}" ]; then',
|
|
108
|
+
' "$git" -c core.quotePath=false --literal-pathspecs --git-dir="$g" ls-files -i -c --exclude-from="$exc" -z 2>/dev/null | xargs -0 "$git" --literal-pathspecs --git-dir="$g" --work-tree="$root" update-index --force-remove -- 2>/dev/null || true',
|
|
109
|
+
" else",
|
|
110
|
+
' "$git" -c core.quotePath=false --literal-pathspecs --git-dir="$g" ls-files -i -c --exclude-from="$exc" -z 2>/dev/null | xargs -0 "$git" --literal-pathspecs --git-dir="$g" update-index --force-remove -- 2>/dev/null || true',
|
|
111
|
+
" fi",
|
|
90
112
|
"fi"
|
|
91
113
|
].join("\n");
|
|
92
114
|
}
|
package/lib/scripts.pwsh.js
CHANGED
|
@@ -13,7 +13,7 @@ function dropGitlinksBlock() {
|
|
|
13
13
|
return [
|
|
14
14
|
"& $git --git-dir=$g ls-files --stage | Where-Object { $_ -like '160000*' } | ForEach-Object {",
|
|
15
15
|
' $p = ($_ -split "`t")[1]',
|
|
16
|
-
" & $git --literal-pathspecs --git-dir=$g update-index --force-remove -- $p",
|
|
16
|
+
" & $git --literal-pathspecs --git-dir=$g --work-tree=$root update-index --force-remove -- $p",
|
|
17
17
|
"}"
|
|
18
18
|
].join("\n");
|
|
19
19
|
}
|
|
@@ -80,7 +80,9 @@ function oversizeBlock(maxBytes) {
|
|
|
80
80
|
"}",
|
|
81
81
|
"for ($i = 0; $i -lt $oversizeRel.Count; $i += 100) {",
|
|
82
82
|
" $batch = $oversizeRel.GetRange($i, [Math]::Min(100, $oversizeRel.Count - $i))",
|
|
83
|
-
|
|
83
|
+
// --work-tree 不可省:bare 影子仓库下裸 update-index 会 fatal,pwsh 不抛
|
|
84
|
+
// → 大文件静默留在 index 里,maxFileBytes 的承诺形同虚设
|
|
85
|
+
" & $git --literal-pathspecs --git-dir=$g --work-tree=$root update-index --force-remove -- $batch",
|
|
84
86
|
"}"
|
|
85
87
|
].join("\n");
|
|
86
88
|
}
|
|
@@ -90,27 +92,31 @@ function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
|
90
92
|
const budget = budgetMs > 0 ? String(budgetMs) : "0";
|
|
91
93
|
return [
|
|
92
94
|
"$limitSw = [Diagnostics.Stopwatch]::StartNew()",
|
|
93
|
-
dirSkipSetBlock("limitSkip"),
|
|
94
|
-
"$limitStack = [System.Collections.Generic.Stack[string]]::new()",
|
|
95
|
-
"$limitStack.Push($root)",
|
|
96
95
|
"$limitBytes = [long]0",
|
|
97
96
|
"$limitHit = ''",
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"
|
|
97
|
+
"$limitRc = 0",
|
|
98
|
+
"$limitFiles = @()",
|
|
99
|
+
// PS 5.1:native stderr 在 EAP=Stop 下会被包成 ErrorRecord 直接抛(pwsh 7
|
|
100
|
+
// 不抛),一条 warning 就能让整条快照挂掉——与下方 add 段同款临时降级
|
|
101
|
+
"$limitEap = $ErrorActionPreference",
|
|
102
|
+
"$ErrorActionPreference = 'Continue'",
|
|
103
|
+
"try {",
|
|
104
|
+
// -z:路径含换行也不分裂(实测 PS 5.1/7 的 @() 都保留 NUL)
|
|
105
|
+
" $limitFiles = @(((& $git -c core.quotePath=false --git-dir=$g --work-tree=$root ls-files -z -co --exclude-standard 2>$null) -join '').Split([char]0))",
|
|
106
|
+
" $limitRc = $LASTEXITCODE",
|
|
107
|
+
"} finally { $ErrorActionPreference = $limitEap }",
|
|
108
|
+
"if ($limitRc -eq 0) {",
|
|
109
|
+
" foreach ($limitRel in $limitFiles) {",
|
|
110
|
+
// 两道守卫吸收两类异常条目:5.1 的 Split 会留尾部空串;目录条目(嵌套仓库
|
|
111
|
+
// 输出 `dir/`)的 Length 为空或抛——两者都不该计入(add 侧同样不纳入)
|
|
112
|
+
" if (-not $limitRel) { continue }",
|
|
113
|
+
" if (" + budget + " -gt 0 -and $limitSw.ElapsedMilliseconds -gt " + budget + ") { $limitHit = 'time'; break }",
|
|
114
|
+
" try { $limitLen = [System.IO.FileInfo]::new($root + '/' + $limitRel).Length } catch { $limitLen = [long]0 }",
|
|
103
115
|
// 口径 = 实际会进快照的字节:超过单文件上限的文件随后被 oversizeBlock
|
|
104
116
|
// 移出 index,不该计入总量(否则一个 300MB 视频就能把干净项目误判超限)
|
|
105
|
-
"
|
|
106
|
-
" if (" + limit + " -gt 0 -and $limitBytes -gt " + limit + ") { $limitHit = 'bytes' }",
|
|
107
|
-
"
|
|
108
|
-
" foreach ($d in $di.EnumerateDirectories()) {",
|
|
109
|
-
" if ($limitSkip.Contains($d.Name)) { continue }",
|
|
110
|
-
" if (($d.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { continue }",
|
|
111
|
-
" $limitStack.Push($d.FullName)",
|
|
112
|
-
" }",
|
|
113
|
-
" } catch {}",
|
|
117
|
+
" if ($limitLen -gt 0 -and $limitLen -le " + fileLimit + ") { $limitBytes += $limitLen }",
|
|
118
|
+
" if (" + limit + " -gt 0 -and $limitBytes -gt " + limit + ") { $limitHit = 'bytes'; break }",
|
|
119
|
+
" }",
|
|
114
120
|
"}",
|
|
115
121
|
"if ($limitHit -eq 'bytes') { Write-Output ('SNAP_LIMIT bytes ' + $limitBytes + ' ' + " + limit + "); exit 0 }",
|
|
116
122
|
"if ($limitHit -eq 'time') { Write-Output ('SNAP_LIMIT time ' + $limitSw.ElapsedMilliseconds + ' ' + " + budget + "); exit 0 }"
|
|
@@ -146,7 +152,14 @@ function excludeSyncBlock(excludeFile, base) {
|
|
|
146
152
|
" } else {",
|
|
147
153
|
" for ($i = 0; $i -lt $hit.Count; $i += 100) {",
|
|
148
154
|
" $batch = @($hit[$i..([Math]::Min($i + 99, $hit.Count - 1))])",
|
|
149
|
-
|
|
155
|
+
// 影子仓库是 bare:写 index 必须显式 --work-tree,否则 update-index fatal。
|
|
156
|
+
// ensureGitScript 也走本函数但没有 $root(建仓不遍历工作区)——那时保留
|
|
157
|
+
// 原样调用,命中列表通常为空,最坏是清理静默跳过(fail-open)
|
|
158
|
+
" if ($root) {",
|
|
159
|
+
" & $git --literal-pathspecs --git-dir=$g --work-tree=$root update-index --force-remove -- $batch",
|
|
160
|
+
" } else {",
|
|
161
|
+
" & $git --literal-pathspecs --git-dir=$g update-index --force-remove -- $batch",
|
|
162
|
+
" }",
|
|
150
163
|
" }",
|
|
151
164
|
" }",
|
|
152
165
|
"}"
|
package/lib/snapshots.js
CHANGED
|
@@ -27,7 +27,7 @@ function bytesText(bytes) {
|
|
|
27
27
|
}
|
|
28
28
|
function buildSnapshotLimitNotice(limit) {
|
|
29
29
|
if (limit.kind === "bytes") {
|
|
30
|
-
return "\u8BE5\u5DE5\u4F5C\u533A\u53EF\u7EB3\u5165\u5FEB\u7167\u7684\u6587\u4EF6\u603B\u91CF\u8D85\u8FC7\u4E0A\u9650\uFF08\u7EA6 " + bytesText(limit.value) + " / " + bytesText(limit.limit) + "\uFF09\uFF0C\u5DF2\u505C\u7528\u5FEB\u7167\uFF1B\u53EF\u8C03\u5927\u300C\u5DE5\u4F5C\u533A\u603B\u91CF\u4E0A\u9650\u300D\u6216\u8865\u5145\u6392\u9664\u8868\u540E\u81EA\u52A8\u6062\u590D";
|
|
30
|
+
return "\u8BE5\u5DE5\u4F5C\u533A\u53EF\u7EB3\u5165\u5FEB\u7167\u7684\u6587\u4EF6\u603B\u91CF\u8D85\u8FC7\u4E0A\u9650\uFF08\u7EA6 " + bytesText(limit.value) + " / " + bytesText(limit.limit) + "\uFF0C\u5DF2\u6309 .gitignore \u4E0E\u6392\u9664\u8868\u8FC7\u6EE4\uFF09\uFF0C\u5DF2\u505C\u7528\u5FEB\u7167\uFF1B\u53EF\u8C03\u5927\u300C\u5DE5\u4F5C\u533A\u603B\u91CF\u4E0A\u9650\u300D\u6216\u8865\u5145\u6392\u9664\u8868\u540E\u81EA\u52A8\u6062\u590D";
|
|
31
31
|
}
|
|
32
32
|
return "\u5FEB\u7167\u524D\u7684\u6587\u4EF6\u626B\u63CF\u8D85\u8FC7\u65F6\u95F4\u9884\u7B97\uFF08\u7EA6 " + Math.max(1, Math.round(limit.value / 1e3)) + " \u79D2 / " + Math.max(1, Math.round(limit.limit / 1e3)) + " \u79D2\uFF09\uFF0C\u5DF2\u505C\u7528\u5FEB\u7167\uFF1B\u53EF\u8C03\u5927\u300C\u626B\u63CF\u65F6\u95F4\u9884\u7B97\u300D\u6216\u8865\u5145\u6392\u9664\u8868\u540E\u81EA\u52A8\u6062\u590D";
|
|
33
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recall-plugin",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "在 DSH 中撤回自己发过的消息:工作区文件与对话历史一起回到那条消息发出之前,消息的文本与附件自动放回输入框,改完即可重发。文件走独立影子 git 快照(不碰项目自身的 git),对话走官方 sessions.fork,原会话归档可找回。",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -74,19 +74,21 @@
|
|
|
74
74
|
"0.2.1-alpha.4": "compatible",
|
|
75
75
|
"0.2.1-alpha.5": "compatible",
|
|
76
76
|
"0.2.1-rc.1": "compatible",
|
|
77
|
-
"0.2.1-rc.2": "compatible"
|
|
77
|
+
"0.2.1-rc.2": "compatible",
|
|
78
|
+
"0.2.1-rc.3": "compatible",
|
|
79
|
+
"0.2.2-alpha.2": "compatible"
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
},
|
|
81
83
|
"peerDependencies": {
|
|
82
|
-
"@deepseek-ai/cordis": "^4.0.1 || >=4.0.5-alpha.1 <4.0.6",
|
|
83
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
84
|
-
"@deepseek-ai/dsh-sandbox-policy": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
85
|
-
"@deepseek-ai/dsh-session": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
86
|
-
"@deepseek-ai/dsh-session-query": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
87
|
-
"@deepseek-ai/dsh-settings": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
88
|
-
"@deepseek-ai/dsh-shell": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
89
|
-
"@deepseek-ai/schemastery": "^3.18.1 || >=3.18.5-alpha.1 <3.18.6",
|
|
84
|
+
"@deepseek-ai/cordis": "^4.0.1 || >=4.0.5-alpha.1 <4.0.6 || >=4.0.6-alpha.1 <4.0.7",
|
|
85
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0",
|
|
86
|
+
"@deepseek-ai/dsh-sandbox-policy": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0",
|
|
87
|
+
"@deepseek-ai/dsh-session": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0",
|
|
88
|
+
"@deepseek-ai/dsh-session-query": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0",
|
|
89
|
+
"@deepseek-ai/dsh-settings": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0",
|
|
90
|
+
"@deepseek-ai/dsh-shell": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0 || >=0.2.2-alpha.1 <0.3.0",
|
|
91
|
+
"@deepseek-ai/schemastery": "^3.18.1 || >=3.18.5-alpha.1 <3.18.6 || >=3.18.6-alpha.1 <3.18.7",
|
|
90
92
|
"react": "^18.2.0"
|
|
91
93
|
},
|
|
92
94
|
"peerDependenciesMeta": {
|