@eduwork/dsh-mail 0.1.0-alpha.5 → 0.1.1

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.
@@ -2,11 +2,12 @@
2
2
 
3
3
  [简体中文](compatibility.md) | **English**
4
4
 
5
- Package `@eduwork/dsh-mail@0.1.0-alpha.5` retains the alpha.4 runtime and settings contracts. See [migration](EDUWORK-MIGRATION.en.md).
5
+ `@eduwork/dsh-mail@0.1.1` targets official DeepSeek Harness `0.1.5-rc.1` while retaining exact peer ranges for previously accepted baselines. See [migration](EDUWORK-MIGRATION.en.md) for the old unscoped package.
6
6
 
7
7
  ## Reviewed baseline
8
8
 
9
- - DeepSeek Harness `0.1.2-alpha.2`;
9
+ - DeepSeek Harness `0.1.5-rc.1` (current release baseline);
10
+ - DeepSeek Harness `0.1.2-rc.1`, `0.1.3-alpha.1`, `0.1.3-alpha.2`, and `0.1.5-alpha.1` (previously accepted compatibility baselines);
10
11
  - Node.js 22+;
11
12
  - dynamic Web Client Bundle;
12
13
  - local Host filesystem;
@@ -14,6 +15,10 @@ Package `@eduwork/dsh-mail@0.1.0-alpha.5` retains the alpha.4 runtime and settin
14
15
 
15
16
  The DSH Profile provides `settings`, `credentials`, `tools`, `permissionPresets`, `approval`, and `fs`. The settings page also depends on official Remotes, Renderer, and Settings Client packages. Ordinary sending requires an interactive approval path; Full Access does not prompt.
16
17
 
18
+ DSH `0.1.5-rc.1` requires plugins to use the Agent explicitly from tool execution context. Mail reads the current session from each execution's `exec.agent`, uses `agent.session` for permission presets and workspace resolution, does not depend on a global Agent, does not create or resume Sessions, and does not use ordinary subprocess handles. The ToolRuntime, Permission Presets, Credentials, Settings, Renderer, and Settings Client seams are accepted.
19
+
20
+ The official generic-file upload, Workspace Files, and `readByteRange()` can support future large-attachment improvements, but they cannot yet replace the Host binary write needed for IMAP downloads: `ctx.fs` still has no `writeBytes()`. This version therefore retains the local download adapter with workspace containment, real-path revalidation, and exclusive random filenames.
21
+
17
22
  ## Protocol scope
18
23
 
19
24
  - IMAP uses read-only mailbox semantics and UIDVALIDITY to reject stale UIDs.
@@ -24,7 +29,7 @@ The DSH Profile provides `settings`, `credentials`, `tools`, `permissionPresets`
24
29
 
25
30
  ## DSH binary-write limitation
26
31
 
27
- DSH `0.1.2-alpha.2` exposes `ctx.fs.readBytes` but no `writeBytes`:
32
+ The current DSH `0.1.5-rc.1` release baseline exposes `ctx.fs.readBytes` but no `writeBytes`:
28
33
 
29
34
  - outbound local attachments use the official seam end to end;
30
35
  - downloads require `ctx.fs.processPath()` and the Host Node process to share one filesystem;
@@ -34,4 +39,4 @@ DSH `0.1.2-alpha.2` exposes `ctx.fs.readBytes` but no `writeBytes`:
34
39
 
35
40
  ## Version policy
36
41
 
37
- During `0.x`, a DSH alpha may change the Client Loader, settings slots, or tool schemas. For every upgrade, run the full automated suite and real IMAP/SMTP acceptance in an isolated Profile. Compilation alone is insufficient.
42
+ The plugin follows its own SemVer instead of mirroring the host product: patch for compatibility fixes and dependency maintenance, minor for backward-compatible capability additions, and major only for breaking tool or configuration contracts. During DSH `0.x`, run the full automated suite and target-runtime acceptance for every upgrade, then complete IMAP/SMTP interoperability in an isolated Profile. Compilation alone is insufficient.
@@ -2,11 +2,12 @@
2
2
 
3
3
  **简体中文** | [English](compatibility.en.md)
4
4
 
5
- `@eduwork/dsh-mail@0.1.0-alpha.5` 保留 alpha.4 的运行时和设置契约。详见[迁移说明](EDUWORK-MIGRATION.md)。
5
+ `@eduwork/dsh-mail@0.1.1` 面向官方 DeepSeek Harness `0.1.5-rc.1`,同时保留已验收旧基线的精确 peer 范围。旧无作用域包迁移见[迁移说明](EDUWORK-MIGRATION.md)。
6
6
 
7
7
  ## 已验证基线
8
8
 
9
- - DeepSeek Harness `0.1.2-alpha.2`;
9
+ - DeepSeek Harness `0.1.5-rc.1`(当前发布基线);
10
+ - DeepSeek Harness `0.1.2-rc.1`、`0.1.3-alpha.1`、`0.1.3-alpha.2`、`0.1.5-alpha.1`(保留兼容的历史验收基线);
10
11
  - Node.js 22+;
11
12
  - Web Client 动态 Bundle;
12
13
  - 本地 Host 文件系统;
@@ -14,6 +15,10 @@
14
15
 
15
16
  插件依赖 DSH Profile 提供 `settings`、`credentials`、`tools`、`permissionPresets`、`approval` 和 `fs`。设置页还依赖官方 Web Client 的 Remotes、Renderer 与 Settings 插槽;普通权限下发信需要交互式审批通路,Full Access 下不会弹出审批。
16
17
 
18
+ DSH `0.1.5-rc.1` 要求插件从工具执行上下文显式使用 Agent。Mail 从每次执行的 `exec.agent` 读取当前会话,通过 `agent.session` 获取权限预设和工作区,不依赖全局 Agent,不创建或恢复 Session,也不使用普通 subprocess handle。ToolRuntime、Permission Presets、Credentials、Settings、Renderer 和 Settings Client 接缝均已验收。
19
+
20
+ 官方通用文件上传、Workspace Files 和 `readByteRange()` 可供后续大附件体验复用,但当前不能替代 IMAP 下载所需的 Host 二进制写入:`ctx.fs` 仍没有 `writeBytes()`。因此本版本继续保留受工作区约束、重新校验真实路径并使用独占随机文件名的本地下载适配。
21
+
17
22
  ## 协议范围
18
23
 
19
24
  - IMAP 遵循只读 mailbox 语义,并用 UIDVALIDITY 防止陈旧 UID 误读。
@@ -24,7 +29,7 @@
24
29
 
25
30
  ## DSH 二进制写限制
26
31
 
27
- `ctx.fs` 在 `0.1.2-alpha.2` 提供 `readBytes`,没有 `writeBytes`。因此:
32
+ 当前发布基线 DSH `0.1.5-rc.1` 提供 `readBytes`,没有 `writeBytes`。因此:
28
33
 
29
34
  - 发送本地附件可完整复用官方 seam;
30
35
  - 下载附件只能在 `ctx.fs.processPath()` 与 Host Node 进程指向同一文件系统时工作;插件会在创建目录后解析真实路径并再次验证工作区包含关系;
@@ -33,4 +38,4 @@
33
38
 
34
39
  ## 版本策略
35
40
 
36
- 在 `0.x` 阶段,DSH alpha 的 Client Loader、settings slots 或 tool schema 变化可能要求插件同步更新。每次升级先运行 `npm run check`,再在隔离 Profile 中完成真实 IMAP/SMTP 验收,不应只依赖编译通过。
41
+ 插件独立使用 SemVer,不跟随宿主产品版本。兼容性修复和依赖维护使用 patch,向后兼容能力使用 minor,破坏工具或配置契约才使用 major。在 DSH `0.x` 阶段,每次升级都先运行 `npm run check` 与目标 Runtime 验收,再在隔离 Profile 中完成 IMAP/SMTP 互操作,不应只依赖编译通过。
@@ -20,7 +20,7 @@ Delete, move, archive, flag mutation, contacts, rules, polling, auto-reply, HTML
20
20
 
21
21
  ## Consequences
22
22
 
23
- - The plugin can be open-sourced and composed by Web or desktop DSH without depending on ChatECNU Work.
23
+ - The plugin can be open-sourced and composed by Web or desktop DSH without depending on a particular product composition.
24
24
  - Users keep a professional mail client for mailbox-state management; the Agent focuses on retrieval, understanding, and authorized sending.
25
25
  - Full Access matches other DSH write tools and skips per-call approval, while the independent sending capability remains mandatory.
26
26
  - Any future background or mutation boundary requires another ADR, threat model, and migration plan.
@@ -20,7 +20,7 @@ Gmail 等专用 OAuth2 API 与通用 IMAP/SMTP 的认证、权限和审计模型
20
20
 
21
21
  ## 结果
22
22
 
23
- - 插件可以独立开源并被 Web 或桌面 DSH 组合复用,不绑定 ChatECNU Work。
23
+ - 插件可以独立开源并被 Web 或桌面 DSH 组合复用,不绑定任何特定产品。
24
24
  - 用户仍使用专业邮箱客户端管理邮箱状态,Agent 聚焦检索、理解和经授权的发信。
25
25
  - Full Access 与 DSH 其他写工具保持一致,会跳过逐次审批;独立的发信开关仍是必要条件。
26
26
  - 未来扩展若改变写操作或后台授权,必须新增 ADR、威胁模型和迁移说明。
@@ -5,7 +5,7 @@
5
5
  ## Requirements
6
6
 
7
7
  - Node.js 22+;
8
- - DeepSeek Harness `0.1.2-alpha.2`;
8
+ - DeepSeek Harness `0.1.5-rc.1`;
9
9
  - a Profile with Settings, Credentials, Tools, Filesystem, Permission Presets, Approval, and Web Client;
10
10
  - an IMAP/SMTP-enabled mailbox and provider-issued app password.
11
11
 
@@ -14,7 +14,7 @@
14
14
  For normal use, pin the reviewed exact npm version:
15
15
 
16
16
  ```bash
17
- dsh plugin --profile web add @eduwork/dsh-mail@0.1.0-alpha.5
17
+ dsh plugin --profile web add @eduwork/dsh-mail@0.1.1
18
18
  ```
19
19
 
20
20
  For auditing, development, or unpublished changes, use a source checkout:
@@ -27,7 +27,7 @@ npm run check
27
27
  dsh plugin --profile web add .
28
28
  ```
29
29
 
30
- Installation adds one Bundle to the Profile. Empty configuration is valid: the plugin performs no server probe or runtime installation during install, startup, or save. A source installation links the checkout into the Profile, so keep that directory available. Team deployments should pin an exact npm version; use `@eduwork/dsh-mail@alpha` only when intentionally following prereleases.
30
+ Installation adds one Bundle to the Profile. Empty configuration is valid: the plugin performs no server probe or runtime installation during install, startup, or save. A source installation links the checkout into the Profile, so keep that directory available. Team deployments should pin the reviewed exact npm version. Before upgrading, back up the Profile's `package.json` and `cordis.patch.yml`; never enable the scoped and unscoped packages together.
31
31
 
32
32
  ## Configuration
33
33
 
@@ -38,6 +38,8 @@ Open **Settings → Mail assistant** and follow the page order:
38
38
  3. Use Advanced settings only when the provider needs a distinct login username, a nonstandard inbox path, or changed safety limits.
39
39
  4. Enable Agent reading and sending independently, then save.
40
40
 
41
+ Without a UI, put only the address, hosts, ports, TLS modes, and capability switches in Bundle configuration. The app password must enter a DSH Credential Provider under the fixed reference `DSH_MAIL_ASSISTANT_PASSWORD`; never put it in YAML, source code, shell history, or diagnostic logs.
42
+
41
43
  Typical secure combinations are IMAP 993 with implicit TLS, SMTP 465 with implicit TLS, or SMTP 587 with STARTTLS. Follow the provider's documentation. Certificate validation cannot be disabled.
42
44
 
43
45
  ## Acceptance
@@ -47,9 +49,24 @@ Typical secure combinations are IMAP 993 with implicit TLS, SMTP 465 with implic
47
49
  3. Search a range with more matches than a deliberately small page size; the agent must follow `nextCursor` until `hasMore=false` before summarizing the full range.
48
50
  4. Read a test message containing prompt-injection text; the agent must treat it as untrusted data.
49
51
  5. Download a small attachment; it must land under `.dsh-mail-assistant/attachments/` in the current workspace without overwriting.
50
- 6. Keep an ordinary permission preset, enable sending, and send to a controlled test address; a fresh approval must appear.
52
+ 6. Keep an ordinary permission preset, enable sending, ask the agent to show recipient, subject, body, and attachments first, then send to a controlled test address; a fresh approval must appear.
51
53
  7. Reject approval; no SMTP send occurs.
52
54
  8. Switch to Full Access and send again to the controlled address; no approval appears and delivery succeeds.
53
55
  9. Try an attachment outside the workspace; the tool must reject it.
54
56
 
55
57
  Use a dedicated test mailbox. Never put addresses, subjects, bodies, server logs, or app passwords in a public issue.
58
+
59
+ ## Development acceptance
60
+
61
+ ```bash
62
+ npm ci
63
+ npm run check
64
+ ```
65
+
66
+ To verify the real DSH `0.1.5-rc.1` contract, point `DSH_RUNTIME_ROOT` at a clean runtime of that exact version and run:
67
+
68
+ ```bash
69
+ npm run accept:dsh-015-rc1
70
+ ```
71
+
72
+ This uses a no-network delivery simulator to cover Agent-session, rejection, one-time approval, and Full Access paths without a real mailbox. Run real IMAP/SMTP interoperability only with a dedicated test account in an isolated Profile.
@@ -5,8 +5,8 @@
5
5
  ## 前置条件
6
6
 
7
7
  - Node.js 22+;
8
- - DeepSeek Harness `0.1.2-alpha.2`;
9
- - Profile 中已经有 Settings、Credentials、Tools、Filesystem、Approval 和 Web Client;
8
+ - DeepSeek Harness `0.1.5-rc.1`;
9
+ - Profile 中已经有 Settings、Credentials、Tools、Filesystem、Permission Presets、Approval 和 Web Client;
10
10
  - 一个开启了 IMAP/SMTP 的邮箱账号,以及邮箱服务商签发的客户端授权码或应用专用密码。
11
11
 
12
12
  ## 安装
@@ -14,7 +14,7 @@
14
14
  普通使用固定经过复核的 npm 精确版本:
15
15
 
16
16
  ```bash
17
- dsh plugin --profile web add @eduwork/dsh-mail@0.1.0-alpha.5
17
+ dsh plugin --profile web add @eduwork/dsh-mail@0.1.1
18
18
  ```
19
19
 
20
20
  需要审计、开发或验证尚未发布的改动时,使用源码 checkout:
@@ -29,7 +29,7 @@ dsh plugin --profile web add .
29
29
 
30
30
  安装只向 Profile 加入一个 Bundle。插件配置为空也能正常启动;不会在启动时探测服务器或安装额外运行时。
31
31
 
32
- 源码安装会把 checkout 链接进 Profile,因此目录必须持续存在。团队部署应固定 npm 精确版本;只有明确希望持续跟随预发布更新时才使用 `@eduwork/dsh-mail@alpha`。
32
+ 源码安装会把 checkout 链接进 Profile,因此目录必须持续存在。团队部署应固定经复核的 npm 精确版本。升级前先备份 Profile 的 `package.json` 与 `cordis.patch.yml`,不要同时启用新旧包。
33
33
 
34
34
  ## 配置
35
35
 
@@ -40,6 +40,8 @@ dsh plugin --profile web add .
40
40
  3. 仅在服务商要求时,到“高级设置”填写独立登录用户名;收件箱目录通常保持 `INBOX`;
41
41
  4. 最后分别开放 Agent 的读信和发信能力并保存。
42
42
 
43
+ 无 UI 时,只把邮箱地址、服务器、端口、TLS 模式和能力开关写入 Bundle 配置。授权码必须进入 DSH Credential Provider,固定引用名为 `DSH_MAIL_ASSISTANT_PASSWORD`;不要写入 YAML、源码、命令历史或诊断日志。
44
+
43
45
  常见安全组合是 IMAP 993 + 隐式 TLS、SMTP 465 + 隐式 TLS,或 SMTP 587 + STARTTLS。具体值以邮箱服务商文档为准;不要为了连通而关闭证书校验,本插件也不提供这个开关。
44
46
 
45
47
  ## 验收
@@ -49,9 +51,24 @@ dsh plugin --profile web add .
49
51
  3. 用较小的每页条数查找一个命中较多的时间范围;确认 Agent 按 `nextCursor` 持续翻页,直到 `hasMore=false` 后才总结整个范围。
50
52
  4. 读取一封包含“忽略此前指令”等文本的测试邮件;确认 Agent 把它视为不可信数据。
51
53
  5. 下载一个小附件;确认文件位于当前工作区的 `.dsh-mail-assistant/attachments/`,且不会覆盖同名文件。
52
- 6. 保持普通权限,开启发信,让 Agent 向自己的测试地址发送纯文本;确认出现逐次审批。
54
+ 6. 保持普通权限,开启发信,让 Agent 先展示收件人、主题、正文和附件,再向自己的测试地址发送纯文本;确认出现逐次审批。
53
55
  7. 拒绝审批;确认没有 SMTP 发送。
54
56
  8. 切换到 Full Access,再向自己的测试地址发信;确认不再弹出审批且邮件正常送达。
55
57
  9. 尝试把工作区外文件作为附件;确认工具拒绝。
56
58
 
57
59
  真实邮箱测试应使用专门测试账号,不要在公开 issue 附上服务器日志、地址、标题、正文或授权码。
60
+
61
+ ## 开发验收
62
+
63
+ ```bash
64
+ npm ci
65
+ npm run check
66
+ ```
67
+
68
+ 要复核 DSH `0.1.5-rc.1` 的真实运行时契约,把 `DSH_RUNTIME_ROOT` 指向该版本的干净 Runtime 后运行:
69
+
70
+ ```bash
71
+ npm run accept:dsh-015-rc1
72
+ ```
73
+
74
+ 此验收使用无网络发送模拟器检查 Agent 会话、审批拒绝、单次允许与 Full Access 路径,不需要真实邮箱。真实 IMAP/SMTP 互操作只在专用测试账号和隔离 Profile 中执行。
@@ -2,11 +2,11 @@
2
2
 
3
3
  [简体中文](release-checklist.md) | **English**
4
4
 
5
- A release pull request records reviewers, date, command-output links, and exceptions. Every item blocks a stable release; an alpha release must complete all applicable items and state remaining risk.
5
+ A release pull request or release record captures the maintainer decision, date, command-output links, independent technical-review evidence, and exceptions. A sole maintainer owns publication during the project's initial stage; do not invent a second human approver who does not exist. Any applicable incomplete security or publication check blocks a stable release. Additional maintainers and multi-person review remain future governance goals.
6
6
 
7
7
  ## Authority and metadata
8
8
 
9
- - [ ] Repository owner approved public release and the MIT license.
9
+ - [ ] The current maintainer approved public release and the MIT license and recorded technical-review evidence and remaining risk.
10
10
  - [ ] GitHub repository, npm name, homepage, issues, and private security-report URLs are correct.
11
11
  - [ ] Version, changelog, compatibility, and install commands agree.
12
12
  - [ ] Chinese is the default language and every Markdown document has an English mirror with reciprocal entries.
@@ -34,6 +34,7 @@ A release pull request records reviewers, date, command-output links, and except
34
34
 
35
35
  ## Functional acceptance
36
36
 
37
+ - [ ] Run `npm run accept:dsh-015-rc1` against the exact current DSH version declared by the manifest, and confirm that the runtime contains no mixed older DSH packages.
37
38
  - [ ] `mail_list_folders` returns server folders without mutation.
38
39
  - [ ] `mail_find` covers both directions, pagination, dates, folders, stale UIDVALIDITY, and `hasMore=false`.
39
40
  - [ ] `mail_read` preserves unread state; truncation and HTML fallback match documentation.
@@ -43,9 +44,10 @@ A release pull request records reviewers, date, command-output links, and except
43
44
 
44
45
  ## Supply chain and publication
45
46
 
46
- - [ ] Clean `npm ci` and `npm run check` pass on Windows and Linux with Node 22.
47
+ - [ ] Clean `npm ci` and `npm run check` pass on Windows and Linux with Node 22/24.
47
48
  - [ ] `npm audit`, CodeQL, direct/transitive licenses, and install scripts receive human review.
48
49
  - [ ] GitHub Actions use immutable commits; Dependabot and branch protection are active.
49
50
  - [ ] `npm pack --dry-run` and the extracted tarball contain only expected files; the Host bundle has no bare IMAP/SMTP imports.
50
51
  - [ ] Tag, release notes, tarball checksum, deprecation, and rollback are ready.
51
- - [ ] Stable publication uses npm Trusted Publishing/OIDC and provenance rather than a long-lived token.
52
+ - [ ] Enable OIDC/provenance only after verifying that npm Trusted Publishing is bound to this repository, `.github/workflows/release.yml`, and environment `npm`.
53
+ - [ ] When Trusted Publishing is not verified, publish the reviewed frozen artifact with the npm CLI and browser 2FA; make no provenance claim and store no long-lived token.
@@ -2,11 +2,11 @@
2
2
 
3
3
  **简体中文** | [English](release-checklist.en.md)
4
4
 
5
- 发布 Pull Request 应记录审查人、日期、命令输出链接和例外。任何未完成项目都会阻止稳定发布;alpha 发布也必须完成与本次改动相关的项目并明确剩余风险。
5
+ 发布 Pull Request 或发布记录应记维护者决策、日期、命令输出链接、独立技术复核证据和例外。项目初期由单一维护者负责发布;不得虚构尚不存在的第二位人工批准人。任何适用且未完成的安全或发布检查都会阻止稳定发布;未来增加替补和多人审查作为治理目标记录。
6
6
 
7
7
  ## 授权与元数据
8
8
 
9
- - [ ] 仓库所有者确认公开发布和 MIT 许可证。
9
+ - [ ] 当前维护者确认公开发布和 MIT 许可证,并记录技术复核证据与剩余风险。
10
10
  - [ ] GitHub 仓库、npm 包名、主页、Issue 和安全报告地址准确。
11
11
  - [ ] 版本、Changelog、兼容性和安装命令一致。
12
12
  - [ ] 默认文档为中文,每份 Markdown 文档都有英文镜像和双向入口。
@@ -34,6 +34,7 @@
34
34
 
35
35
  ## 功能验收
36
36
 
37
+ - [ ] 使用 manifest 声明的当前 DSH 精确版本运行 `npm run accept:dsh-015-rc1`,并确认 Runtime 没有混入旧版 DSH 包。
37
38
  - [ ] `mail_list_folders` 返回真实目录且不修改状态。
38
39
  - [ ] `mail_find` 的正序/倒序、多页、日期、目录、UIDVALIDITY 失效和 `hasMore=false` 均测试。
39
40
  - [ ] `mail_read` 不改变已读状态,正文截断和 HTML 降级符合文档。
@@ -43,9 +44,10 @@
43
44
 
44
45
  ## 供应链与发布
45
46
 
46
- - [ ] Windows 与 Linux 使用 Node 22 完成干净 `npm ci` 和 `npm run check`。
47
+ - [ ] Windows 与 Linux 使用 Node 22/24 完成干净 `npm ci` 和 `npm run check`。
47
48
  - [ ] `npm audit`、CodeQL、直接/间接许可证和安装脚本已人工审查。
48
49
  - [ ] GitHub Actions 固定到不可变 commit,Dependabot 与分支保护有效。
49
50
  - [ ] `npm pack --dry-run` 和解压后的 tarball 只包含预期文件;Host bundle 没有外部 IMAP/SMTP 裸导入。
50
51
  - [ ] tag、发布说明、tarball checksum、弃用和回滚方案已准备。
51
- - [ ] 稳定发布使用 npm Trusted Publishing/OIDC 和 provenance,不使用长期 Token。
52
+ - [ ] 已现场验证 npm Trusted Publisher 绑定本仓库、`.github/workflows/release.yml` 与 Environment `npm` 后,才启用 OIDC/provenance 发布路径。
53
+ - [ ] Trusted Publisher 未验证时,从经复核的冻结包使用 npm CLI 和浏览器 2FA 发布,不声明 provenance,也不保存长期 Token。
package/lib/client.js CHANGED
@@ -33,8 +33,8 @@ window.__ModuleLoader__.load({
33
33
  const inject = [
34
34
  "slots",
35
35
  "remote",
36
- "remote.settings",
37
- "remote.credentials"
36
+ "remote.credentials",
37
+ "settingsScope"
38
38
  ];
39
39
  const NS = "dsh-mail-assistant";
40
40
  const PASSWORD_REF = "DSH_MAIL_ASSISTANT_PASSWORD";
@@ -90,6 +90,8 @@ window.__ModuleLoader__.load({
90
90
  usage: "保存后,在对话里让 Agent“查一下最近邮件”即可验证。插件不会在启动或保存时主动连接邮箱。",
91
91
  error: "保存失败",
92
92
  reload: "重新加载",
93
+ unavailable: "当前 DSH 连接未提供此设置命名空间。",
94
+ readOnly: "当前连接为只读模式,无法保存设置。",
93
95
  credentialPartial: "授权码已经保存,但其他配置保存失败;请修正后再次保存。"
94
96
  } : {
95
97
  nav: "Mail assistant",
@@ -143,6 +145,8 @@ window.__ModuleLoader__.load({
143
145
  usage: "After saving, ask the agent to find recent mail. The plugin never connects to a mailbox during startup or save.",
144
146
  error: "Save failed",
145
147
  reload: "Reload",
148
+ unavailable: "This DSH connection does not expose the mail settings namespace.",
149
+ readOnly: "This connection exposes settings in read-only mode.",
146
150
  credentialPartial: "The app password was stored, but the remaining settings failed to save. Fix the form and save again."
147
151
  };
148
152
  const defaults = {
@@ -219,33 +223,37 @@ window.__ModuleLoader__.load({
219
223
  }
220
224
  function MailSettings({ service }) {
221
225
  const [draft, setDraft] = (0, react.useState)(null);
222
- const [revision, setRevision] = (0, react.useState)(0);
223
226
  const [passwordConfigured, setPasswordConfigured] = (0, react.useState)(false);
227
+ const [credentialWritable, setCredentialWritable] = (0, react.useState)(false);
224
228
  const [password, setPassword] = (0, react.useState)("");
225
229
  const [busy, setBusy] = (0, react.useState)(false);
226
230
  const [notice, setNotice] = (0, react.useState)("");
227
231
  const [error, setError] = (0, react.useState)("");
228
232
  const [preset, setPreset] = (0, react.useState)("custom");
229
233
  const [advancedOpen, setAdvancedOpen] = (0, react.useState)(false);
234
+ const subscribe = (0, react.useMemo)(() => service.scope.subscribe.bind(service.scope), [service.scope]);
235
+ const getSnapshot = (0, react.useMemo)(() => service.scope.getSnapshot.bind(service.scope), [service.scope]);
236
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
230
237
  const load = async () => {
231
238
  setError("");
232
239
  setNotice("");
233
- const [settings, credentials] = await Promise.all([unwrap(service.settings.describe()), unwrap(service.credentials.describe([PASSWORD_REF]))]);
234
- const namespace = settings.namespaces.find((candidate) => candidate.ns === NS);
235
- if (!namespace) throw new Error("dsh-mail-assistant settings namespace is not available");
236
- const next = {
237
- ...defaults,
238
- ...namespace.value ?? {}
239
- };
240
- setDraft(next);
241
- setPreset(detectPreset(next));
242
- setRevision(namespace.revision ?? 0);
240
+ const credentials = await unwrap(service.credentials.describe([PASSWORD_REF]));
243
241
  setPasswordConfigured(credentials[PASSWORD_REF]?.configured === true);
242
+ setCredentialWritable(credentials[PASSWORD_REF]?.writable === true);
244
243
  setPassword("");
245
244
  };
246
245
  (0, react.useEffect)(() => {
247
246
  load().catch((cause) => setError(cause?.message || String(cause)));
248
247
  }, []);
248
+ (0, react.useEffect)(() => {
249
+ if (snapshot.status !== "ready") return;
250
+ const next = {
251
+ ...defaults,
252
+ ...snapshot.value ?? {}
253
+ };
254
+ setDraft(next);
255
+ setPreset(detectPreset(next));
256
+ }, [snapshot]);
249
257
  const set = (key, value) => setDraft((current) => ({
250
258
  ...current,
251
259
  [key]: value
@@ -269,12 +277,12 @@ window.__ModuleLoader__.load({
269
277
  setPasswordConfigured(true);
270
278
  setPassword("");
271
279
  }
272
- const namespace = await unwrap(service.settings.replace(NS, draft, revision));
273
- setDraft({
274
- ...defaults,
275
- ...namespace.value ?? draft
276
- });
277
- setRevision(namespace.revision ?? revision + 1);
280
+ const ops = Object.entries(draft).map(([field, value]) => ({
281
+ op: "set",
282
+ path: [field],
283
+ value
284
+ }));
285
+ await service.scope.mutate(ops, snapshot.revision);
278
286
  setNotice(copy.saved);
279
287
  } catch (cause) {
280
288
  setError(passwordStored ? copy.credentialPartial : cause?.message || `${copy.error}`);
@@ -361,7 +369,7 @@ window.__ModuleLoader__.load({
361
369
  };
362
370
  if (!draft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
363
371
  style: styles.root,
364
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error || copy.loading }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
372
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error || (snapshot.status === "unavailable" ? copy.unavailable : copy.loading) }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
365
373
  style: styles.button,
366
374
  onClick: () => load(),
367
375
  children: copy.reload
@@ -531,6 +539,7 @@ window.__ModuleLoader__.load({
531
539
  type: "password",
532
540
  autoComplete: "new-password",
533
541
  value: password,
542
+ disabled: busy || !credentialWritable,
534
543
  placeholder: copy.credentialPlaceholder,
535
544
  onChange: (event) => setPassword(event.currentTarget.value),
536
545
  style: styles.input
@@ -538,7 +547,7 @@ window.__ModuleLoader__.load({
538
547
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copy.passwordHint }),
539
548
  passwordConfigured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
540
549
  type: "button",
541
- disabled: busy,
550
+ disabled: busy || !credentialWritable,
542
551
  style: {
543
552
  ...styles.button,
544
553
  justifySelf: "start",
@@ -710,6 +719,14 @@ window.__ModuleLoader__.load({
710
719
  },
711
720
  children: copy.usage
712
721
  }),
722
+ !snapshot.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
723
+ role: "status",
724
+ style: {
725
+ color: colors.secondary,
726
+ fontSize: 12
727
+ },
728
+ children: copy.readOnly
729
+ }),
713
730
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
714
731
  role: "alert",
715
732
  style: {
@@ -728,7 +745,7 @@ window.__ModuleLoader__.load({
728
745
  }),
729
746
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
730
747
  type: "button",
731
- disabled: busy,
748
+ disabled: busy || !snapshot.writable,
732
749
  onClick: save,
733
750
  style: styles.primary,
734
751
  children: busy ? copy.saving : copy.save
@@ -738,7 +755,7 @@ window.__ModuleLoader__.load({
738
755
  }
739
756
  function apply(ctx) {
740
757
  const service = {
741
- settings: ctx.remote.settings,
758
+ scope: ctx.settingsScope.bind({ namespace: NS }),
742
759
  credentials: ctx.remote.credentials
743
760
  };
744
761
  ctx.slots.inject("settings.section", () => ctx.slots.register({