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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.2-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.0
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 精确版本。
33
33
 
34
34
  ## 配置
35
35
 
@@ -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.
@@ -43,9 +43,10 @@ A release pull request records reviewers, date, command-output links, and except
43
43
 
44
44
  ## Supply chain and publication
45
45
 
46
- - [ ] Clean `npm ci` and `npm run check` pass on Windows and Linux with Node 22.
46
+ - [ ] Clean `npm ci` and `npm run check` pass on Windows and Linux with Node 22/24.
47
47
  - [ ] `npm audit`, CodeQL, direct/transitive licenses, and install scripts receive human review.
48
48
  - [ ] GitHub Actions use immutable commits; Dependabot and branch protection are active.
49
49
  - [ ] `npm pack --dry-run` and the extracted tarball contain only expected files; the Host bundle has no bare IMAP/SMTP imports.
50
50
  - [ ] 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.
51
+ - [ ] Publish the first stable release from the reviewed frozen package with the npm CLI and maintainer-confirmed browser 2FA; record that Trusted Publishing is not configured and make no provenance claim for this release.
52
+ - [ ] For later releases, enable OIDC/provenance only after verifying that npm Trusted Publishing is bound to this repository's `.github/workflows/release.yml` and environment `npm`; never use a long-lived publish 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 文档都有英文镜像和双向入口。
@@ -43,9 +43,10 @@
43
43
 
44
44
  ## 供应链与发布
45
45
 
46
- - [ ] Windows 与 Linux 使用 Node 22 完成干净 `npm ci` 和 `npm run check`。
46
+ - [ ] Windows 与 Linux 使用 Node 22/24 完成干净 `npm ci` 和 `npm run check`。
47
47
  - [ ] `npm audit`、CodeQL、直接/间接许可证和安装脚本已人工审查。
48
48
  - [ ] GitHub Actions 固定到不可变 commit,Dependabot 与分支保护有效。
49
49
  - [ ] `npm pack --dry-run` 和解压后的 tarball 只包含预期文件;Host bundle 没有外部 IMAP/SMTP 裸导入。
50
50
  - [ ] tag、发布说明、tarball checksum、弃用和回滚方案已准备。
51
- - [ ] 稳定发布使用 npm Trusted Publishing/OIDC 和 provenance,不使用长期 Token。
51
+ - [ ] 首次稳定版从经复核的冻结包使用 npm CLI 发布,由维护者在浏览器完成 2FA;本次记录 Trusted Publishing 尚未配置,不声明 provenance。
52
+ - [ ] 后续发布在验证 npm Trusted Publisher 已绑定本仓库的 `.github/workflows/release.yml` 与 Environment `npm` 后,再启用 OIDC/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({