dsh-web-fetch-enhanced 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +23 -10
- package/README.md +23 -10
- package/docs/design.zh-CN.md +11 -2
- package/docs/migration-0.1.7-rc.1.zh-CN.md +63 -0
- package/docs/releases/0.0.7.md +22 -0
- package/lib/client.js +242 -186
- package/lib/index.js +57 -54
- package/lib/types/client/AllowlistCard.d.ts +38 -3
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/locales.d.ts +4 -5
- package/lib/types/index.d.ts +30 -25
- package/package.json +30 -24
package/README.en.md
CHANGED
|
@@ -35,7 +35,7 @@ With an empty CIDR allowlist, the security boundary remains equivalent to the na
|
|
|
35
35
|
|
|
36
36
|
## Compatibility & Support Policy
|
|
37
37
|
|
|
38
|
-
- **Minimum Supported DSH Version**: `0.1.
|
|
38
|
+
- **Minimum Supported DSH Version**: `0.1.7-rc.1`
|
|
39
39
|
- **Support Policy**: This plugin **only supports DeepSeek Harness RC (Release Candidate) releases and future stable releases**. Compatibility is not maintained for rapid-moving Alpha or development snapshot versions.
|
|
40
40
|
|
|
41
41
|
## Quick start
|
|
@@ -60,9 +60,9 @@ dsh plugin --profile web add link:/absolute/path/to/dsh-web-fetch-enhanced
|
|
|
60
60
|
|
|
61
61
|
Open DSH Web and go to:
|
|
62
62
|
|
|
63
|
-
**
|
|
63
|
+
**Plugins → dsh-web-fetch-enhanced**
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
The details page shows the allowlist form directly. Enter one network per line under **Allowed CIDRs**. For a typical Clash / Mihomo fake-IP setup:
|
|
66
66
|
|
|
67
67
|
```text
|
|
68
68
|
198.18.0.0/15
|
|
@@ -75,7 +75,9 @@ api.example.com
|
|
|
75
75
|
*.docs.example.com
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Choose **Save**. The next `web_fetch` uses the new policy; no Profile restart is required.
|
|
78
|
+
Choose **Save**. The page shows **Saved** and keeps the fields visible. The next `web_fetch` uses the new policy; no Profile restart is required.
|
|
79
|
+
|
|
80
|
+
DSH displays component status below the form. You do not need to open a component to configure the allowlist.
|
|
79
81
|
|
|
80
82
|
### 3. Keep using `web_fetch` normally
|
|
81
83
|
|
|
@@ -150,12 +152,21 @@ Allow only the smallest range you actually need. Do not add all RFC 1918 space f
|
|
|
150
152
|
|
|
151
153
|
## Settings actions
|
|
152
154
|
|
|
153
|
-
- **Save** writes
|
|
154
|
-
- **Discard** drops unsaved edits and restores
|
|
155
|
-
- **
|
|
156
|
-
-
|
|
155
|
+
- **Save** writes both allowlists in one mutation to the selected Profile plugin row. Host rejection or a revision conflict is not reported as success.
|
|
156
|
+
- **Discard** drops unsaved edits and restores effective values.
|
|
157
|
+
- **Restore inherited values** stages removal of both allowlist overrides; choose **Save** to apply. Inherited values may contain a non-empty allowlist, so review the resulting policy.
|
|
158
|
+
- **Read-only** means the form is not ready, the row is unavailable, or the connection cannot write. The card never falls back to another Profile or global configuration.
|
|
159
|
+
|
|
160
|
+
## Profile configuration and migration
|
|
161
|
+
|
|
162
|
+
Starting with DSH `0.1.7-rc.1`, this plugin uses Profile-owned Cordis `Config`, `.volatile()`, and plugin-manager `configForms`. It no longer reads global `$DSH_HOME/settings.yaml`.
|
|
157
163
|
|
|
158
|
-
|
|
164
|
+
- Effective configuration combines schema defaults with the current Profile composition/patch. Web edits target only the selected plugin row.
|
|
165
|
+
- **Clearing a field and saving writes an explicit `[]`** to revoke inherited allowlists. Empty arrays are meaningful security overrides and are no longer pruned.
|
|
166
|
+
- **Restore is not clear**: only an explicit restore uses `unset`, which may reactivate an inherited allowlist.
|
|
167
|
+
- Back up legacy settings, review the old `web-fetch-enhanced` section, and move intended rules into the target Profile plugin configuration. Global authorizations are never automatically copied to every Profile.
|
|
168
|
+
|
|
169
|
+
See the [0.1.7-rc.1 migration notes (Chinese)](docs/migration-0.1.7-rc.1.zh-CN.md).
|
|
159
170
|
|
|
160
171
|
## Security guidance
|
|
161
172
|
|
|
@@ -211,7 +222,9 @@ No. After a successful Web settings save, the next `web_fetch` uses the new poli
|
|
|
211
222
|
<details>
|
|
212
223
|
<summary><strong>Why is the settings card read-only?</strong></summary>
|
|
213
224
|
|
|
214
|
-
|
|
225
|
+
Verify that the target Profile plugin row is enabled, its form is available, and the current connection has configuration write permission. An unavailable form never falls back to a different configuration.
|
|
226
|
+
|
|
227
|
+
If both inputs remain disabled on DSH `0.1.7-rc.1` while the card renders normally, update to a build containing the configuration schema serialization fix, restart the Host, and refresh the page. Older builds expose custom validators whose callbacks are stripped during transport, preventing the browser form from becoming ready. The fix retains full Host validation.
|
|
215
228
|
</details>
|
|
216
229
|
|
|
217
230
|
<details>
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ DeepSeek Harness 原生 HTTP provider 默认拒绝所有非公网地址,这是
|
|
|
35
35
|
|
|
36
36
|
## 版本兼容性与支持策略
|
|
37
37
|
|
|
38
|
-
- **最低支持的 DSH 版本**:`0.1.
|
|
38
|
+
- **最低支持的 DSH 版本**:`0.1.7-rc.1`
|
|
39
39
|
- **版本支持策略**:本插件**仅对 DeepSeek Harness 的 RC(Release Candidate)候选发布版本及后续稳定正式版提供支持**。由于 Alpha 或开发快照版本更迭频繁且缺乏稳定的 API 保证,本插件不再对 Alpha 等非 RC 阶段版本进行维护与适配。
|
|
40
40
|
|
|
41
41
|
## 快速开始
|
|
@@ -60,9 +60,9 @@ dsh plugin --profile web add link:/absolute/path/to/dsh-web-fetch-enhanced
|
|
|
60
60
|
|
|
61
61
|
打开 DSH Web,进入:
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
**插件 → dsh-web-fetch-enhanced**
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
详情页直接显示白名单表单,在“允许的 CIDR”中每行填写一个网段。例如 Clash / Mihomo 的常见 Fake-IP 配置:
|
|
66
66
|
|
|
67
67
|
```text
|
|
68
68
|
198.18.0.0/15
|
|
@@ -75,7 +75,9 @@ api.example.com
|
|
|
75
75
|
*.docs.example.com
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
点击“保存”,页面显示“已保存”并保留输入框。无需重启 Profile,下一次 `web_fetch` 就会使用新规则。
|
|
79
|
+
|
|
80
|
+
下方“包含的组件”由 DSH 展示插件运行状态,配置白名单不需要进入组件。
|
|
79
81
|
|
|
80
82
|
### 3. 正常使用 `web_fetch`
|
|
81
83
|
|
|
@@ -150,12 +152,21 @@ wiki.corp.example
|
|
|
150
152
|
|
|
151
153
|
## 设置页按钮说明
|
|
152
154
|
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
155
|
+
- **保存**:将两项白名单作为一次配置变更写入当前 Profile 的插件行;Host 校验失败或版本冲突不会显示为成功。
|
|
156
|
+
- **放弃修改**:丢弃尚未保存的编辑,恢复当前生效值。
|
|
157
|
+
- **恢复继承值**:将移除两项白名单覆盖的操作加入草稿;仍需点击“保存”才生效。继承值可能包含非空白名单,请检查保存后的生效值。
|
|
158
|
+
- **只读状态**:当前表单尚未就绪、配置行不可用或连接没有写权限;不会回退到其他 Profile 或全局配置。
|
|
159
|
+
|
|
160
|
+
## Profile 配置与旧版本迁移
|
|
161
|
+
|
|
162
|
+
从 DSH `0.1.7-rc.1` 起,本插件使用 Profile 拥有的 Cordis `Config`、`.volatile()` 和插件管理页的 `configForms` 表单,不再读取全局 `$DSH_HOME/settings.yaml`。
|
|
157
163
|
|
|
158
|
-
|
|
164
|
+
- 配置由 Schema 默认值与当前 Profile composition/patch 决定;Web 保存只修改选中的插件行。
|
|
165
|
+
- **清空输入并保存会显式写入 `[]`**,用于撤销继承的白名单。空数组具有安全意义,不再自动清理。
|
|
166
|
+
- **恢复继承与清空不同**:只有显式恢复操作才执行 `unset`,可能重新启用底层配置中的白名单。
|
|
167
|
+
- 升级前备份旧设置,将旧 `web-fetch-enhanced` 节中的授权规则逐项审查后迁入目标 Profile 的插件配置。插件不会自动将全局授权复制到所有 Profile。
|
|
168
|
+
|
|
169
|
+
详见 [0.1.7-rc.1 升级说明](docs/migration-0.1.7-rc.1.zh-CN.md)。
|
|
159
170
|
|
|
160
171
|
## 安全提示
|
|
161
172
|
|
|
@@ -211,7 +222,9 @@ Fake-IP 模式会把域名解析到 `198.18.0.0/15` 等保留网段,再由代
|
|
|
211
222
|
<details>
|
|
212
223
|
<summary><strong>为什么设置卡片是只读的?</strong></summary>
|
|
213
224
|
|
|
214
|
-
|
|
225
|
+
请确认目标 Profile 的插件行已启用且配置表单可用,并检查当前连接的配置写权限。表单未就绪或已失效时不会尝试修改其他配置。
|
|
226
|
+
|
|
227
|
+
若在 DSH `0.1.7-rc.1` 中卡片正常显示,但两个输入框持续禁用,请更新到包含配置 schema 序列化修复的构建,重启 Host 后刷新页面。旧构建的自定义校验回调在传输中被移除,导致浏览器表单无法就绪;修复保留了 Host 端的完整校验。
|
|
215
228
|
</details>
|
|
216
229
|
|
|
217
230
|
<details>
|
package/docs/design.zh-CN.md
CHANGED
|
@@ -40,7 +40,7 @@ flowchart LR
|
|
|
40
40
|
|
|
41
41
|
本插件是 Host 侧 provider:它消费已有的 <code>web</code> 服务并注册一个 fetch provider,但不发布新的 Cordis 服务。因此它属于 Host composition,不属于 agent preset。模型工具仍由 preset 中原有的 <code>tool-web</code> 行提供。
|
|
42
42
|
|
|
43
|
-
入口采用 namespace plugin
|
|
43
|
+
入口采用 namespace plugin 约定,无 default export;源码使用显式 `.ts` 相对导入。构建生成 Host ESM `lib/index.js`、类型声明及通过 DSH ModuleLoader 注册的浏览器 `lib/client.js`。Client face 在 `plugins.bundle.config` 中提供插件详情页白名单表单,通过 `configForms.get('web-fetch-enhanced')` 绑定当前 Profile 的固定插件行,并用 `useSyncExternalStore` 订阅状态。页面不重复绘制插件标题或嵌套折叠卡片,保存后保持字段可见并显示结果。Host face 使用带 `.volatile()` 的 Config 字段,通过 `.get()` 读取每次请求的配置快照。Loader 在完整 schema 校验后原子提交 live refs,`loader/volatile-update` 通知更新系统提示词;`providerId` 保持非 volatile,修改时由 Loader 重建实例。随包发布的 `cordis.patch.yml` 为 Profile 提供默认 composition。
|
|
44
44
|
|
|
45
45
|
## 3. Provider 选择
|
|
46
46
|
|
|
@@ -100,6 +100,15 @@ flowchart LR
|
|
|
100
100
|
|
|
101
101
|
## 6. 配置与生命周期
|
|
102
102
|
|
|
103
|
+
### 6.1 Profile 配置层级
|
|
104
|
+
|
|
105
|
+
配置由 Schema 默认值、Profile composition 和 Profile patch 覆盖共同决定,不再使用全局 `settings.yaml` 或 `settings.installSection`。客户端只绑定随包 patch 声明的 `web-fetch-enhanced` 行;配置不可用或只读时不向其他行写入。
|
|
106
|
+
|
|
107
|
+
### 6.2 保存、清空与恢复继承
|
|
108
|
+
|
|
109
|
+
表单将两项白名单通过单次 `form.mutate(ops, revision)` 原子提交,使用草稿开始时的 revision 校验并发修改。清空字段保存为显式空数组,只有“恢复继承”执行 `unset`。保存失败时保留草稿,放弃修改重新读取当前 Profile 的值。离开页面会丢弃未保存的草稿;外部变更会更新未编辑的表单,不覆盖正在编辑的草稿。
|
|
110
|
+
|
|
111
|
+
### 6.3 启动阶段校验
|
|
103
112
|
Cordis Config schema 提供所有默认值;<code>createProvider()</code> 也在直接调用时应用同一默认值。启动阶段校验:
|
|
104
113
|
|
|
105
114
|
- provider ID 字符集和长度;
|
|
@@ -129,7 +138,7 @@ Cordis Config schema 提供所有默认值;<code>createProvider()</code> 也
|
|
|
129
138
|
|
|
130
139
|
## 8. 兼容策略
|
|
131
140
|
|
|
132
|
-
|
|
141
|
+
最低支持 DSH `0.1.7-rc.1`,DSH peers 声明 `>=0.1.7-rc.1 <0.2.0`,开发依赖固定为经过验证的 rc.1。使用 web、timeout、http-proxy、system-prompt、Cordis Loader 与客户端插件管理/配置表单的公开导出,不依赖 DSH 包的 `src/*` 深路径。安全 transport 在本包内维护,相关行为通过契约测试固定。
|
|
133
142
|
|
|
134
143
|
版本升级时重点回归:
|
|
135
144
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# DSH 0.1.7-rc.1 适配与迁移说明
|
|
2
|
+
|
|
3
|
+
## 基线与版本门禁
|
|
4
|
+
|
|
5
|
+
此次按上游 `dsh-v0.1.7-rc.1` 源码及 npm 发布包核对接口,不将历史会话评估当作 API 规范。最低支持版本由 `0.1.5-rc.2` 提升为 **`0.1.7-rc.1`**。
|
|
6
|
+
|
|
7
|
+
- `engines.dsh` 和所有 DSH peer 范围为 `>=0.1.7-rc.1 <0.2.0`。
|
|
8
|
+
- DSH 开发依赖固定 `0.1.7-rc.1`,锁文件同步更新;Cordis 使用 4.0.4、Schemastery 使用 3.18.4、Loader 使用 1.0.5。
|
|
9
|
+
- 上游 `packages/boot/app-boot/src/plugin-compatibility.ts` 实际使用 `semver.satisfies(..., { includePrerelease: true })`。显式 rc 下界避免错误接纳更早版本,也能在常规包管理器 semver 语义下接纳目标 rc。
|
|
10
|
+
- 本次验证针对 rc.1;范围内的未来版本不代表已实测,0.2 系列需重新评估。没有添加版本豁免,也没有修改用户 Profile。
|
|
11
|
+
|
|
12
|
+
## 采用的上游新能力
|
|
13
|
+
|
|
14
|
+
| 接入面 | 原实现 | 本次迁移 |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Host 配置 | settings.installSection/register 与全局覆盖 | Profile-owned Config + `.volatile()` |
|
|
17
|
+
| 实时值 | 自定义 current/source/watch | Loader 稳定 live refs 的 `.get()` |
|
|
18
|
+
| 配置通知 | Settings watcher | `loader/volatile-update`;授权变更后通知系统提示词 |
|
|
19
|
+
| 客户端 | settingsScope + settings.plugin.item | 插件详情页 `plugins.bundle.config` 与订阅当前 Profile 的 configForms |
|
|
20
|
+
| 保存 | 比对 user layer 推测写入结果 | 单次批量 mutate、revision 乐观并发校验和 Promise<boolean> 结果 |
|
|
21
|
+
| 清空白名单 | unset 并自动清理空数组 | 显式保存 [];恢复继承才 unset |
|
|
22
|
+
| 类型契约 | 本地模拟 Settings/SystemPrompt seam | 公开 Loader、SystemPrompt、PluginManager 类型 |
|
|
23
|
+
|
|
24
|
+
注意:rc.1 的 volatile 字段是引用对象,不是自动变更的普通数组;没有使用未经上游证实的 `ctx.accept` API。`providerId` 不是 volatile 字段,修改 composition 中的身份会重建 provider,而不是把旧 ID 静默指向新配置。
|
|
25
|
+
|
|
26
|
+
白名单和资源限制在 schema 校验阶段完整验证,Loader 成功后原子更新;非法候选不会部分扩大授权。每个请求捕获一份配置快照,在途请求继续使用启动时规则,下一次请求使用新配置。
|
|
27
|
+
|
|
28
|
+
## 操作者迁移步骤
|
|
29
|
+
|
|
30
|
+
1. 备份旧版 `$DSH_HOME/settings.yaml` 与目标 Profile composition。
|
|
31
|
+
2. 将 Host 升级至 DSH `0.1.7-rc.1` 或更高的兼容版本,并安装/链接本插件新构建。
|
|
32
|
+
3. 审查旧 `web-fetch-enhanced` 节,将必要的 `allowCidrs`、`allowHostnames` 及资源限制迁入目标 Profile 的 `web-fetch-enhanced` 插件行 config;或在插件详情页直接编辑白名单。
|
|
33
|
+
4. 不再依赖全局 settings.yaml;本插件不会自动把全局授权复制到各 Profile,以免扩大网络访问权限。
|
|
34
|
+
5. 保存后确认生效值。清空 CIDR 并保存意味着明确撤销非公网例外;恢复继承可能重新启用底层白名单,两者不可混同。
|
|
35
|
+
6. 升级插件文件后按部署方式重载/重启 Host 并刷新 Web 页面;配置字段的正常保存不需要重启。
|
|
36
|
+
7. 在实际部署中验证一个明确授权目标及一个未授权私网目标;不要为了测试额外放宽生产白名单。
|
|
37
|
+
|
|
38
|
+
默认 bundle 行 ID 为 `web-fetch-enhanced`,插件详情页注册键为包名 `dsh-web-fetch-enhanced`。bundle 插槽不提供 owner form,客户端显式订阅该 Profile 行的 ConfigForm。手写 composition 改名时,需要使用宿主通用配置表单或配套调整订阅的行 ID;组件不会猜测其他行的配置。
|
|
39
|
+
|
|
40
|
+
白名单在包详情页直接显示,不再进入组件行或展开额外卡片。保存后保留字段并显示结果,宿主的组件状态列表位于表单下方。
|
|
41
|
+
|
|
42
|
+
## 输入框持续禁用的修复
|
|
43
|
+
|
|
44
|
+
在 rc.1 的真实页面中,插件行正常运行且 Host 可写,但两个白名单输入框持续禁用。原因是 `settings.describe` 的传输 schema 保留了 `transform` 节点,却移除了其 `callback`。浏览器的 `ConfigFormController.decode()` 调用 `SettingsSchemaService.validate()` 时因此失败,表单一直停留在 `loading`。
|
|
45
|
+
|
|
46
|
+
插件现在仅在 Host 的运行时 schema 中保留自定义校验;序列化时输出对应的普通数组和数值 schema,并保留默认值、上下界及 volatile 元数据。Host 的 ConfigEditor/Loader 仍通过原始 Standard Schema 校验完整候选,再持久化和提交 live refs;浏览器表单不承担 CIDR 和域名的安全校验。序列化节点身份保持稳定,避免无配置变化时递增 revision。
|
|
47
|
+
|
|
48
|
+
已运行的 Host 需要重载插件模块或重启后刷新浏览器,单独刷新页面不会替换内存中的旧 Host schema。
|
|
49
|
+
|
|
50
|
+
## 保持不变及不适用项目
|
|
51
|
+
|
|
52
|
+
- `web_fetch` 工具名称、请求参数和返回结果不变;实际 rc.1 WebFetchProvider、WebFetchResult、代理路由接口与现有实现兼容。
|
|
53
|
+
- DNS 全答案检查、连接固定、NAT64 检查、同源重定向、取消/超时、匿名 GET 与非公网默认拒绝策略保持。
|
|
54
|
+
- 本插件没有旧图标导出、PTC、session-start、自定义会话附件、workspaceFiles 或工具视图接入,不为追逐新特性引入无关的浏览器/终端权限。
|
|
55
|
+
- 程序化调用 `createProvider()` 的原始配置类型现在为 `ProviderConfig`;`Config` 表示 Loader 解析后的 live-ref 配置。旧客户端 settingsScope 相关 helper 不再导出。
|
|
56
|
+
|
|
57
|
+
## 验证入口
|
|
58
|
+
|
|
59
|
+
运行 `pnpm run check`:类型检查、类型感知 lint、Host/Client 构建、全部测试及 publint。测试覆盖版本门禁、真实 Loader 热配置生命周期、原子拒绝非法候选、系统提示词刷新、请求快照、表单拒绝/版本冲突和既有 SSRF/代理回归。另在独立临时 Profile、DSH 0.1.7-rc.1 和 Chromium 中验收实际配置页面;该验收不代表运行中的用户 Profile 已重载插件。
|
|
60
|
+
|
|
61
|
+
本次验证结果:类型检查通过;类型感知 lint 为 0 warnings / 0 errors;Host、Client 与声明构建通过;13 个测试文件、100 项测试通过;发布覆盖率检查通过(行 99.54%、分支 95.48%);`git diff --check` 通过。publint 退出码为 0,但保留一项提示:`./client` 的 `.js` 在 `type: module` 包中含 CJS 形式。该产物不是直接由 Node 导入的通用 ESM,而是 DSH `window.__ModuleLoader__.load` 的 factory 包装;已有构建产物测试验证实际 loader 注册与执行。没有为消除提示擅自改变宿主要求的客户端加载格式。
|
|
62
|
+
|
|
63
|
+
真实浏览器验收通过:插件详情页直接显示可编辑表单、保存后字段保持可见并显示成功、刷新保留值、Host 拒绝非法 CIDR 且保留草稿、放弃修改、显式空数组持久化、恢复继承并从 Profile patch 中移除覆盖。桌面与 390px 窄屏布局均已检查;双标签页验证了未编辑表单同步、外部更新保留草稿及过期 revision 拒绝写入。测试使用隔离的临时 Profile,未改动用户生产 Profile。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# v0.0.7
|
|
2
|
+
|
|
3
|
+
## 兼容性
|
|
4
|
+
|
|
5
|
+
- 最低支持 DeepSeek Harness **0.1.7-rc.1**,支持范围为 `>=0.1.7-rc.1 <0.2.0`。
|
|
6
|
+
- 配置改为当前 Profile 的 Cordis Config 与 volatile 字段,不再读取全局 `settings.yaml`。旧版用户请按[迁移说明](../migration-0.1.7-rc.1.zh-CN.md)迁移白名单。
|
|
7
|
+
- 程序化创建 provider 的原始配置类型为 `ProviderConfig`;`Config` 表示 Loader 解析后的 live-ref 配置。
|
|
8
|
+
|
|
9
|
+
## 修复与改进
|
|
10
|
+
|
|
11
|
+
- 修复配置 schema 的校验回调在传输中被移除后,两个输入框持续禁用的问题;Host 仍完整校验所有保存请求。
|
|
12
|
+
- 白名单表单直接显示在插件详情页,无需进入组件;去掉重复标题和折叠卡片,保存后显示结果并保持字段可见。
|
|
13
|
+
- 同步外部配置变更,保留未保存草稿,通过 revision 检查拒绝过期写入。
|
|
14
|
+
- 清空白名单保存为显式空数组;只有“恢复继承”才移除覆盖,避免意外恢复底层授权。
|
|
15
|
+
- 白名单与资源限制更新后供下一次请求使用,在途请求保留原配置快照;授权变化同步更新系统提示词。
|
|
16
|
+
- 改善窄屏按钮布局,补充表单交互、schema 传输与 Loader 热更新回归测试。
|
|
17
|
+
|
|
18
|
+
升级插件后重启 Host 并刷新 Web 页面。日常白名单保存无需重启。
|
|
19
|
+
|
|
20
|
+
## English
|
|
21
|
+
|
|
22
|
+
Requires DSH 0.1.7-rc.1 or later within the 0.1 series. Configuration now belongs to the active Profile; review and migrate legacy global allowlists before upgrading. This release fixes disabled configuration inputs, moves the form onto the plugin details page, preserves drafts during external updates, rejects stale writes, and distinguishes explicit empty overrides from resetting to inherited values. Host validation and request-level configuration snapshots remain enforced. Restart the Host and refresh the browser after upgrading.
|
package/lib/client.js
CHANGED
|
@@ -15,12 +15,6 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
const value = Reflect.get(layer, field);
|
|
16
16
|
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
|
|
17
17
|
}
|
|
18
|
-
function hasLayerField(layer, field) {
|
|
19
|
-
return typeof layer === "object" && layer !== null && Object.prototype.hasOwnProperty.call(layer, field);
|
|
20
|
-
}
|
|
21
|
-
function equalValues(actual, expected) {
|
|
22
|
-
return actual !== void 0 && actual.length === expected.length && actual.every((value, index) => value === expected[index]);
|
|
23
|
-
}
|
|
24
18
|
function parseLines(text) {
|
|
25
19
|
const values = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
26
20
|
return {
|
|
@@ -28,228 +22,287 @@ window.__ModuleLoader__.load({
|
|
|
28
22
|
duplicate: new Set(values).size !== values.length
|
|
29
23
|
};
|
|
30
24
|
}
|
|
31
|
-
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
/** Empty input is an explicit empty profile override, never an implicit reset. */
|
|
26
|
+
function buildSaveOps(input, resetToInherited = false) {
|
|
27
|
+
if (resetToInherited) return [{
|
|
28
|
+
op: "unset",
|
|
29
|
+
path: ["allowCidrs"]
|
|
30
|
+
}, {
|
|
31
|
+
op: "unset",
|
|
32
|
+
path: ["allowHostnames"]
|
|
33
|
+
}];
|
|
34
|
+
return [{
|
|
35
|
+
op: "set",
|
|
36
|
+
path: ["allowCidrs"],
|
|
37
|
+
value: [...input.cidrs]
|
|
38
|
+
}, {
|
|
39
|
+
op: "set",
|
|
40
|
+
path: ["allowHostnames"],
|
|
41
|
+
value: [...input.hostnames]
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
/** The boolean Host result is authoritative, including when recovered values match. */
|
|
45
|
+
async function saveAllowlist(form, ops, revision) {
|
|
46
|
+
if (form.state.status !== "ready" || !form.state.writable || revision === void 0) return false;
|
|
47
|
+
try {
|
|
48
|
+
return await form.mutate(ops, revision);
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function isDirty(params) {
|
|
54
|
+
return Boolean(params.resetToInherited) || params.cidrs !== params.resolvedCidrs || params.hostnames !== params.resolvedHostnames;
|
|
55
|
+
}
|
|
56
|
+
/** Subscribe to the bundle's exact Profile entry, including writes and external edits. */
|
|
57
|
+
function AllowlistBundlePage({ configForm, t }) {
|
|
58
|
+
const store = (0, react.useMemo)(() => ({
|
|
59
|
+
subscribe: (listener) => configForm.subscribe(listener),
|
|
60
|
+
getSnapshot: () => configForm.getSnapshot(),
|
|
61
|
+
mutate: configForm.mutate.bind(configForm)
|
|
62
|
+
}), [configForm]);
|
|
63
|
+
return (0, react_jsx_runtime.jsx)(AllowlistPage, {
|
|
64
|
+
view: "page",
|
|
65
|
+
form: {
|
|
66
|
+
state: (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot, store.getSnapshot),
|
|
67
|
+
mutate: store.mutate
|
|
68
|
+
},
|
|
69
|
+
t
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Render only the supplied Profile form; never fall back to a global entry. */
|
|
73
|
+
function AllowlistPage({ view, form, t }) {
|
|
74
|
+
if (view === "summary") return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: t("description") });
|
|
75
|
+
if (!form || form.state.status === "unavailable") return (0, react_jsx_runtime.jsx)("p", {
|
|
76
|
+
role: "status",
|
|
77
|
+
children: t("unavailable")
|
|
78
|
+
});
|
|
79
|
+
if (form.state.status === "loading") return (0, react_jsx_runtime.jsx)("p", {
|
|
80
|
+
role: "status",
|
|
81
|
+
children: t("loading")
|
|
82
|
+
});
|
|
83
|
+
return (0, react_jsx_runtime.jsx)(AllowlistCard, {
|
|
84
|
+
form,
|
|
85
|
+
t
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const cardCss = `
|
|
89
|
+
.web-fetch-enhanced-form{color:var(--dsw-alias-label-primary)}
|
|
90
|
+
.web-fetch-enhanced-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}
|
|
91
|
+
.web-fetch-enhanced-field+.web-fetch-enhanced-field{border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
92
|
+
.web-fetch-enhanced-label{font-size:13px;font-weight:500;line-height:1.5}
|
|
93
|
+
.web-fetch-enhanced-textarea{box-sizing:border-box;width:100%;min-height:96px;resize:vertical;padding:9px 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font:12px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-primary)}
|
|
94
|
+
.web-fetch-enhanced-textarea:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary)}
|
|
95
|
+
.web-fetch-enhanced-textarea[aria-invalid='true']{border-color:var(--dsw-alias-state-error-primary)}
|
|
96
|
+
.web-fetch-enhanced-textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
97
|
+
.web-fetch-enhanced-hint,.web-fetch-enhanced-status{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
98
|
+
.web-fetch-enhanced-invalid,.web-fetch-enhanced-failed{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
|
|
99
|
+
.web-fetch-enhanced-readonly{margin:0 0 12px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
100
|
+
.web-fetch-enhanced-footer{display:flex;align-items:center;gap:12px;padding:16px 0 4px}
|
|
101
|
+
.web-fetch-enhanced-failed,.web-fetch-enhanced-status{flex:1;min-width:0}
|
|
102
|
+
.web-fetch-enhanced-actions{margin-left:auto;display:flex;align-items:center;justify-content:flex-end;gap:8px}
|
|
103
|
+
.web-fetch-enhanced-button{appearance:none;border:.5px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 14px;background:none;font:inherit;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
104
|
+
.web-fetch-enhanced-button:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
105
|
+
.web-fetch-enhanced-save{border-color:transparent;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
106
|
+
.web-fetch-enhanced-save:hover:not(:disabled){border-color:transparent;color:var(--dsw-alias-bg-layer-3)}
|
|
107
|
+
.web-fetch-enhanced-button:disabled{opacity:.4;cursor:default}
|
|
108
|
+
.web-fetch-enhanced-button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
109
|
+
@media(max-width:620px){.web-fetch-enhanced-footer{align-items:stretch;flex-direction:column}.web-fetch-enhanced-actions{width:100%;margin-left:0;flex-wrap:wrap}.web-fetch-enhanced-button{flex:1}.web-fetch-enhanced-button:first-child{flex-basis:100%}}
|
|
110
|
+
`;
|
|
111
|
+
function AllowlistCard({ form, t }) {
|
|
112
|
+
const snapshot = form.state;
|
|
113
|
+
const resolvedCidrs = formatLines(layerValues(snapshot.value, "allowCidrs"));
|
|
114
|
+
const resolvedHostnames = formatLines(layerValues(snapshot.value, "allowHostnames"));
|
|
36
115
|
const [cidrs, setCidrs] = (0, react.useState)(resolvedCidrs);
|
|
37
116
|
const [hostnames, setHostnames] = (0, react.useState)(resolvedHostnames);
|
|
38
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
39
117
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
118
|
+
const [saved, setSaved] = (0, react.useState)(false);
|
|
40
119
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
41
120
|
const [hasDraft, setHasDraft] = (0, react.useState)(false);
|
|
42
|
-
const [
|
|
43
|
-
const
|
|
121
|
+
const [resetToInherited, setResetToInherited] = (0, react.useState)(false);
|
|
122
|
+
const [draftRevision, setDraftRevision] = (0, react.useState)(snapshot.revision);
|
|
123
|
+
const cidrsId = (0, react.useId)();
|
|
124
|
+
const hostnamesId = (0, react.useId)();
|
|
44
125
|
const cidrsHintId = (0, react.useId)();
|
|
45
126
|
const hostnamesHintId = (0, react.useId)();
|
|
46
127
|
(0, react.useEffect)(() => {
|
|
47
128
|
if (!saving && !hasDraft) {
|
|
48
129
|
setCidrs(resolvedCidrs);
|
|
49
130
|
setHostnames(resolvedHostnames);
|
|
131
|
+
setDraftRevision(snapshot.revision);
|
|
50
132
|
}
|
|
51
133
|
}, [
|
|
52
134
|
hasDraft,
|
|
53
135
|
resolvedCidrs,
|
|
54
136
|
resolvedHostnames,
|
|
55
|
-
saving
|
|
137
|
+
saving,
|
|
138
|
+
snapshot.revision
|
|
56
139
|
]);
|
|
57
140
|
const parsedCidrs = (0, react.useMemo)(() => parseLines(cidrs), [cidrs]);
|
|
58
141
|
const parsedHostnames = (0, react.useMemo)(() => parseLines(hostnames), [hostnames]);
|
|
59
|
-
const dirty =
|
|
142
|
+
const dirty = failed || isDirty({
|
|
143
|
+
cidrs,
|
|
144
|
+
hostnames,
|
|
145
|
+
resolvedCidrs,
|
|
146
|
+
resolvedHostnames,
|
|
147
|
+
resetToInherited
|
|
148
|
+
});
|
|
60
149
|
const invalid = parsedCidrs.duplicate || parsedHostnames.duplicate;
|
|
61
150
|
const disabled = snapshot.status !== "ready" || !snapshot.writable || saving;
|
|
62
151
|
const save = async () => {
|
|
63
152
|
if (disabled || !dirty || invalid) return;
|
|
64
153
|
setSaving(true);
|
|
154
|
+
setSaved(false);
|
|
65
155
|
setFailed(false);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const user = scope.getSnapshot().user;
|
|
72
|
-
accepted = !hasLayerField(user, "allowCidrs") && !hasLayerField(user, "allowHostnames");
|
|
73
|
-
} else {
|
|
74
|
-
await scope.set("allowCidrs", parsedCidrs.values);
|
|
75
|
-
await scope.set("allowHostnames", parsedHostnames.values);
|
|
76
|
-
const user = scope.getSnapshot().user;
|
|
77
|
-
accepted = equalValues(layerValues(user, "allowCidrs"), parsedCidrs.values) && equalValues(layerValues(user, "allowHostnames"), parsedHostnames.values);
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
accepted = false;
|
|
81
|
-
} finally {
|
|
82
|
-
setSaving(false);
|
|
83
|
-
}
|
|
156
|
+
const accepted = await saveAllowlist(form, buildSaveOps({
|
|
157
|
+
cidrs: parsedCidrs.values,
|
|
158
|
+
hostnames: parsedHostnames.values
|
|
159
|
+
}, resetToInherited), draftRevision);
|
|
160
|
+
setSaving(false);
|
|
84
161
|
if (accepted) {
|
|
85
162
|
setHasDraft(false);
|
|
86
|
-
|
|
87
|
-
|
|
163
|
+
setResetToInherited(false);
|
|
164
|
+
setSaved(true);
|
|
88
165
|
} else setFailed(true);
|
|
89
166
|
};
|
|
90
167
|
const stageReset = () => {
|
|
91
168
|
if (disabled) return;
|
|
92
169
|
setCidrs(formatLines(layerValues(snapshot.base, "allowCidrs")));
|
|
93
170
|
setHostnames(formatLines(layerValues(snapshot.base, "allowHostnames")));
|
|
94
|
-
|
|
171
|
+
setResetToInherited(true);
|
|
95
172
|
setHasDraft(true);
|
|
96
173
|
setFailed(false);
|
|
174
|
+
setSaved(false);
|
|
97
175
|
};
|
|
98
176
|
const discard = () => {
|
|
99
177
|
setCidrs(resolvedCidrs);
|
|
100
178
|
setHostnames(resolvedHostnames);
|
|
101
|
-
|
|
179
|
+
setResetToInherited(false);
|
|
102
180
|
setHasDraft(false);
|
|
103
181
|
setFailed(false);
|
|
182
|
+
setSaved(false);
|
|
104
183
|
};
|
|
105
184
|
if (snapshot.status === "unavailable") return null;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"
|
|
185
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
186
|
+
className: "web-fetch-enhanced-form",
|
|
187
|
+
"aria-label": t("title"),
|
|
188
|
+
"aria-busy": saving,
|
|
110
189
|
children: [
|
|
111
190
|
(0, react_jsx_runtime.jsx)("style", { children: cardCss }),
|
|
112
|
-
(0, react_jsx_runtime.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
setOpen((value) => !value);
|
|
120
|
-
},
|
|
191
|
+
!snapshot.writable && snapshot.status === "ready" ? (0, react_jsx_runtime.jsx)("p", {
|
|
192
|
+
className: "web-fetch-enhanced-readonly",
|
|
193
|
+
role: "status",
|
|
194
|
+
children: t("readOnly")
|
|
195
|
+
}) : null,
|
|
196
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
197
|
+
className: "web-fetch-enhanced-field",
|
|
121
198
|
children: [
|
|
122
|
-
(0, react_jsx_runtime.
|
|
123
|
-
className: "web-fetch-enhanced-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
children: title
|
|
127
|
-
}), (0, react_jsx_runtime.jsx)("span", {
|
|
128
|
-
className: "web-fetch-enhanced-description",
|
|
129
|
-
children: t("description")
|
|
130
|
-
})]
|
|
199
|
+
(0, react_jsx_runtime.jsx)("label", {
|
|
200
|
+
className: "web-fetch-enhanced-label",
|
|
201
|
+
htmlFor: cidrsId,
|
|
202
|
+
children: t("cidrs")
|
|
131
203
|
}),
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"aria-
|
|
204
|
+
(0, react_jsx_runtime.jsx)("textarea", {
|
|
205
|
+
id: cidrsId,
|
|
206
|
+
className: "web-fetch-enhanced-textarea",
|
|
207
|
+
value: cidrs,
|
|
208
|
+
disabled,
|
|
209
|
+
placeholder: t("cidrsPlaceholder"),
|
|
210
|
+
"aria-describedby": cidrsHintId,
|
|
211
|
+
"aria-invalid": parsedCidrs.duplicate || void 0,
|
|
212
|
+
spellCheck: false,
|
|
213
|
+
onChange: (event) => {
|
|
214
|
+
const value = event.target.value;
|
|
215
|
+
setCidrs(value);
|
|
216
|
+
setResetToInherited(false);
|
|
217
|
+
setHasDraft(value !== resolvedCidrs || hostnames !== resolvedHostnames);
|
|
218
|
+
setFailed(false);
|
|
219
|
+
setSaved(false);
|
|
220
|
+
}
|
|
221
|
+
}),
|
|
222
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
223
|
+
id: cidrsHintId,
|
|
224
|
+
className: parsedCidrs.duplicate ? "web-fetch-enhanced-invalid" : "web-fetch-enhanced-hint",
|
|
225
|
+
children: parsedCidrs.duplicate ? t("duplicate") : t("cidrsHint")
|
|
139
226
|
})
|
|
140
227
|
]
|
|
141
228
|
}),
|
|
142
|
-
|
|
143
|
-
className: "web-fetch-enhanced-
|
|
144
|
-
id: bodyId,
|
|
229
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
230
|
+
className: "web-fetch-enhanced-field",
|
|
145
231
|
children: [
|
|
146
|
-
|
|
147
|
-
className: "web-fetch-enhanced-
|
|
232
|
+
(0, react_jsx_runtime.jsx)("label", {
|
|
233
|
+
className: "web-fetch-enhanced-label",
|
|
234
|
+
htmlFor: hostnamesId,
|
|
235
|
+
children: t("hostnames")
|
|
236
|
+
}),
|
|
237
|
+
(0, react_jsx_runtime.jsx)("textarea", {
|
|
238
|
+
id: hostnamesId,
|
|
239
|
+
className: "web-fetch-enhanced-textarea",
|
|
240
|
+
value: hostnames,
|
|
241
|
+
disabled,
|
|
242
|
+
placeholder: t("hostnamesPlaceholder"),
|
|
243
|
+
"aria-describedby": hostnamesHintId,
|
|
244
|
+
"aria-invalid": parsedHostnames.duplicate || void 0,
|
|
245
|
+
autoCapitalize: "none",
|
|
246
|
+
spellCheck: false,
|
|
247
|
+
onChange: (event) => {
|
|
248
|
+
const value = event.target.value;
|
|
249
|
+
setHostnames(value);
|
|
250
|
+
setResetToInherited(false);
|
|
251
|
+
setHasDraft(cidrs !== resolvedCidrs || value !== resolvedHostnames);
|
|
252
|
+
setFailed(false);
|
|
253
|
+
setSaved(false);
|
|
254
|
+
}
|
|
255
|
+
}),
|
|
256
|
+
(0, react_jsx_runtime.jsx)("p", {
|
|
257
|
+
id: hostnamesHintId,
|
|
258
|
+
className: parsedHostnames.duplicate ? "web-fetch-enhanced-invalid" : "web-fetch-enhanced-hint",
|
|
259
|
+
children: parsedHostnames.duplicate ? t("duplicate") : t("hostnamesHint")
|
|
260
|
+
})
|
|
261
|
+
]
|
|
262
|
+
}),
|
|
263
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
264
|
+
className: "web-fetch-enhanced-footer",
|
|
265
|
+
children: [
|
|
266
|
+
failed ? (0, react_jsx_runtime.jsx)("p", {
|
|
267
|
+
className: "web-fetch-enhanced-failed",
|
|
268
|
+
role: "alert",
|
|
269
|
+
children: t("failed")
|
|
270
|
+
}) : null,
|
|
271
|
+
!failed ? (0, react_jsx_runtime.jsx)("p", {
|
|
272
|
+
className: "web-fetch-enhanced-status",
|
|
148
273
|
role: "status",
|
|
149
|
-
children: t("
|
|
274
|
+
children: dirty ? t("unsaved") : saved ? t("saved") : ""
|
|
150
275
|
}) : null,
|
|
151
276
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
152
|
-
className: "web-fetch-enhanced-
|
|
153
|
-
children: [
|
|
154
|
-
(0, react_jsx_runtime.jsx)("label", {
|
|
155
|
-
className: "web-fetch-enhanced-label",
|
|
156
|
-
htmlFor: "web-fetch-enhanced-cidrs",
|
|
157
|
-
children: t("cidrs")
|
|
158
|
-
}),
|
|
159
|
-
(0, react_jsx_runtime.jsx)("textarea", {
|
|
160
|
-
id: "web-fetch-enhanced-cidrs",
|
|
161
|
-
className: "web-fetch-enhanced-textarea",
|
|
162
|
-
value: cidrs,
|
|
163
|
-
disabled,
|
|
164
|
-
placeholder: t("cidrsPlaceholder"),
|
|
165
|
-
"aria-describedby": cidrsHintId,
|
|
166
|
-
"aria-invalid": parsedCidrs.duplicate || void 0,
|
|
167
|
-
spellCheck: false,
|
|
168
|
-
onChange: (event) => {
|
|
169
|
-
const value = event.target.value;
|
|
170
|
-
setCidrs(value);
|
|
171
|
-
setResetToProfile(false);
|
|
172
|
-
setHasDraft(value !== resolvedCidrs || hostnames !== resolvedHostnames);
|
|
173
|
-
setFailed(false);
|
|
174
|
-
}
|
|
175
|
-
}),
|
|
176
|
-
(0, react_jsx_runtime.jsx)("p", {
|
|
177
|
-
id: cidrsHintId,
|
|
178
|
-
className: parsedCidrs.duplicate ? "web-fetch-enhanced-invalid" : "web-fetch-enhanced-hint",
|
|
179
|
-
children: parsedCidrs.duplicate ? t("duplicate") : t("cidrsHint")
|
|
180
|
-
})
|
|
181
|
-
]
|
|
182
|
-
}),
|
|
183
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
184
|
-
className: "web-fetch-enhanced-field",
|
|
277
|
+
className: "web-fetch-enhanced-actions",
|
|
185
278
|
children: [
|
|
186
|
-
(0, react_jsx_runtime.jsx)("
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
279
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
280
|
+
type: "button",
|
|
281
|
+
className: "web-fetch-enhanced-button",
|
|
282
|
+
disabled: disabled || resetToInherited,
|
|
283
|
+
onClick: stageReset,
|
|
284
|
+
children: t("reset")
|
|
190
285
|
}),
|
|
191
|
-
(0, react_jsx_runtime.jsx)("
|
|
192
|
-
|
|
193
|
-
className: "web-fetch-enhanced-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
"aria-describedby": hostnamesHintId,
|
|
198
|
-
"aria-invalid": parsedHostnames.duplicate || void 0,
|
|
199
|
-
autoCapitalize: "none",
|
|
200
|
-
spellCheck: false,
|
|
201
|
-
onChange: (event) => {
|
|
202
|
-
const value = event.target.value;
|
|
203
|
-
setHostnames(value);
|
|
204
|
-
setResetToProfile(false);
|
|
205
|
-
setHasDraft(cidrs !== resolvedCidrs || value !== resolvedHostnames);
|
|
206
|
-
setFailed(false);
|
|
207
|
-
}
|
|
286
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
287
|
+
type: "button",
|
|
288
|
+
className: "web-fetch-enhanced-button",
|
|
289
|
+
disabled: disabled || !dirty,
|
|
290
|
+
onClick: discard,
|
|
291
|
+
children: t("discard")
|
|
208
292
|
}),
|
|
209
|
-
(0, react_jsx_runtime.jsx)("
|
|
210
|
-
|
|
211
|
-
className:
|
|
212
|
-
|
|
293
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
294
|
+
type: "button",
|
|
295
|
+
className: "web-fetch-enhanced-button web-fetch-enhanced-save",
|
|
296
|
+
disabled: disabled || !dirty || invalid,
|
|
297
|
+
onClick: () => {
|
|
298
|
+
save();
|
|
299
|
+
},
|
|
300
|
+
children: saving ? t("saving") : t("save")
|
|
213
301
|
})
|
|
214
302
|
]
|
|
215
|
-
}),
|
|
216
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
217
|
-
className: "web-fetch-enhanced-footer",
|
|
218
|
-
children: [failed ? (0, react_jsx_runtime.jsx)("p", {
|
|
219
|
-
className: "web-fetch-enhanced-failed",
|
|
220
|
-
role: "alert",
|
|
221
|
-
children: t("failed")
|
|
222
|
-
}) : null, (0, react_jsx_runtime.jsxs)("div", {
|
|
223
|
-
className: "web-fetch-enhanced-actions",
|
|
224
|
-
children: [
|
|
225
|
-
(0, react_jsx_runtime.jsx)("button", {
|
|
226
|
-
type: "button",
|
|
227
|
-
className: "web-fetch-enhanced-button",
|
|
228
|
-
disabled: disabled || resetToProfile,
|
|
229
|
-
onClick: stageReset,
|
|
230
|
-
children: t("reset")
|
|
231
|
-
}),
|
|
232
|
-
(0, react_jsx_runtime.jsx)("button", {
|
|
233
|
-
type: "button",
|
|
234
|
-
className: "web-fetch-enhanced-button",
|
|
235
|
-
disabled: disabled || !dirty,
|
|
236
|
-
onClick: discard,
|
|
237
|
-
children: t("discard")
|
|
238
|
-
}),
|
|
239
|
-
(0, react_jsx_runtime.jsx)("button", {
|
|
240
|
-
type: "button",
|
|
241
|
-
className: "web-fetch-enhanced-button web-fetch-enhanced-save",
|
|
242
|
-
disabled: disabled || !dirty || invalid,
|
|
243
|
-
onClick: () => {
|
|
244
|
-
save();
|
|
245
|
-
},
|
|
246
|
-
children: saving ? t("saving") : t("save")
|
|
247
|
-
})
|
|
248
|
-
]
|
|
249
|
-
})]
|
|
250
303
|
})
|
|
251
304
|
]
|
|
252
|
-
})
|
|
305
|
+
})
|
|
253
306
|
]
|
|
254
307
|
});
|
|
255
308
|
}
|
|
@@ -258,8 +311,7 @@ window.__ModuleLoader__.load({
|
|
|
258
311
|
const en = {
|
|
259
312
|
title: "Web Fetch Enhanced",
|
|
260
313
|
description: "Allow selected non-public destinations for web_fetch. Public addresses remain available by default.",
|
|
261
|
-
|
|
262
|
-
collapse: "Collapse",
|
|
314
|
+
loading: "Loading configuration…",
|
|
263
315
|
unsaved: "Unsaved",
|
|
264
316
|
cidrs: "Allowed CIDRs",
|
|
265
317
|
cidrsHint: "One IPv4 or IPv6 CIDR per line. Keep the range as narrow as possible.",
|
|
@@ -268,12 +320,12 @@ window.__ModuleLoader__.load({
|
|
|
268
320
|
hostnamesHint: "Optional second factor. Use an exact hostname or a left-most *. wildcard; omit protocols, paths, and ports.",
|
|
269
321
|
hostnamesPlaceholder: "api.example.com\n*.docs.example.com",
|
|
270
322
|
save: "Save",
|
|
271
|
-
reset: "Reset to
|
|
323
|
+
reset: "Reset to inherited defaults",
|
|
272
324
|
discard: "Discard",
|
|
273
325
|
saving: "Saving…",
|
|
274
326
|
saved: "Saved",
|
|
275
|
-
failed: "The
|
|
276
|
-
unavailable: "This
|
|
327
|
+
failed: "The change was not saved. Review the entries, or discard to reload the current Profile before retrying.",
|
|
328
|
+
unavailable: "This plugin configuration is not available in the current Profile.",
|
|
277
329
|
readOnly: "This connection cannot persist Host Profile settings.",
|
|
278
330
|
duplicate: "Duplicate entries are not allowed.",
|
|
279
331
|
blank: "Blank entries are ignored."
|
|
@@ -281,8 +333,7 @@ window.__ModuleLoader__.load({
|
|
|
281
333
|
const zh = {
|
|
282
334
|
title: "Web Fetch Enhanced",
|
|
283
335
|
description: "允许 web_fetch 访问指定的非公网目标;公网地址默认仍可正常访问。",
|
|
284
|
-
|
|
285
|
-
collapse: "折叠",
|
|
336
|
+
loading: "正在加载配置…",
|
|
286
337
|
unsaved: "未保存",
|
|
287
338
|
cidrs: "允许的 CIDR",
|
|
288
339
|
cidrsHint: "每行一个 IPv4 或 IPv6 CIDR;请尽量缩小放行范围。",
|
|
@@ -291,25 +342,24 @@ window.__ModuleLoader__.load({
|
|
|
291
342
|
hostnamesHint: "可选的第二因子。填写精确域名或最左侧 *. 通配规则,不要包含协议、路径或端口。",
|
|
292
343
|
hostnamesPlaceholder: "api.example.com\n*.docs.example.com",
|
|
293
344
|
save: "保存",
|
|
294
|
-
reset: "
|
|
345
|
+
reset: "重置为继承的默认配置",
|
|
295
346
|
discard: "放弃修改",
|
|
296
347
|
saving: "正在保存…",
|
|
297
348
|
saved: "已保存",
|
|
298
|
-
failed: "
|
|
299
|
-
unavailable: "当前 Profile
|
|
349
|
+
failed: "修改未保存。请检查条目,或放弃修改以重新加载当前 Profile 后重试。",
|
|
350
|
+
unavailable: "当前 Profile 未提供此插件配置。",
|
|
300
351
|
readOnly: "当前连接不能持久化 Host Profile 设置。",
|
|
301
352
|
duplicate: "不允许重复条目。",
|
|
302
353
|
blank: "空白行会被忽略。"
|
|
303
354
|
};
|
|
304
355
|
//#endregion
|
|
305
356
|
//#region lib/types/client/index.js
|
|
306
|
-
const SETTINGS_NAMESPACE = "web-fetch-enhanced";
|
|
307
357
|
const LOCALE_NAMESPACE = "settings.webFetchEnhanced";
|
|
308
358
|
const name = "web-fetch-enhanced-client";
|
|
309
359
|
const inject = [
|
|
310
360
|
"slots",
|
|
311
361
|
"locale",
|
|
312
|
-
"
|
|
362
|
+
"configForms"
|
|
313
363
|
];
|
|
314
364
|
function apply(ctx) {
|
|
315
365
|
const t = ctx.locale.bind(LOCALE_NAMESPACE);
|
|
@@ -317,23 +367,29 @@ window.__ModuleLoader__.load({
|
|
|
317
367
|
en,
|
|
318
368
|
zh
|
|
319
369
|
}), "web-fetch-enhanced: settings dictionaries");
|
|
320
|
-
const
|
|
321
|
-
ctx.slots.inject("
|
|
322
|
-
name: "
|
|
323
|
-
key:
|
|
370
|
+
const configForm = ctx.configForms.get("web-fetch-enhanced");
|
|
371
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
372
|
+
name: "plugins.bundle.config",
|
|
373
|
+
key: "dsh-web-fetch-enhanced",
|
|
324
374
|
locale: LOCALE_NAMESPACE,
|
|
325
375
|
inject: () => ({
|
|
326
|
-
|
|
327
|
-
|
|
376
|
+
t,
|
|
377
|
+
configForm
|
|
328
378
|
})
|
|
329
|
-
},
|
|
379
|
+
}, AllowlistBundlePage));
|
|
330
380
|
}
|
|
331
381
|
//#endregion
|
|
382
|
+
exports.AllowlistBundlePage = AllowlistBundlePage;
|
|
332
383
|
exports.AllowlistCard = AllowlistCard;
|
|
384
|
+
exports.AllowlistPage = AllowlistPage;
|
|
333
385
|
exports.apply = apply;
|
|
386
|
+
exports.buildSaveOps = buildSaveOps;
|
|
334
387
|
exports.inject = inject;
|
|
388
|
+
exports.isDirty = isDirty;
|
|
389
|
+
exports.layerValues = layerValues;
|
|
335
390
|
exports.name = name;
|
|
336
391
|
exports.parseLines = parseLines;
|
|
392
|
+
exports.saveAllowlist = saveAllowlist;
|
|
337
393
|
return module.exports;
|
|
338
394
|
}
|
|
339
395
|
});
|
package/lib/index.js
CHANGED
|
@@ -601,37 +601,59 @@ function translateAbortOrNetwork(error, signal) {
|
|
|
601
601
|
* @module dsh-web-fetch-enhanced
|
|
602
602
|
*/
|
|
603
603
|
const MAX_NODE_TIMER_DELAY_MS = 2147483647;
|
|
604
|
-
const FIBER_DISPOSED = 4;
|
|
605
|
-
const FIBER_UNLOADING = 5;
|
|
606
604
|
/** Explicit product User-Agent used by default. */
|
|
607
605
|
const DEFAULT_USER_AGENT = "dsh-web-fetch-enhanced/0.1.0";
|
|
608
606
|
/** Default provider id; select it in the dsh-web row with fetchProvider. */
|
|
609
607
|
const DEFAULT_PROVIDER_ID = "http-enhanced";
|
|
610
|
-
/**
|
|
608
|
+
/** Default Loader entry id paired with the Web Profile configuration card. */
|
|
611
609
|
const SETTINGS_NAMESPACE = "web-fetch-enhanced";
|
|
612
610
|
/** Cordis plugin name used by loader diagnostics. */
|
|
613
611
|
const name = "web-fetch-enhanced";
|
|
614
612
|
/** The web seam this provider contributes to. */
|
|
615
613
|
const inject = ["web"];
|
|
614
|
+
/** Keep Host-only checks out of the schema serialized for browser forms. */
|
|
615
|
+
function hostValidated(schema, validate) {
|
|
616
|
+
const checked = z.transform(schema, (value) => {
|
|
617
|
+
validate(value);
|
|
618
|
+
return value;
|
|
619
|
+
}, true);
|
|
620
|
+
const presentation = new z(schema.toJSON());
|
|
621
|
+
checked.toJSON = function() {
|
|
622
|
+
presentation.meta = {
|
|
623
|
+
...schema.meta,
|
|
624
|
+
...this.meta
|
|
625
|
+
};
|
|
626
|
+
return presentation.toJSON();
|
|
627
|
+
};
|
|
628
|
+
return checked;
|
|
629
|
+
}
|
|
630
|
+
/** Schemastery's number bounds alone do not reject NaN; validate finiteness before commit. */
|
|
631
|
+
function positiveLimit(field, max = Number.MAX_VALUE) {
|
|
632
|
+
return hostValidated(z.number().min(Number.MIN_VALUE).max(max), (value) => {
|
|
633
|
+
assertPositiveFinite(field, value);
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
/** Validate the whole candidate before Loader atomically commits any live references. */
|
|
616
637
|
const Config = z.object({
|
|
617
|
-
providerId: z.string().default(DEFAULT_PROVIDER_ID),
|
|
618
|
-
allowCidrs: z.array(z.string())
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
638
|
+
providerId: z.string().pattern(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u).default(DEFAULT_PROVIDER_ID),
|
|
639
|
+
allowCidrs: hostValidated(z.array(z.string()), (value) => {
|
|
640
|
+
new AddressPolicy({ allowCidrs: value });
|
|
641
|
+
}).default([]).volatile(),
|
|
642
|
+
allowHostnames: hostValidated(z.array(z.string()), (value) => {
|
|
643
|
+
new AddressPolicy({ allowHostnames: value });
|
|
644
|
+
}).default([]).volatile(),
|
|
645
|
+
maxResponseBytes: positiveLimit("maxResponseBytes").default(5e6).volatile(),
|
|
646
|
+
maxBodyChars: positiveLimit("maxBodyChars").default(1e5).volatile(),
|
|
647
|
+
timeoutMs: positiveLimit("timeoutMs", MAX_NODE_TIMER_DELAY_MS).default(3e4).volatile(),
|
|
648
|
+
maxRedirects: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(5).volatile(),
|
|
649
|
+
userAgent: z.string().pattern(/^[\x20-\x7e\x80-\xff]*$/u).default(DEFAULT_USER_AGENT).volatile()
|
|
625
650
|
});
|
|
626
651
|
/** Build prompt guidance copy informing the model of authorized non-public destinations. */
|
|
627
652
|
function formatAllowlistPrompt(config) {
|
|
628
653
|
const cidrs = config.allowCidrs ?? [];
|
|
629
654
|
const hostnames = config.allowHostnames ?? [];
|
|
630
|
-
if (cidrs.length === 0
|
|
631
|
-
|
|
632
|
-
if (cidrs.length > 0) items.push(`CIDRs: ${cidrs.join(", ")}`);
|
|
633
|
-
if (hostnames.length > 0) items.push(`hostnames: ${hostnames.join(", ")}`);
|
|
634
|
-
return `The operator has explicitly authorized web_fetch access to the following non-public destinations: [${items.join("; ")}]. You can safely fetch these endpoints.`;
|
|
655
|
+
if (cidrs.length === 0) return "";
|
|
656
|
+
return `${`The operator has explicitly authorized web_fetch access to non-public addresses within [CIDRs: ${cidrs.join(", ")}].`}${hostnames.length === 0 ? "" : ` This exception requires BOTH an address within those CIDRs AND a URL hostname matching one of [hostnames: ${hostnames.join(", ")}]. Hostname rules only restrict the CIDR exceptions; they do not independently authorize any non-public address.`} All other URL, DNS, redirect, and transport safety checks still apply.`;
|
|
635
657
|
}
|
|
636
658
|
/** Construct the provider without mounting it, useful for tests and custom compositions. */
|
|
637
659
|
function createProvider(config = {}, proxyResolver) {
|
|
@@ -654,57 +676,38 @@ function createProvider(config = {}, proxyResolver) {
|
|
|
654
676
|
};
|
|
655
677
|
return new EnhancedHttpFetchProvider(resolved.providerId, limits, createAllowlistResolver(policy), proxyResolver);
|
|
656
678
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
return
|
|
679
|
+
/** Capture one coherent immutable configuration for a single request or prompt assembly. */
|
|
680
|
+
function snapshotConfig(config) {
|
|
681
|
+
return {
|
|
682
|
+
providerId: config.providerId,
|
|
683
|
+
allowCidrs: config.allowCidrs.get(),
|
|
684
|
+
allowHostnames: config.allowHostnames.get(),
|
|
685
|
+
maxResponseBytes: config.maxResponseBytes.get(),
|
|
686
|
+
maxBodyChars: config.maxBodyChars.get(),
|
|
687
|
+
timeoutMs: config.timeoutMs.get(),
|
|
688
|
+
maxRedirects: config.maxRedirects.get(),
|
|
689
|
+
userAgent: config.userAgent.get()
|
|
690
|
+
};
|
|
660
691
|
}
|
|
661
|
-
/** Register
|
|
692
|
+
/** Register a provider backed exclusively by Loader-owned live Config references. */
|
|
662
693
|
function apply(ctx, config) {
|
|
663
|
-
const providerId =
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
const settings = settingsCtx.settings;
|
|
667
|
-
if (typeof settings.installSection === "function") {
|
|
668
|
-
settings.installSection(ctx, SETTINGS_NAMESPACE, Config, config, {
|
|
669
|
-
setSource: (source) => {
|
|
670
|
-
current = source;
|
|
671
|
-
},
|
|
672
|
-
onChange: () => {
|
|
673
|
-
ctx.emit("system-prompt/change");
|
|
674
|
-
},
|
|
675
|
-
validate: (value) => {
|
|
676
|
-
if (resolveConfig(value).providerId !== providerId) throw new Error("web-fetch-enhanced: providerId cannot be changed through live settings");
|
|
677
|
-
createProvider(value);
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const scope = settings.register(SETTINGS_NAMESPACE, Config, {
|
|
683
|
-
base: config,
|
|
684
|
-
validate: (value) => {
|
|
685
|
-
if (resolveConfig(value).providerId !== providerId) throw new Error("web-fetch-enhanced: providerId cannot be changed through live settings");
|
|
686
|
-
createProvider(value);
|
|
687
|
-
}
|
|
688
|
-
});
|
|
689
|
-
current = () => scope.get();
|
|
690
|
-
settingsCtx.effect(() => () => {
|
|
691
|
-
if (isUnloading(ctx)) return;
|
|
692
|
-
current = () => config;
|
|
693
|
-
ctx.emit("system-prompt/change");
|
|
694
|
-
});
|
|
694
|
+
const providerId = createProvider(snapshotConfig(config)).id;
|
|
695
|
+
ctx.on("loader/volatile-update", (paths) => {
|
|
696
|
+
if (paths.some(([field]) => field === "allowCidrs" || field === "allowHostnames")) ctx.emit("system-prompt/change");
|
|
695
697
|
});
|
|
696
698
|
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
697
699
|
promptCtx.systemPrompt.section({
|
|
698
700
|
name: "web-fetch-enhanced:allowlist",
|
|
699
701
|
order: 2105,
|
|
700
|
-
text: () => formatAllowlistPrompt(
|
|
702
|
+
text: () => formatAllowlistPrompt(snapshotConfig(config)),
|
|
703
|
+
interpolate: false
|
|
701
704
|
});
|
|
702
705
|
});
|
|
703
706
|
const dynamicProvider = {
|
|
704
707
|
id: providerId,
|
|
705
708
|
available: () => true,
|
|
706
709
|
fetch: async (request, signal) => {
|
|
707
|
-
return await createProvider(
|
|
710
|
+
return await createProvider(snapshotConfig(config)).fetch(request, signal);
|
|
708
711
|
}
|
|
709
712
|
};
|
|
710
713
|
ctx.web.registerFetchProvider(dynamicProvider);
|
|
@@ -1,16 +1,51 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ConfigPageForm, PluginConfigViewProps } from '@deepseek-ai/dsh-client-ui-plugin-manager/client';
|
|
2
|
+
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
2
3
|
import type { LocaleKey } from './locales.ts';
|
|
3
4
|
export interface AllowlistSettings {
|
|
4
5
|
allowCidrs?: string[];
|
|
5
6
|
allowHostnames?: string[];
|
|
6
7
|
}
|
|
7
8
|
export interface AllowlistCardProps {
|
|
8
|
-
|
|
9
|
+
form: ConfigPageForm;
|
|
9
10
|
t: (key: LocaleKey) => string;
|
|
10
11
|
}
|
|
12
|
+
export type SettingsPathOp = {
|
|
13
|
+
op: 'set';
|
|
14
|
+
path: string[];
|
|
15
|
+
value: string[];
|
|
16
|
+
} | {
|
|
17
|
+
op: 'unset';
|
|
18
|
+
path: string[];
|
|
19
|
+
};
|
|
20
|
+
export declare function formatLines(value: readonly string[] | undefined): string;
|
|
21
|
+
export declare function layerValues(layer: unknown, field: keyof AllowlistSettings): string[] | undefined;
|
|
11
22
|
export declare function parseLines(text: string): {
|
|
12
23
|
values: string[];
|
|
13
24
|
duplicate: boolean;
|
|
14
25
|
};
|
|
15
|
-
|
|
26
|
+
/** Empty input is an explicit empty profile override, never an implicit reset. */
|
|
27
|
+
export declare function buildSaveOps(input: {
|
|
28
|
+
cidrs: readonly string[];
|
|
29
|
+
hostnames: readonly string[];
|
|
30
|
+
}, resetToInherited?: boolean): SettingsPathOp[];
|
|
31
|
+
/** The boolean Host result is authoritative, including when recovered values match. */
|
|
32
|
+
export declare function saveAllowlist(form: ConfigPageForm, ops: readonly SettingsPathOp[], revision: number | undefined): Promise<boolean>;
|
|
33
|
+
export interface DirtyCheckParams {
|
|
34
|
+
cidrs: string;
|
|
35
|
+
hostnames: string;
|
|
36
|
+
resolvedCidrs: string;
|
|
37
|
+
resolvedHostnames: string;
|
|
38
|
+
resetToInherited?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function isDirty(params: DirtyCheckParams): boolean;
|
|
41
|
+
/** Subscribe to the bundle's exact Profile entry, including writes and external edits. */
|
|
42
|
+
export declare function AllowlistBundlePage({ configForm, t }: {
|
|
43
|
+
configForm: ConfigForm<Record<string, unknown>>;
|
|
44
|
+
t: (key: LocaleKey) => string;
|
|
45
|
+
}): import("react").JSX.Element;
|
|
46
|
+
/** Render only the supplied Profile form; never fall back to a global entry. */
|
|
47
|
+
export declare function AllowlistPage({ view, form, t }: PluginConfigViewProps & {
|
|
48
|
+
t: (key: LocaleKey) => string;
|
|
49
|
+
}): import("react").JSX.Element;
|
|
50
|
+
export declare function AllowlistCard({ form, t }: AllowlistCardProps): import("react").JSX.Element | null;
|
|
16
51
|
//# sourceMappingURL=AllowlistCard.d.ts.map
|
|
@@ -8,6 +8,6 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
8
8
|
export declare const name = "web-fetch-enhanced-client";
|
|
9
9
|
export declare const inject: string[];
|
|
10
10
|
export declare function apply(ctx: Context): void;
|
|
11
|
-
export { AllowlistCard, parseLines } from './AllowlistCard.tsx';
|
|
12
|
-
export type { AllowlistCardProps, AllowlistSettings } from './AllowlistCard.tsx';
|
|
11
|
+
export { AllowlistCard, AllowlistPage, AllowlistBundlePage, parseLines, buildSaveOps, saveAllowlist, isDirty, layerValues } from './AllowlistCard.tsx';
|
|
12
|
+
export type { AllowlistCardProps, AllowlistSettings, SettingsPathOp, DirtyCheckParams } from './AllowlistCard.tsx';
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export declare const en: {
|
|
2
2
|
readonly title: "Web Fetch Enhanced";
|
|
3
3
|
readonly description: "Allow selected non-public destinations for web_fetch. Public addresses remain available by default.";
|
|
4
|
-
readonly
|
|
5
|
-
readonly collapse: "Collapse";
|
|
4
|
+
readonly loading: "Loading configuration…";
|
|
6
5
|
readonly unsaved: "Unsaved";
|
|
7
6
|
readonly cidrs: "Allowed CIDRs";
|
|
8
7
|
readonly cidrsHint: "One IPv4 or IPv6 CIDR per line. Keep the range as narrow as possible.";
|
|
@@ -11,12 +10,12 @@ export declare const en: {
|
|
|
11
10
|
readonly hostnamesHint: "Optional second factor. Use an exact hostname or a left-most *. wildcard; omit protocols, paths, and ports.";
|
|
12
11
|
readonly hostnamesPlaceholder: "api.example.com\n*.docs.example.com";
|
|
13
12
|
readonly save: "Save";
|
|
14
|
-
readonly reset: "Reset to
|
|
13
|
+
readonly reset: "Reset to inherited defaults";
|
|
15
14
|
readonly discard: "Discard";
|
|
16
15
|
readonly saving: "Saving…";
|
|
17
16
|
readonly saved: "Saved";
|
|
18
|
-
readonly failed: "The
|
|
19
|
-
readonly unavailable: "This
|
|
17
|
+
readonly failed: "The change was not saved. Review the entries, or discard to reload the current Profile before retrying.";
|
|
18
|
+
readonly unavailable: "This plugin configuration is not available in the current Profile.";
|
|
20
19
|
readonly readOnly: "This connection cannot persist Host Profile settings.";
|
|
21
20
|
readonly duplicate: "Duplicate entries are not allowed.";
|
|
22
21
|
readonly blank: "Blank entries are ignored.";
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,27 +6,26 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
8
|
import z from '@deepseek-ai/schemastery';
|
|
9
|
-
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
10
9
|
import { EnhancedHttpFetchProvider } from './provider.ts';
|
|
11
10
|
import type { ProxyRouteResolver } from './resolver.ts';
|
|
12
11
|
/** Explicit product User-Agent used by default. */
|
|
13
12
|
export declare const DEFAULT_USER_AGENT = "dsh-web-fetch-enhanced/0.1.0";
|
|
14
13
|
/** Default provider id; select it in the dsh-web row with fetchProvider. */
|
|
15
14
|
export declare const DEFAULT_PROVIDER_ID = "http-enhanced";
|
|
16
|
-
/**
|
|
17
|
-
export declare const SETTINGS_NAMESPACE
|
|
15
|
+
/** Default Loader entry id paired with the Web Profile configuration card. */
|
|
16
|
+
export declare const SETTINGS_NAMESPACE = "web-fetch-enhanced";
|
|
18
17
|
/** Cordis plugin name used by loader diagnostics. */
|
|
19
18
|
export declare const name = "web-fetch-enhanced";
|
|
20
19
|
/** The web seam this provider contributes to. */
|
|
21
20
|
export declare const inject: string[];
|
|
22
21
|
/** Plugin configuration. Every non-public exception is explicit and deny-by-default. */
|
|
23
|
-
export interface
|
|
22
|
+
export interface ProviderConfig {
|
|
24
23
|
/** Provider id registered in ctx.web. Defaults to http-enhanced. */
|
|
25
24
|
providerId?: string;
|
|
26
25
|
/** Non-public IPv4/IPv6 CIDRs that may bypass the public-address filter. */
|
|
27
|
-
allowCidrs?: string[];
|
|
26
|
+
allowCidrs?: readonly string[];
|
|
28
27
|
/** Optional exact hosts or left-most wildcard rules required in addition to allowCidrs. */
|
|
29
|
-
allowHostnames?: string[];
|
|
28
|
+
allowHostnames?: readonly string[];
|
|
30
29
|
/** Maximum response body size in bytes. */
|
|
31
30
|
maxResponseBytes?: number;
|
|
32
31
|
/** Maximum decoded body length in characters. */
|
|
@@ -38,29 +37,35 @@ export interface Config {
|
|
|
38
37
|
/** User-Agent header sent on every request. */
|
|
39
38
|
userAgent?: string;
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
/** Validate the whole candidate before Loader atomically commits any live references. */
|
|
41
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
42
|
+
providerId: z<string, string, "defined">;
|
|
43
|
+
allowCidrs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
44
|
+
allowHostnames: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
45
|
+
maxResponseBytes: z<number, number, "volatile-defined">;
|
|
46
|
+
maxBodyChars: z<number, number, "volatile-defined">;
|
|
47
|
+
timeoutMs: z<number, number, "volatile-defined">;
|
|
48
|
+
maxRedirects: z<number, number, "volatile-defined">;
|
|
49
|
+
userAgent: z<string, string, "volatile-defined">;
|
|
50
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
51
|
+
providerId: z<string, string, "defined">;
|
|
52
|
+
allowCidrs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
53
|
+
allowHostnames: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
54
|
+
maxResponseBytes: z<number, number, "volatile-defined">;
|
|
55
|
+
maxBodyChars: z<number, number, "volatile-defined">;
|
|
56
|
+
timeoutMs: z<number, number, "volatile-defined">;
|
|
57
|
+
maxRedirects: z<number, number, "volatile-defined">;
|
|
58
|
+
userAgent: z<string, string, "volatile-defined">;
|
|
59
|
+
}>>, "plain">;
|
|
60
|
+
/** Parsed plugin Config: ordinary identity plus stable live field references. */
|
|
61
|
+
export type Config = ReturnType<typeof Config>;
|
|
57
62
|
/** Build prompt guidance copy informing the model of authorized non-public destinations. */
|
|
58
|
-
export declare function formatAllowlistPrompt(config:
|
|
63
|
+
export declare function formatAllowlistPrompt(config: ProviderConfig): string;
|
|
59
64
|
/** Construct the provider without mounting it, useful for tests and custom compositions. */
|
|
60
|
-
export declare function createProvider(config?:
|
|
65
|
+
export declare function createProvider(config?: ProviderConfig, proxyResolver?: ProxyRouteResolver): EnhancedHttpFetchProvider;
|
|
61
66
|
export { isNonPublicIpLiteral } from './address-policy.ts';
|
|
62
67
|
export type { ProxyRouteResolver, ProxyRouteResult } from './resolver.ts';
|
|
63
68
|
export { defaultProxyRoute } from './resolver.ts';
|
|
64
|
-
/** Register
|
|
69
|
+
/** Register a provider backed exclusively by Loader-owned live Config references. */
|
|
65
70
|
export declare function apply(ctx: Context, config: Config): void;
|
|
66
71
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-web-fetch-enhanced",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Configurable non-public address allowlists for DeepSeek Harness web_fetch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"client": {
|
|
38
38
|
"inject": [
|
|
39
|
-
"@deepseek-ai/dsh-client-ui-
|
|
39
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager",
|
|
40
40
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
41
41
|
"@deepseek-ai/dsh-client-locale",
|
|
42
42
|
"@deepseek-ai/dsh-client-ui-renderer"
|
|
@@ -66,45 +66,51 @@
|
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
68
68
|
"node": "^22.19.0 || >=24.0.0",
|
|
69
|
-
"dsh": ">=0.1.
|
|
69
|
+
"dsh": ">=0.1.7-rc.1 <0.2.0"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
73
|
-
"@deepseek-ai/
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
"@deepseek-ai/dsh-client-
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-renderer": "
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-settings": "
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-
|
|
79
|
-
"@deepseek-ai/dsh-
|
|
80
|
-
"@deepseek-ai/dsh-
|
|
81
|
-
"@deepseek-ai/dsh-
|
|
72
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
73
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.5",
|
|
74
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.1 <0.2.0",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.1.7-rc.1 <0.2.0",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-renderer": ">=0.1.7-rc.1 <0.2.0",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-rc.1 <0.2.0",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-rc.1 <0.2.0",
|
|
79
|
+
"@deepseek-ai/dsh-http-proxy": ">=0.1.7-rc.1 <0.2.0",
|
|
80
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.1.7-rc.1 <0.2.0",
|
|
81
|
+
"@deepseek-ai/dsh-timeout": ">=0.1.7-rc.1 <0.2.0",
|
|
82
|
+
"@deepseek-ai/dsh-web": ">=0.1.7-rc.1 <0.2.0"
|
|
82
83
|
},
|
|
83
84
|
"dependencies": {
|
|
84
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
85
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
85
86
|
"ipaddr.js": "^2.5.0",
|
|
86
87
|
"undici": "^8.10.0"
|
|
87
88
|
},
|
|
88
89
|
"devDependencies": {
|
|
89
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
90
|
-
"@deepseek-ai/
|
|
91
|
-
"@deepseek-ai/dsh-
|
|
92
|
-
"@deepseek-ai/dsh-client-
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-renderer": "
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-settings": "
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-
|
|
96
|
-
"@deepseek-ai/dsh-
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
90
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
91
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.5",
|
|
92
|
+
"@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
|
|
93
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.1.7-rc.1",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-http-proxy": "0.1.7-rc.1",
|
|
98
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-timeout": "0.1.7-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-web": "0.1.7-rc.1",
|
|
99
101
|
"@types/js-yaml": "^4.0.9",
|
|
100
102
|
"@types/node": "^22.19.13",
|
|
101
103
|
"@types/react": "~18.3.1",
|
|
104
|
+
"@types/react-test-renderer": "18.3.1",
|
|
105
|
+
"@types/semver": "^7.7.1",
|
|
102
106
|
"@vitest/coverage-v8": "^4.1.8",
|
|
103
107
|
"js-yaml": "^4.1.0",
|
|
104
108
|
"oxlint": "^1.51.0",
|
|
105
109
|
"oxlint-tsgolint": "7.0.2001",
|
|
106
110
|
"publint": "^0.3.21",
|
|
107
111
|
"react": "^18.2.0",
|
|
112
|
+
"react-test-renderer": "18.3.1",
|
|
113
|
+
"semver": "^7.8.5",
|
|
108
114
|
"tsdown": "^0.21.0",
|
|
109
115
|
"typescript": "^6.0.3",
|
|
110
116
|
"vitest": "^4.1.8"
|