dsh-recall-plugin 2.5.1 → 2.5.3
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 +33 -0
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/lib/client.js +38 -8
- package/lib/scripts.posix.js +56 -18
- package/lib/scripts.pwsh.js +96 -24
- package/lib/snapshots.js +46 -9
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.5.3] - 2026-10-30
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- **大工作区护栏静默失效(issue #29)**:快照前的预算扫描以 `git ls-files` 为准,此前它非零退出会被当作「没超限」——`$HOME` 级工作区(523 GB / 233 万条目)上实测每条消息先写约 10 GB 对象、600 秒后被杀,prune 回收后下一条消息再重复一遍。现在统计失败一律 **fail-closed**:脚本在 `add` 之前输出 `SNAP_SCAN_FAIL` / `SNAP_SCAN_ERR` 并跳过本次快照,原因(git 的 stderr 首行)进「最近错误」,该工作区暂停快照 10 分钟(改配置或 TTL 到期自动重试),不再重付一次全量扫描。
|
|
12
|
+
- **超过 `maxFileBytes` 的文件不再先写对象再移出索引(issue #29)**:此前 `add -A` 会先为超大文件写入 blob,之后才由剔除逻辑移出 index——「小文件少 + 大文件多」的工作区能在预算通过后仍写满磁盘。改为先扫出大文件清单,`add` 带 `--pathspec-from-file` 的 exclude 清单执行(大文件既不进索引也不写对象),add 后仍按清单兜底剔除一次;不支持该选项的旧 git 自动退回裸 `add`(行为同旧版)。
|
|
13
|
+
- **分钟级失败不再免费重试两次(issue #29)**:失败耗时 ≥60 秒视为「昂贵失败」,直接进入退避(5 分钟起、指数递增),不再按「连续 3 次失败」让用户先付三轮分钟级 + GB 级写入。
|
|
14
|
+
|
|
15
|
+
### 变更
|
|
16
|
+
|
|
17
|
+
- **快照 / 预览 / 回退三处 `add` 前统一切 cwd 到工作区根**:`--pathspec-from-file` 的隐含全集会被 cwd 前缀裁切(`:(top)` 救不回来,实测),脚本原先的 cwd 是宿主工作目录,可能正在工作区子目录里。
|
|
18
|
+
|
|
19
|
+
## [2.5.2] - 2026-10-28
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- **编辑重发带图片时「草稿填了却不发」(活体回归发现,仅 0.2.2-alpha.2 触发)**:`createDrafts` 第三参传 `true` 会让图片进输入态即后台上传,而官方 `sendSession` 会**等上传回执**才发 prompt——刚 fork 的子会话上传迟迟不结算时,草稿留在输入框、消息不发、控制台无错(真机复现两次;同场景纯文本编辑重发一次成功)。改为按场景分流:撤回回填(只填不发)仍传 `true` 保留即时上传;编辑重发(自动发送)传 `false`,图片字节随 prompt 内联,发送一步到位。
|
|
24
|
+
|
|
25
|
+
### 兼容
|
|
26
|
+
|
|
27
|
+
- **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 内联),不让整批附件丢失。
|
|
28
|
+
- **`sessions.fork` 停写 `parentSession`**:普通分支改由 header `origin` 判定,插件只读 `origin` 且血缘自存 `lineage.json`,无需改码;fork 子会话不再有被当作子代理的残余风险。
|
|
29
|
+
- **cordis 配置访问统一**:loader 的 ref 来源 `fiber.config` → `fiber.effectiveConfig`,volatile 提交与 `loader/volatile-update` 派发链未变,设置面热更不受影响。
|
|
30
|
+
|
|
31
|
+
### 变更
|
|
32
|
+
|
|
33
|
+
- **`test:probe` 58 条**:新增「附件回读链(B1)」钉住 `readAttachment` 单参形状与 `{ attachment, data }` 返回;`createDrafts` 探针并入第三参(新旧两形态都放行);loader 探针按 `fiber.effectiveConfig` 同步改名并注明两种拼写。
|
|
34
|
+
- **client 单测 121 → 129**:新增 `tests/client/draft-fill.test.ts`(对象口径 / 字符串口径 / 命中代缓存 / 双失败降级 / 仅裸 id 的旧调用方形状 / 回填第三参 true / 自动发送第三参 false / 三参抛错退回两参);`attachmentRefsFromBlocks` 的形状断言并入既有 unit 用例(原始引用随行)。
|
|
35
|
+
|
|
36
|
+
### 文档
|
|
37
|
+
|
|
38
|
+
- 台账同步 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 双语徽章与版本范围。
|
|
39
|
+
|
|
7
40
|
## [2.5.1] - 2026-10-27
|
|
8
41
|
|
|
9
42
|
### 兼容
|
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
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**.
|
|
@@ -87,7 +87,7 @@ Prerequisites:
|
|
|
87
87
|
|
|
88
88
|
- git CLI: without it the recall button won't appear (a notice shows at the top of the page); DSH itself keeps running.
|
|
89
89
|
- Shell: PowerShell 5.1 / 7 on Windows; bash on Linux/macOS.
|
|
90
|
-
- 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).
|
|
91
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.
|
|
92
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.
|
|
93
93
|
|
|
@@ -97,10 +97,10 @@ Prerequisites:
|
|
|
97
97
|
peerDependencies open a window per minor line, consistent with the `dsh.compatibility.dshReleases` declaration:
|
|
98
98
|
|
|
99
99
|
```
|
|
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
|
|
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
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
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).
|
|
104
104
|
|
|
105
105
|
</details>
|
|
106
106
|
|
|
@@ -155,7 +155,7 @@ All options can be edited visually in the "**Settings → Plugin Config → Reca
|
|
|
155
155
|
| --- | --- | --- |
|
|
156
156
|
| `gcSnaps` | 50 | Run `git gc` after this many snapshots accumulate (env `DSH_RECALL_GC_SNAPS` force-overrides) |
|
|
157
157
|
| `gcHours` | 24 | Run gc when this many hours have passed since the last one (whichever trigger fires first; env `DSH_RECALL_GC_HOURS`) |
|
|
158
|
-
| `maxFileBytes` | 8388608 (8MB) | Files larger than this are neither snapshotted nor touched by recalls |
|
|
158
|
+
| `maxFileBytes` | 8388608 (8MB) | Files larger than this are neither snapshotted nor touched by recalls; they are filtered out *before* `git add` from a path list, so no objects are written (older git falls back to add-then-remove) |
|
|
159
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
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 |
|
|
161
161
|
| `maxSnapshotsPerWorkspace` | 500 | Maximum snapshots kept per workspace; oldest pruned beyond the cap. 0 = unlimited |
|
|
@@ -198,7 +198,7 @@ The plugin manages disk usage automatically — no manual housekeeping needed:
|
|
|
198
198
|
|
|
199
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
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/`)
|
|
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/`), exceeded the `maxWorkspaceBytes` / `scanBudgetMs` guardrails, or the pre-snapshot size scan itself failed (`git ls-files` error; the reason is in "Recent errors" and it retries after 10 minutes) — recent messages carry a recovery notice (raise the limit or extend the exclusion list and it recovers automatically).
|
|
202
202
|
|
|
203
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
204
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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
|
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
- git CLI:未安装时撤回按钮不出现(页面顶部会提示安装 git),不影响 DSH 运行。
|
|
86
86
|
- shell:Windows 上 PowerShell 5.1 / 7 均可;Linux/macOS 需 bash。
|
|
87
|
-
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.
|
|
87
|
+
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.2-alpha.2`;已核验 minor 线内的后续版本自动放行,未核验的新 minor 线会被启动期兼容门禁拦截(开窗机制见下方折叠块)。
|
|
88
88
|
- **0.1.7-alpha.1 是破坏性版本**(shell 执行与 settings 面接口更换),插件内置双分支共存适配:同一发布同时兼容 0.1.2–0.1.6 各线与 0.1.7+,老版本 DSH 上行为不变。
|
|
89
89
|
- `0.1.1-rc.2` 及更早不支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
|
|
90
90
|
|
|
@@ -94,10 +94,10 @@
|
|
|
94
94
|
peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases` 声明一致:
|
|
95
95
|
|
|
96
96
|
```
|
|
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
|
|
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
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
每条线以首个核验版本为下限、上界开到下一 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)。
|
|
101
101
|
|
|
102
102
|
</details>
|
|
103
103
|
|
|
@@ -152,7 +152,7 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
152
152
|
| --- | --- | --- |
|
|
153
153
|
| `gcSnaps` | 50 | 每积累多少条快照触发一次 `git gc`(env `DSH_RECALL_GC_SNAPS` 可强制覆盖) |
|
|
154
154
|
| `gcHours` | 24 | 距上次 gc 超过多少小时触发(与条数先到先触发;env `DSH_RECALL_GC_HOURS`) |
|
|
155
|
-
| `maxFileBytes` | 8388608(8MB) |
|
|
155
|
+
| `maxFileBytes` | 8388608(8MB) | 超过该大小的文件不进快照、不被回退触碰;在 `git add` 之前就按清单剔除,不写对象(旧 git 退回「先加后剔」) |
|
|
156
156
|
| `maxWorkspaceBytes` | 268435456(256MB) | 单工作区可纳入快照的文件总量上限,口径与 `git add` 一致(`.gitignore`、`.git/info/exclude`、全局 excludesFile 都算数)。超过时**停用该工作区快照**(不截断——截断快照回退时会误删未纳入的文件),并在最近消息上提示;调大该值或补充排除表后自动恢复;0 = 不限制 |
|
|
157
157
|
| `scanBudgetMs` | 10000(10 秒) | 快照前统计纳入总量的时间预算。超过时停用本次快照并提示(防病态大目录拖死启动);0 = 不限制 |
|
|
158
158
|
| `maxSnapshotsPerWorkspace` | 500 | 每个工作区保留的最大快照数,超限自动删除最旧的;0 = 不限制 |
|
|
@@ -195,7 +195,7 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
195
195
|
|
|
196
196
|
使用中最常遇到的几类问题与排查路径;更完整的设计边界见上文「已知限制」,环境类失败(git 缺失 / 磁盘满 / 无权限等)的原因在设置卡片「最近错误」里可查。
|
|
197
197
|
|
|
198
|
-
**消息旁没有「↶ 撤回」按钮?** 按概率排查:① 装完没重启 DSH 进程——重启后硬刷新页面(Ctrl+Shift+R);② git CLI 不在 PATH——页面顶部会有安装提示;③ 该消息发送于插件启用之前——快照只在消息发送时创建,历史消息无快照;④ 该工作区被停用快照——工作区开在 `dist/`、`target/` 这类命中基础排除表目录形态的位置,或超出 `maxWorkspaceBytes` / `scanBudgetMs`
|
|
198
|
+
**消息旁没有「↶ 撤回」按钮?** 按概率排查:① 装完没重启 DSH 进程——重启后硬刷新页面(Ctrl+Shift+R);② git CLI 不在 PATH——页面顶部会有安装提示;③ 该消息发送于插件启用之前——快照只在消息发送时创建,历史消息无快照;④ 该工作区被停用快照——工作区开在 `dist/`、`target/` 这类命中基础排除表目录形态的位置,或超出 `maxWorkspaceBytes` / `scanBudgetMs` 护栏,或快照前的范围统计本身失败(`git ls-files` 报错,原因见「最近错误」,10 分钟后自动重试);最近消息上会给出恢复提示(调大上限或补充排除表后自动恢复)。
|
|
199
199
|
|
|
200
200
|
**撤回后个别文件没有恢复?** 该文件不在快照里:超过 `maxFileBytes`(默认 8MB)、命中排除表,或位于嵌套 git 仓库 / win32 目录重解析点内(见「已知限制」)。撤回前确认面板的清单就是实际回退范围,以它为准。
|
|
201
201
|
|
package/lib/client.js
CHANGED
|
@@ -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/scripts.posix.js
CHANGED
|
@@ -36,14 +36,34 @@ function dirPruneArgsBlock() {
|
|
|
36
36
|
"if [ ${#oversize_args[@]} -gt 0 ]; then oversize_prune=('(' -type d '(' \"${oversize_args[@]}\" ')' ')' -prune -o); fi"
|
|
37
37
|
].join("\n");
|
|
38
38
|
}
|
|
39
|
-
function
|
|
39
|
+
function oversizeRemoveBlock() {
|
|
40
|
+
return 'xargs -0 "$git" --literal-pathspecs --git-dir="$g" --work-tree="$root" update-index --force-remove -- < "$oversize_r" 2>/dev/null || true';
|
|
41
|
+
}
|
|
42
|
+
function oversizeListBlock(maxBytes) {
|
|
40
43
|
return [
|
|
41
44
|
dirPruneArgsBlock(),
|
|
45
|
+
'oversize_p="$g/oversize-pathspec.$$"',
|
|
46
|
+
'oversize_r="$g/oversize-rel.$$"',
|
|
47
|
+
': > "$oversize_p"',
|
|
48
|
+
': > "$oversize_r"',
|
|
42
49
|
'find "$root" "${oversize_prune[@]}" -type f -size +' + String(maxBytes || MAX_FILE_BYTES) + "c -print0 2>/dev/null | while IFS= read -r -d '' f; do",
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
' oversize_rel="${f#"$root"/}"',
|
|
51
|
+
` printf '%s\\0' ":(top,exclude,literal)$oversize_rel" >> "$oversize_p"`,
|
|
52
|
+
` printf '%s\\0' "$oversize_rel" >> "$oversize_r"`,
|
|
53
|
+
"done",
|
|
54
|
+
oversizeRemoveBlock()
|
|
55
|
+
].join("\n");
|
|
56
|
+
}
|
|
57
|
+
function oversizeAddPrepBlock() {
|
|
58
|
+
return [
|
|
59
|
+
"pa_args=()",
|
|
60
|
+
'if [ -s "$oversize_p" ]; then',
|
|
61
|
+
` if [ -f "$g/pathspec-from-file.ok" ] || "$git" add -h 2>&1 | grep -q -- '--pathspec-from-file'; then`,
|
|
62
|
+
` printf '1\\n' > "$g/pathspec-from-file.ok" 2>/dev/null || true`,
|
|
63
|
+
' pa_args=(--pathspec-from-file="$oversize_p" --pathspec-file-nul)',
|
|
64
|
+
" fi",
|
|
65
|
+
"fi",
|
|
66
|
+
'cd "$root"'
|
|
47
67
|
].join("\n");
|
|
48
68
|
}
|
|
49
69
|
function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
@@ -53,13 +73,22 @@ function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
|
53
73
|
return [
|
|
54
74
|
"limit_start=$(date +%s)",
|
|
55
75
|
'limit_tmp="$g/limit-scan.$$"',
|
|
56
|
-
|
|
57
|
-
// set -e
|
|
58
|
-
'if "$git" -c core.quotePath=false --git-dir="$g" --work-tree="$root" ls-files -z -co --exclude-standard > "$limit_tmp" 2
|
|
59
|
-
"
|
|
76
|
+
'limit_err="$g/limit-scan-err.$$"',
|
|
77
|
+
// set -e:条件位置豁免,失败由 else 分支显式处理(不许穿透到 add)
|
|
78
|
+
'if "$git" -c core.quotePath=false --git-dir="$g" --work-tree="$root" ls-files -z -co --exclude-standard > "$limit_tmp" 2> "$limit_err"; then',
|
|
79
|
+
" :",
|
|
80
|
+
"else",
|
|
81
|
+
" limit_rc=$?",
|
|
82
|
+
// 压平成单行才能随哨兵回传;截 300 字符防一条巨长 stderr 撑爆错误环
|
|
83
|
+
' limit_msg=$(head -c 300 "$limit_err" 2>/dev/null | tr "\\n\\r" " ")',
|
|
84
|
+
' rm -f "$limit_tmp" "$limit_err"',
|
|
85
|
+
` printf 'SNAP_SCAN_FAIL %s\\n' "$limit_rc"`,
|
|
86
|
+
` printf 'SNAP_SCAN_ERR %s\\n' "$limit_msg"`,
|
|
87
|
+
" exit 0",
|
|
60
88
|
"fi",
|
|
89
|
+
'rm -f "$limit_err"',
|
|
61
90
|
'limit_sizes=""',
|
|
62
|
-
'if [
|
|
91
|
+
'if [ -s "$limit_tmp" ]; then',
|
|
63
92
|
// ls-files 输出相对 root 的路径,而脚本 cwd 是宿主工作目录:必须 cd 进去再 stat
|
|
64
93
|
` if find "$root" -maxdepth 0 -printf '' >/dev/null 2>&1; then`,
|
|
65
94
|
` limit_sizes=$(cd "$root" && xargs -0 stat -c '%s %F' -- < "$limit_tmp" 2>/dev/null || true)`,
|
|
@@ -70,7 +99,7 @@ function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
|
70
99
|
'rm -f "$limit_tmp"',
|
|
71
100
|
// 目录条目(嵌套仓库在 ls-files 里输出 `dir/`)不计入:add 侧同样不纳入它
|
|
72
101
|
// 的内容(gitlink 被剔除 / 无提交时 unable to index);越过单文件上限的也
|
|
73
|
-
// 不计入——
|
|
102
|
+
// 不计入——oversizeListBlock 随后会把它移出 index
|
|
74
103
|
`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 }')`,
|
|
75
104
|
"limit_elapsed=$(( $(date +%s) - limit_start ))",
|
|
76
105
|
'case "$limit_total" in',
|
|
@@ -205,14 +234,17 @@ function snapshotScript(root, store, gitExe, messageId, base) {
|
|
|
205
234
|
excludeSyncBlock(store.excludeFile, base),
|
|
206
235
|
// issue #24:总量/预算护栏先于 add——超限时连 add 都不跑
|
|
207
236
|
limitScanBlock(store.maxWorkspaceBytes, store.maxFileBytes, store.scanBudgetMs),
|
|
237
|
+
// issue #29:大文件清单先于 add——add 带 exclude 后不再为它们写对象
|
|
238
|
+
oversizeListBlock(store.maxFileBytes),
|
|
208
239
|
attrsMigrateBlock(),
|
|
240
|
+
oversizeAddPrepBlock(),
|
|
209
241
|
// fail-open add(issue #7 加固,语义见 pwsh 版同款注释):--ignore-errors
|
|
210
242
|
// 下「无法索引的路径」以退出码 1 结束但索引已落盘;≥2 才是真 fatal,
|
|
211
243
|
// 显式退出让 runShell 抛错(set -e 对 add 非零本会终止,但 || rc=$?
|
|
212
244
|
// 捕获后必须自检,否则 tolerated/fatal 无法区分)。stderr 合并进变量
|
|
213
245
|
// 供 fatal 时带回诊断与 SNAP_SKIP 提取。
|
|
214
246
|
"addrc=0",
|
|
215
|
-
'add_log=$("$git" --git-dir="$g" --work-tree="$root" add -A --ignore-errors 2>&1) || addrc=$?',
|
|
247
|
+
'add_log=$("$git" --git-dir="$g" --work-tree="$root" add -A --ignore-errors "${pa_args[@]}" 2>&1) || addrc=$?',
|
|
216
248
|
`if [ "$addrc" -ge 2 ]; then printf '%s\\n' "$add_log" >&2; exit "$addrc"; fi`,
|
|
217
249
|
`printf '%s\\n' "$add_log" | sed -n "s/^error: unable to index file '\\(.*\\)'$/\\1/p" | sort -u | while IFS= read -r sk; do`,
|
|
218
250
|
// 循环体用 if/fi 而非 && 列表:&& 列表条件为假时整条管道退出码为 1,
|
|
@@ -220,7 +252,9 @@ function snapshotScript(root, store, gitExe, messageId, base) {
|
|
|
220
252
|
` if [ -n "$sk" ]; then printf 'SNAP_SKIP %s\\n' "$sk"; fi`,
|
|
221
253
|
"done",
|
|
222
254
|
dropGitlinksBlock(),
|
|
223
|
-
|
|
255
|
+
// 兜底:旧 git 走裸 add 时大文件仍会被重新索引,这里按清单再清一次
|
|
256
|
+
oversizeRemoveBlock(),
|
|
257
|
+
'rm -f "$oversize_p" "$oversize_r"',
|
|
224
258
|
'tree=$("$git" --git-dir="$g" --work-tree="$root" write-tree)',
|
|
225
259
|
'commit=$("$git" --git-dir="$g" -c user.name=dsh-recall -c user.email=recall@dsh.local commit-tree "$tree" -m ' + psq("snapshot " + messageId) + ")",
|
|
226
260
|
'"$git" --git-dir="$g" tag -f ' + psq("snap-" + messageId) + ' "$commit" >/dev/null',
|
|
@@ -237,14 +271,18 @@ function collectListsBlock(store, gitExe, root, tag, base) {
|
|
|
237
271
|
"root=" + psq(root),
|
|
238
272
|
dropGitlinksBlock(),
|
|
239
273
|
excludeSyncBlock(store.excludeFile, base),
|
|
274
|
+
// issue #29:与 snapshotScript 同形——大文件不进 add(否则 preview 一次
|
|
275
|
+
// 也要为它们写对象),清单随 add 后的兜底剔除一起收口
|
|
276
|
+
oversizeListBlock(store.maxFileBytes),
|
|
277
|
+
oversizeAddPrepBlock(),
|
|
240
278
|
// fail-open add(语义见 snapshotScript 同款注释):diff/rollback 的当前
|
|
241
279
|
// 清单来自这次 add 后的索引——被跳过的路径不进清单,diff 不显示、
|
|
242
280
|
// rollback 删除清单也不会误删它们;≥2 显式退出防「旧索引假成功」
|
|
243
281
|
"addrc=0",
|
|
244
|
-
'"$git" --git-dir="$g" --work-tree="$root" add -A --ignore-errors || addrc=$?',
|
|
282
|
+
'"$git" --git-dir="$g" --work-tree="$root" add -A --ignore-errors "${pa_args[@]}" || addrc=$?',
|
|
245
283
|
'[ "$addrc" -le 1 ] || exit "$addrc"',
|
|
246
284
|
dropGitlinksBlock(),
|
|
247
|
-
|
|
285
|
+
oversizeRemoveBlock(),
|
|
248
286
|
"tmpc=" + psq(store.dir + "/diff-cur.$$"),
|
|
249
287
|
"tmpt=" + psq(store.dir + "/diff-tgt.$$"),
|
|
250
288
|
`"$git" -c core.quotePath=false --git-dir="$g" --work-tree="$root" ls-files --stage | grep -v '^160000 ' > "$tmpc" || true`,
|
|
@@ -255,7 +293,7 @@ function diffScript(root, store, gitExe, tag, base) {
|
|
|
255
293
|
return [
|
|
256
294
|
"set -e -o pipefail",
|
|
257
295
|
collectListsBlock(store, gitExe, root, tag, base),
|
|
258
|
-
`trap 'rm -f "$tmpc" "$tmpt"' EXIT`,
|
|
296
|
+
`trap 'rm -f "$tmpc" "$tmpt" "$oversize_p" "$oversize_r"' EXIT`,
|
|
259
297
|
"awk -F'\\t' -v OFS='\\t' '",
|
|
260
298
|
" FNR==1 { fidx++ }",
|
|
261
299
|
' fidx==1 { split($1, a, " "); cur[$2]=a[2]; next }',
|
|
@@ -278,7 +316,7 @@ function rollbackScript(root, store, gitExe, tag, base) {
|
|
|
278
316
|
"set -e -o pipefail",
|
|
279
317
|
collectListsBlock(store, gitExe, root, tag, base),
|
|
280
318
|
"tmpr=" + psq(store.dir + "/diff-restore.$$"),
|
|
281
|
-
`trap 'rm -f "$tmpc" "$tmpt" "$tmpr"' EXIT`,
|
|
319
|
+
`trap 'rm -f "$tmpc" "$tmpt" "$tmpr" "$oversize_p" "$oversize_r"' EXIT`,
|
|
282
320
|
// 恢复集 = 目标有、当前无或 sha 不同(awk 结构与 diffScript 同款:
|
|
283
321
|
// cur 侧 "mode sha stage<TAB>path" 取 a[2],target 侧 "mode type sha<TAB>path" 取 a[3])
|
|
284
322
|
"awk -F'\\t' '",
|
|
@@ -314,7 +352,7 @@ function rollbackScript(root, store, gitExe, tag, base) {
|
|
|
314
352
|
" fi",
|
|
315
353
|
"fi",
|
|
316
354
|
"tmpd=" + psq(store.dir + "/diff-del.$$"),
|
|
317
|
-
`trap 'rm -f "$tmpc" "$tmpt" "$tmpr" "$tmpd"' EXIT`,
|
|
355
|
+
`trap 'rm -f "$tmpc" "$tmpt" "$tmpr" "$tmpd" "$oversize_p" "$oversize_r"' EXIT`,
|
|
318
356
|
"awk -F'\\t' '",
|
|
319
357
|
" FNR==1 { fidx++ }",
|
|
320
358
|
" fidx==1 { cur[$2]=1; next }",
|
package/lib/scripts.pwsh.js
CHANGED
|
@@ -56,9 +56,20 @@ function reparseDirsBlock() {
|
|
|
56
56
|
"$reparseRel.Sort()"
|
|
57
57
|
].join("\n");
|
|
58
58
|
}
|
|
59
|
-
function
|
|
59
|
+
function oversizeRemoveBlock() {
|
|
60
|
+
return [
|
|
61
|
+
"for ($i = 0; $i -lt $oversizeRel.Count; $i += 100) {",
|
|
62
|
+
" $batch = $oversizeRel.GetRange($i, [Math]::Min(100, $oversizeRel.Count - $i))",
|
|
63
|
+
// --work-tree 不可省:bare 影子仓库下裸 update-index 会 fatal,pwsh 不抛
|
|
64
|
+
// → 大文件静默留在 index 里,maxFileBytes 的承诺形同虚设
|
|
65
|
+
" & $git --literal-pathspecs --git-dir=$g --work-tree=$root update-index --force-remove -- $batch",
|
|
66
|
+
"}"
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
function oversizeListBlock(maxBytes) {
|
|
60
70
|
return [
|
|
61
71
|
dirSkipSetBlock("oversizeSkip"),
|
|
72
|
+
'$oversizeP = Join-Path $g ("oversize-pathspec." + $PID)',
|
|
62
73
|
"$oversizeStack = [System.Collections.Generic.Stack[string]]::new()",
|
|
63
74
|
"$oversizeStack.Push($root)",
|
|
64
75
|
"$oversizeRel = [System.Collections.Generic.List[string]]::new()",
|
|
@@ -78,14 +89,44 @@ function oversizeBlock(maxBytes) {
|
|
|
78
89
|
" }",
|
|
79
90
|
" } catch {}",
|
|
80
91
|
"}",
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
92
|
+
// 清单必须存在(--pathspec-from-file 指错会让 add fatal):无大文件时写空文件,
|
|
93
|
+
// 后续按长度 0 判定「不传该参数」
|
|
94
|
+
"if ($oversizeRel.Count -gt 0) {",
|
|
95
|
+
" $oversizeSb = [System.Text.StringBuilder]::new()",
|
|
96
|
+
' foreach ($r in $oversizeRel) { [void]$oversizeSb.Append(":(top,exclude,literal)").Append($r).Append([char]0) }',
|
|
97
|
+
" [System.IO.File]::WriteAllText($oversizeP, $oversizeSb.ToString(), (New-Object System.Text.UTF8Encoding($false)))",
|
|
98
|
+
"} else {",
|
|
99
|
+
' Set-Content -LiteralPath $oversizeP -Value "" -NoNewline -Encoding ascii -ErrorAction SilentlyContinue',
|
|
100
|
+
"}",
|
|
101
|
+
oversizeRemoveBlock()
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
function oversizeAddPrepBlock() {
|
|
105
|
+
return [
|
|
106
|
+
"$paArgs = @()",
|
|
107
|
+
"if ((Test-Path -LiteralPath $oversizeP) -and ((Get-Item -LiteralPath $oversizeP).Length -gt 0)) {",
|
|
108
|
+
" $paOk = Join-Path $g 'pathspec-from-file.ok'",
|
|
109
|
+
" $paDetect = Test-Path -LiteralPath $paOk",
|
|
110
|
+
" if (-not $paDetect) {",
|
|
111
|
+
" $paHit = @()",
|
|
112
|
+
" $paEap = $ErrorActionPreference",
|
|
113
|
+
" $ErrorActionPreference = 'Continue'",
|
|
114
|
+
" try {",
|
|
115
|
+
" $paHit = @(& $git add -h 2>&1 | Select-String -SimpleMatch '--pathspec-from-file')",
|
|
116
|
+
" } finally { $ErrorActionPreference = $paEap }",
|
|
117
|
+
" if ($paHit.Count -gt 0) {",
|
|
118
|
+
" Set-Content -LiteralPath $paOk -Value 1 -Encoding ascii -ErrorAction SilentlyContinue",
|
|
119
|
+
" $paDetect = $true",
|
|
120
|
+
" }",
|
|
121
|
+
" }",
|
|
122
|
+
' if ($paDetect) { $paArgs = @(("--pathspec-from-file=" + $oversizeP), "--pathspec-file-nul") }',
|
|
123
|
+
"}",
|
|
124
|
+
"Set-Location -LiteralPath $root"
|
|
87
125
|
].join("\n");
|
|
88
126
|
}
|
|
127
|
+
function oversizeCleanupBlock() {
|
|
128
|
+
return "Remove-Item -LiteralPath $oversizeP -ErrorAction SilentlyContinue";
|
|
129
|
+
}
|
|
89
130
|
function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
90
131
|
const limit = maxBytes > 0 ? String(maxBytes) : "0";
|
|
91
132
|
const fileLimit = String(fileMaxBytes || MAX_FILE_BYTES);
|
|
@@ -96,27 +137,43 @@ function limitScanBlock(maxBytes, fileMaxBytes, budgetMs) {
|
|
|
96
137
|
"$limitHit = ''",
|
|
97
138
|
"$limitRc = 0",
|
|
98
139
|
"$limitFiles = @()",
|
|
140
|
+
// 临时文件名带 $PID:脚本每次都是新进程,避免同一 store 上的残留互相覆盖
|
|
141
|
+
'$limitErrFile = Join-Path $g ("limit-scan-err." + $PID)',
|
|
99
142
|
// PS 5.1:native stderr 在 EAP=Stop 下会被包成 ErrorRecord 直接抛(pwsh 7
|
|
100
143
|
// 不抛),一条 warning 就能让整条快照挂掉——与下方 add 段同款临时降级
|
|
101
144
|
"$limitEap = $ErrorActionPreference",
|
|
102
145
|
"$ErrorActionPreference = 'Continue'",
|
|
103
146
|
"try {",
|
|
104
|
-
// -z:路径含换行也不分裂(实测 PS 5.1/7 的 @() 都保留 NUL
|
|
105
|
-
|
|
147
|
+
// -z:路径含换行也不分裂(实测 PS 5.1/7 的 @() 都保留 NUL);stderr 落文件
|
|
148
|
+
// 而不是 $null——失败原因是 issue #29 的第一诉求,不能再吞掉
|
|
149
|
+
" $limitFiles = @(((& $git -c core.quotePath=false --git-dir=$g --work-tree=$root ls-files -z -co --exclude-standard 2>$limitErrFile) -join '').Split([char]0))",
|
|
106
150
|
" $limitRc = $LASTEXITCODE",
|
|
107
151
|
"} finally { $ErrorActionPreference = $limitEap }",
|
|
108
|
-
"if ($limitRc -
|
|
109
|
-
"
|
|
152
|
+
"if ($limitRc -ne 0) {",
|
|
153
|
+
" $limitMsg = ''",
|
|
154
|
+
// 只取首个非空行:PS 5.1 把 native stderr 写成 ErrorRecord(后续行是
|
|
155
|
+
// 调用点/脚本路径上下文),整段压平会把噪声塞进「最近错误」
|
|
156
|
+
" try {",
|
|
157
|
+
" $limitLines = @(Get-Content -LiteralPath $limitErrFile -Encoding UTF8 -ErrorAction SilentlyContinue | Where-Object { $_.Trim() })",
|
|
158
|
+
" if ($limitLines.Count -gt 0) { $limitMsg = ($limitLines[0] -replace '\\s+', ' ').Trim() }",
|
|
159
|
+
" } catch {}",
|
|
160
|
+
" Remove-Item -LiteralPath $limitErrFile -ErrorAction SilentlyContinue",
|
|
161
|
+
" if ($limitMsg.Length -gt 300) { $limitMsg = $limitMsg.Substring(0, 300) }",
|
|
162
|
+
" Write-Output ('SNAP_SCAN_FAIL ' + $limitRc)",
|
|
163
|
+
" Write-Output ('SNAP_SCAN_ERR ' + $limitMsg)",
|
|
164
|
+
" exit 0",
|
|
165
|
+
"}",
|
|
166
|
+
"Remove-Item -LiteralPath $limitErrFile -ErrorAction SilentlyContinue",
|
|
167
|
+
"foreach ($limitRel in $limitFiles) {",
|
|
110
168
|
// 两道守卫吸收两类异常条目:5.1 的 Split 会留尾部空串;目录条目(嵌套仓库
|
|
111
169
|
// 输出 `dir/`)的 Length 为空或抛——两者都不该计入(add 侧同样不纳入)
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
// 口径 = 实际会进快照的字节:超过单文件上限的文件随后被
|
|
170
|
+
" if (-not $limitRel) { continue }",
|
|
171
|
+
" if (" + budget + " -gt 0 -and $limitSw.ElapsedMilliseconds -gt " + budget + ") { $limitHit = 'time'; break }",
|
|
172
|
+
" try { $limitLen = [System.IO.FileInfo]::new($root + '/' + $limitRel).Length } catch { $limitLen = [long]0 }",
|
|
173
|
+
// 口径 = 实际会进快照的字节:超过单文件上限的文件随后被 oversizeListBlock
|
|
116
174
|
// 移出 index,不该计入总量(否则一个 300MB 视频就能把干净项目误判超限)
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
" }",
|
|
175
|
+
" if ($limitLen -gt 0 -and $limitLen -le " + fileLimit + ") { $limitBytes += $limitLen }",
|
|
176
|
+
" if (" + limit + " -gt 0 -and $limitBytes -gt " + limit + ") { $limitHit = 'bytes'; break }",
|
|
120
177
|
"}",
|
|
121
178
|
"if ($limitHit -eq 'bytes') { Write-Output ('SNAP_LIMIT bytes ' + $limitBytes + ' ' + " + limit + "); exit 0 }",
|
|
122
179
|
"if ($limitHit -eq 'time') { Write-Output ('SNAP_LIMIT time ' + $limitSw.ElapsedMilliseconds + ' ' + " + budget + "); exit 0 }"
|
|
@@ -246,7 +303,10 @@ function snapshotScript(root, store, gitExe, messageId, base) {
|
|
|
246
303
|
excludeSyncBlock(store.excludeFile, base),
|
|
247
304
|
// issue #24:总量/预算护栏先于 add——超限时连 add 都不跑
|
|
248
305
|
limitScanBlock(store.maxWorkspaceBytes, store.maxFileBytes, store.scanBudgetMs),
|
|
306
|
+
// issue #29:大文件清单先于 add——add 带 exclude 后不再为它们写对象
|
|
307
|
+
oversizeListBlock(store.maxFileBytes),
|
|
249
308
|
attrsMigrateBlock(),
|
|
309
|
+
oversizeAddPrepBlock(),
|
|
250
310
|
// fail-open add(issue #7 加固):--ignore-errors 让「个别路径无法索引」
|
|
251
311
|
// (无提交的嵌入式仓库、不可读文件等)以退出码 1 结束但索引照常落盘,
|
|
252
312
|
// 快照缺个别路径可接受,好过整条快照 fatal。退出码 ≥2 才是真 fatal
|
|
@@ -260,13 +320,16 @@ function snapshotScript(root, store, gitExe, messageId, base) {
|
|
|
260
320
|
// 后按 "unable to index file 'X'" 提取被跳过的路径,以 SNAP_SKIP 行
|
|
261
321
|
// 回传 JS 侧做用户可见提示。
|
|
262
322
|
"$ErrorActionPreference = 'Continue'",
|
|
263
|
-
'$addLog = (@(& $git --git-dir=$g --work-tree=$root add -A --ignore-errors 2>&1) | ForEach-Object { [string]$_ }) -join "`n"',
|
|
323
|
+
'$addLog = (@(& $git --git-dir=$g --work-tree=$root add -A --ignore-errors @paArgs 2>&1) | ForEach-Object { [string]$_ }) -join "`n"',
|
|
264
324
|
"$addRc = $LASTEXITCODE",
|
|
265
325
|
"$ErrorActionPreference = 'Stop'",
|
|
266
326
|
'if ($addRc -ge 2) { throw ("git add fatal (exit " + $addRc + "): " + $addLog) }',
|
|
267
327
|
`foreach ($m in [regex]::Matches($addLog, "unable to index file '([^']+)'") ) { Write-Output ('SNAP_SKIP ' + $m.Groups[1].Value) }`,
|
|
268
328
|
dropGitlinksBlock(),
|
|
269
|
-
|
|
329
|
+
// 兜底:旧 git 走裸 add 时大文件仍会被重新索引,这里按清单再清一次;
|
|
330
|
+
// pathspec 生效时它是空操作(清单条目本就不在 index)
|
|
331
|
+
oversizeRemoveBlock(),
|
|
332
|
+
oversizeCleanupBlock(),
|
|
270
333
|
"$tree = (& $git --git-dir=$g --work-tree=$root write-tree).Trim()",
|
|
271
334
|
"$commit = (& $git --git-dir=$g -c user.name=dsh-recall -c user.email=recall@dsh.local commit-tree $tree -m ('snapshot ' + " + psq(messageId) + ")).Trim()",
|
|
272
335
|
"& $git --git-dir=$g tag -f " + psq("snap-" + messageId) + " $commit | Out-Null",
|
|
@@ -286,14 +349,19 @@ function diffScript(root, store, gitExe, tag, base, maxChanges) {
|
|
|
286
349
|
"$root = " + psq(root),
|
|
287
350
|
dropGitlinksBlock(),
|
|
288
351
|
excludeSyncBlock(store.excludeFile, base),
|
|
352
|
+
// issue #29:与 snapshotScript 同形——大文件不进 add(否则 preview 一次
|
|
353
|
+
// 也要为它们写对象),清单随 add 后的兜底剔除一起收口
|
|
354
|
+
oversizeListBlock(store.maxFileBytes),
|
|
355
|
+
oversizeAddPrepBlock(),
|
|
289
356
|
// fail-open add:语义同 snapshotScript(--ignore-errors 跳过无法索引的
|
|
290
357
|
// 路径、≥2 显式 throw 防「旧索引假成功」);此处不提取 SNAP_SKIP——
|
|
291
358
|
// 被跳过的路径不进索引,diff 天然不显示、rollback 的删除清单来自当前
|
|
292
359
|
// 索引也天然不会误删它们
|
|
293
|
-
"& $git --git-dir=$g --work-tree=$root add -A --ignore-errors",
|
|
360
|
+
"& $git --git-dir=$g --work-tree=$root add -A --ignore-errors @paArgs",
|
|
294
361
|
'if ($LASTEXITCODE -ge 2) { throw ("git add fatal (exit " + $LASTEXITCODE + ")") }',
|
|
295
362
|
dropGitlinksBlock(),
|
|
296
|
-
|
|
363
|
+
oversizeRemoveBlock(),
|
|
364
|
+
oversizeCleanupBlock(),
|
|
297
365
|
"$curOut = & $git -c core.quotePath=false --git-dir=$g --work-tree=$root ls-files --stage",
|
|
298
366
|
// 旧 tag 的树里可能仍有 gitlink(修复前留下的),从目标侧一并剔除,
|
|
299
367
|
// 否则 diff 会报出“恢复 dsh-recall-plugin”这类幻影条目
|
|
@@ -335,11 +403,15 @@ function rollbackScript(root, store, gitExe, tag, base) {
|
|
|
335
403
|
"$root = " + psq(root),
|
|
336
404
|
dropGitlinksBlock(),
|
|
337
405
|
excludeSyncBlock(store.excludeFile, base),
|
|
406
|
+
// issue #29:同 snapshotScript(回退前的当前清单同样不该为大文件写对象)
|
|
407
|
+
oversizeListBlock(store.maxFileBytes),
|
|
408
|
+
oversizeAddPrepBlock(),
|
|
338
409
|
// fail-open add:语义同 snapshotScript 同款注释(diff/rollback 复用)
|
|
339
|
-
"& $git --git-dir=$g --work-tree=$root add -A --ignore-errors",
|
|
410
|
+
"& $git --git-dir=$g --work-tree=$root add -A --ignore-errors @paArgs",
|
|
340
411
|
'if ($LASTEXITCODE -ge 2) { throw ("git add fatal (exit " + $LASTEXITCODE + ")") }',
|
|
341
412
|
dropGitlinksBlock(),
|
|
342
|
-
|
|
413
|
+
oversizeRemoveBlock(),
|
|
414
|
+
oversizeCleanupBlock(),
|
|
343
415
|
// 同 diffScript:-z 的 NUL 输出会被 PowerShell 捕获丢弃,改为逐行 + quotePath=false
|
|
344
416
|
"$curOut = & $git -c core.quotePath=false --git-dir=$g --work-tree=$root ls-files --stage",
|
|
345
417
|
"$targetOut = @(& $git -c core.quotePath=false --git-dir=$g ls-tree -r " + psq(tag) + " | Where-Object { -not $_.StartsWith('160000') })",
|
package/lib/snapshots.js
CHANGED
|
@@ -21,11 +21,27 @@ function parseSnapshotLimit(out) {
|
|
|
21
21
|
}
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
|
+
function parseScanFail(out) {
|
|
25
|
+
let rc = null;
|
|
26
|
+
let message = "";
|
|
27
|
+
for (const line of String(out || "").split(/\r?\n/)) {
|
|
28
|
+
if (line.indexOf("SNAP_SCAN_FAIL ") === 0) {
|
|
29
|
+
const n = parseInt(line.slice(15).trim(), 10);
|
|
30
|
+
if (Number.isFinite(n)) rc = n;
|
|
31
|
+
} else if (line.indexOf("SNAP_SCAN_ERR ") === 0) {
|
|
32
|
+
message = line.slice(14).trim();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return rc === null ? null : { rc, message };
|
|
36
|
+
}
|
|
24
37
|
function bytesText(bytes) {
|
|
25
38
|
if (bytes >= 1048576) return Math.round(bytes / 1048576) + " MB";
|
|
26
39
|
return Math.max(1, Math.round(bytes / 1024)) + " KB";
|
|
27
40
|
}
|
|
28
41
|
function buildSnapshotLimitNotice(limit) {
|
|
42
|
+
if (limit.kind === "scan") {
|
|
43
|
+
return "\u65E0\u6CD5\u7EDF\u8BA1\u8BE5\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u8303\u56F4\uFF08git \u9000\u51FA\u7801 " + limit.value + "\uFF09\uFF0C\u672C\u6B21\u5FEB\u7167\u5DF2\u8DF3\u8FC7\uFF1B\u8BE5\u5DE5\u4F5C\u533A\u6682\u505C\u5FEB\u7167\u7EA6 10 \u5206\u949F\uFF0C\u4FEE\u590D\u540E\u81EA\u52A8\u6062\u590D\uFF0C\u8BE6\u60C5\u89C1\u300C\u6700\u8FD1\u9519\u8BEF\u300D";
|
|
44
|
+
}
|
|
29
45
|
if (limit.kind === "bytes") {
|
|
30
46
|
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
47
|
}
|
|
@@ -185,10 +201,20 @@ function createSnapshots(ctx, rt, config) {
|
|
|
185
201
|
const FUSE_AFTER = 3;
|
|
186
202
|
const FUSE_BACKOFF_BASE_MS = 5 * 60 * 1e3;
|
|
187
203
|
const FUSE_BACKOFF_CAP_MS = 60 * 60 * 1e3;
|
|
204
|
+
const EXPENSIVE_FAIL_MS = 60 * 1e3;
|
|
188
205
|
const snapFailures = /* @__PURE__ */ new Map();
|
|
189
206
|
const SNAP_LIMIT_RETRY_MS = 10 * 60 * 1e3;
|
|
190
207
|
const snapLimits = /* @__PURE__ */ new Map();
|
|
191
208
|
const limitBlockedAt = /* @__PURE__ */ new Map();
|
|
209
|
+
function markSnapshotLimited(root, limit) {
|
|
210
|
+
snapLimits.set(root, { ...limit, at: Date.now(), maxWorkspaceBytes: config.maxWorkspaceBytes, scanBudgetMs: config.scanBudgetMs });
|
|
211
|
+
}
|
|
212
|
+
function noteSnapshotBlocked(root, detail) {
|
|
213
|
+
const last = limitBlockedAt.get(root) || 0;
|
|
214
|
+
if (Date.now() - last < 5 * 60 * 1e3) return;
|
|
215
|
+
limitBlockedAt.set(root, Date.now());
|
|
216
|
+
rt.recordError(detail);
|
|
217
|
+
}
|
|
192
218
|
const SUPPORTED_FORMAT = 1;
|
|
193
219
|
const FORMAT_OK_TTL_MS = 3e4;
|
|
194
220
|
const formatOkAt = /* @__PURE__ */ new Map();
|
|
@@ -413,20 +439,26 @@ function createSnapshots(ctx, rt, config) {
|
|
|
413
439
|
return;
|
|
414
440
|
}
|
|
415
441
|
await loadIndex(root, sessionId);
|
|
442
|
+
const captureStartedAt = Date.now();
|
|
416
443
|
try {
|
|
417
444
|
const out = await rt.runShell(S.snapshotScript(root, store, state.gitExe || "", messageId, BASE()), { timeoutMs: 6e5, stdoutMaxBytes: 65536 });
|
|
418
445
|
const limit = parseSnapshotLimit(out);
|
|
419
446
|
if (limit) {
|
|
420
|
-
|
|
447
|
+
markSnapshotLimited(root, limit);
|
|
421
448
|
const notice = buildSnapshotLimitNotice(limit);
|
|
422
|
-
|
|
423
|
-
if (Date.now() - last >= 5 * 60 * 1e3) {
|
|
424
|
-
limitBlockedAt.set(root, Date.now());
|
|
425
|
-
rt.recordError("recall snapshot disabled for " + root + ": " + notice);
|
|
426
|
-
}
|
|
449
|
+
noteSnapshotBlocked(root, "recall snapshot disabled for " + root + ": " + notice);
|
|
427
450
|
setFeedback(messageId, { failed: true, kind: "workspaceLimit", error: notice });
|
|
428
451
|
return;
|
|
429
452
|
}
|
|
453
|
+
const scanFail = parseScanFail(out);
|
|
454
|
+
if (scanFail) {
|
|
455
|
+
const pseudo = { kind: "scan", value: scanFail.rc, limit: 0 };
|
|
456
|
+
markSnapshotLimited(root, pseudo);
|
|
457
|
+
const notice = buildSnapshotLimitNotice(pseudo);
|
|
458
|
+
noteSnapshotBlocked(root, "recall snapshot scan failed for " + root + ": rc=" + scanFail.rc + " " + scanFail.message);
|
|
459
|
+
setFeedback(messageId, { failed: true, kind: "scanFail", error: notice });
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
430
462
|
snapFailures.delete(root);
|
|
431
463
|
state.snapshots.set(String(messageId), { root, time: time || Date.now(), sessionId });
|
|
432
464
|
await saveIndex(root, sessionId);
|
|
@@ -434,7 +466,7 @@ function createSnapshots(ctx, rt, config) {
|
|
|
434
466
|
} catch (error) {
|
|
435
467
|
rt.recordError("recall snapshot failed: " + String(error));
|
|
436
468
|
setFeedback(messageId, { failed: true, ...buildFeedbackError(String(error)) });
|
|
437
|
-
await handleSnapshotFailure(root, store);
|
|
469
|
+
await handleSnapshotFailure(root, store, Date.now() - captureStartedAt);
|
|
438
470
|
}
|
|
439
471
|
}
|
|
440
472
|
function setFeedback(messageId, rec) {
|
|
@@ -460,7 +492,7 @@ function createSnapshots(ctx, rt, config) {
|
|
|
460
492
|
}
|
|
461
493
|
return {};
|
|
462
494
|
}
|
|
463
|
-
async function handleSnapshotFailure(root, store) {
|
|
495
|
+
async function handleSnapshotFailure(root, store, elapsedMs = 0) {
|
|
464
496
|
if (store && state.gitExe) {
|
|
465
497
|
try {
|
|
466
498
|
await rt.runShell(S.pruneScript(store, state.gitExe), { timeoutMs: 6e5, stdoutMaxBytes: 4096 });
|
|
@@ -470,11 +502,15 @@ function createSnapshots(ctx, rt, config) {
|
|
|
470
502
|
}
|
|
471
503
|
const f = snapFailures.get(root) || { count: 0, skipUntil: 0 };
|
|
472
504
|
f.count++;
|
|
505
|
+
const expensive = elapsedMs >= EXPENSIVE_FAIL_MS;
|
|
506
|
+
if (expensive && f.count < FUSE_AFTER) f.count = FUSE_AFTER;
|
|
473
507
|
if (f.count >= FUSE_AFTER) {
|
|
474
508
|
const backoff = Math.min(FUSE_BACKOFF_BASE_MS * 2 ** (f.count - FUSE_AFTER), FUSE_BACKOFF_CAP_MS);
|
|
475
509
|
const wasFused = Date.now() < f.skipUntil;
|
|
476
510
|
f.skipUntil = Date.now() + backoff;
|
|
477
|
-
if (!wasFused)
|
|
511
|
+
if (!wasFused) {
|
|
512
|
+
rt.recordError("recall snapshot fused after " + f.count + " consecutive failures, backoff " + Math.round(backoff / 6e4) + "min for: " + root + (expensive ? " (last failure took " + Math.round(elapsedMs / 1e3) + "s)" : ""));
|
|
513
|
+
}
|
|
478
514
|
}
|
|
479
515
|
snapFailures.set(root, f);
|
|
480
516
|
}
|
|
@@ -579,6 +615,7 @@ export {
|
|
|
579
615
|
judgeStoreFormat,
|
|
580
616
|
parseChanges,
|
|
581
617
|
parseDiffOutput,
|
|
618
|
+
parseScanFail,
|
|
582
619
|
parseSkipped,
|
|
583
620
|
parseSnapshotLimit,
|
|
584
621
|
parseTagsWithTime,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recall-plugin",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "在 DSH 中撤回自己发过的消息:工作区文件与对话历史一起回到那条消息发出之前,消息的文本与附件自动放回输入框,改完即可重发。文件走独立影子 git 快照(不碰项目自身的 git),对话走官方 sessions.fork,原会话归档可找回。",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -75,19 +75,20 @@
|
|
|
75
75
|
"0.2.1-alpha.5": "compatible",
|
|
76
76
|
"0.2.1-rc.1": "compatible",
|
|
77
77
|
"0.2.1-rc.2": "compatible",
|
|
78
|
-
"0.2.1-rc.3": "compatible"
|
|
78
|
+
"0.2.1-rc.3": "compatible",
|
|
79
|
+
"0.2.2-alpha.2": "compatible"
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
},
|
|
82
83
|
"peerDependencies": {
|
|
83
|
-
"@deepseek-ai/cordis": "^4.0.1 || >=4.0.5-alpha.1 <4.0.6",
|
|
84
|
-
"@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",
|
|
85
|
-
"@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",
|
|
86
|
-
"@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",
|
|
87
|
-
"@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",
|
|
88
|
-
"@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",
|
|
89
|
-
"@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",
|
|
90
|
-
"@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",
|
|
91
92
|
"react": "^18.2.0"
|
|
92
93
|
},
|
|
93
94
|
"peerDependenciesMeta": {
|