dsh-email 0.9.0 → 0.9.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.
package/README.en.md CHANGED
@@ -26,7 +26,7 @@ Pure Node, **cross-platform** (one codebase for Windows / macOS / Linux), no she
26
26
 
27
27
  Once an account is configured, a "whale-girl courier" popup lives in the bottom-right corner of the main UI: it checks for new mail every 30 seconds and shows a card (sender + subject) when something arrives, auto-dismissing after 12 seconds. The popup shares the cursor logic with `email_watch` but keeps its own counter, so they never consume each other's mail.
28
28
 
29
- The popup artwork prefers the locally installed [dsh-deep-whale](https://github.com/Small-tailqwq/dsh-deep-whale) whale-girl skin assets (**never bundled** — read at runtime from your own installation): the artwork is a derivative of the original whale-girl character by [上善](https://www.pixiv.net/users/62155430) (skin by Small-tailqwq), published under CC BY-NC-SA 4.0 (Attribution-NonCommercial-ShareAlike); the popup shows the full attribution chain. A built-in fallback image is used when no skin is installed.
29
+ The popup artwork prefers the locally installed [dsh-deep-whale](https://github.com/Small-tailqwq/dsh-deep-whale) whale-girl skin assets (**never bundled** — read at runtime from your own installation): the artwork is a derivative of the original whale-girl character by [上善](https://www.pixiv.net/users/62155430) (skin by Small-tailqwq), published under CC BY-NC-SA 4.0 (Attribution-NonCommercial-ShareAlike); the popup shows the full attribution chain. Without the skin, a bundled community whale-girl artwork is used (copyright stays with the original author, personal non-commercial use only; removed on request via issue).
30
30
 
31
31
  Example:
32
32
 
@@ -42,7 +42,7 @@ Example:
42
42
 
43
43
  ## Compatibility
44
44
 
45
- Verified against `@deepseek-ai/dsh@0.1.2-alpha.2` on 2026-08-31. Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
45
+ Verified against `@deepseek-ai/dsh@0.1.2-alpha.3` on 2026-09-01 (settings page / asset route / email_watch fully regression-tested). Built for the cordis patch-bundle plugin model (`cordis.patch.yml` + `dsh.bundle.patch`). No runtime imports of `@deepseek-ai/*` internals.
46
46
 
47
47
  ## Installation
48
48
 
package/README.md CHANGED
@@ -34,7 +34,7 @@ Email tools for DeepSeek Harness: list, read, search and send mail through stand
34
34
 
35
35
  配置好账号后,主界面右下角会出现「鲸鱼娘递信」小弹窗:每 30 秒检查一次新邮件,有新邮件时弹出卡片(发件人 + 主题),12 秒自动消失。弹窗与 `email_watch` 工具共用同一套游标逻辑但各自独立计数,互不抢占。
36
36
 
37
- 弹窗形象优先使用本地安装的 [dsh-deep-whale](https://github.com/Small-tailqwq/dsh-deep-whale) 鲸鱼娘皮肤素材(**不打包分发**,运行时从你自己的安装目录读取):该素材为一创 [上善](https://www.pixiv.net/users/62155430) 鲸鱼娘形象的衍生创作(二创 Small-tailqwq),以 CC BY-NC-SA 4.0(署名-非商业性使用-相同方式共享)发布,弹窗内附完整署名链。未安装皮肤时使用内置回退图。
37
+ 弹窗形象优先使用本地安装的 [dsh-deep-whale](https://github.com/Small-tailqwq/dsh-deep-whale) 鲸鱼娘皮肤素材(**不打包分发**,运行时从你自己的安装目录读取):该素材为一创 [上善](https://www.pixiv.net/users/62155430) 鲸鱼娘形象的衍生创作(二创 Small-tailqwq),以 CC BY-NC-SA 4.0(署名-非商业性使用-相同方式共享)发布,弹窗内附完整署名链。未安装皮肤时使用内置的社区鲸鱼娘形象(版权归原作者,仅供个人非商业使用;如有异议请提 Issue,会立即移除)。
38
38
 
39
39
  示例对话:
40
40
 
@@ -42,6 +42,7 @@ Email tools for DeepSeek Harness: list, read, search and send mail through stand
42
42
 
43
43
  ### 版本记录
44
44
 
45
+ - **0.9.1**:修复设置页空主机遮蔽 provider 预设(#3/#6);IMAP 连接超时不再杀死整个 DSH 进程(#4);暗色模式输入控件可见(#2);密码栏提示环境变量 `DSH_EMAIL_PASSWORD` 免明文方案(#5)。
45
46
  - **0.9.0**:新增 `email_watch` 增量新邮件检查工具(游标式,适合定时提醒);Web 端新增「鲸鱼娘递信」新邮件弹窗(本地皮肤素材运行时读取 + 内置回退图)。
46
47
  - **0.8.2**:`since` / `until` 参数描述与其余参数统一为英文,方便多语言 agent 理解。
47
48
  - **0.8.0/0.8.1**:`email_list` / `email_search` 新增 `since` / `until` 日期范围过滤;新增 `email_health` 自检(账号/连接/配置一键体检);适配 harness 0.1.2(清理已删除的客户端注入声明)。
@@ -50,7 +51,7 @@ Email tools for DeepSeek Harness: list, read, search and send mail through stand
50
51
 
51
52
  ## 兼容性
52
53
 
53
- 在 `@deepseek-ai/dsh@0.1.2-alpha.2` 上验证(2026-08-31)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
54
+ 在 `@deepseek-ai/dsh@0.1.2-alpha.3` 上实测通过(2026-09-01,设置页/素材路由/email_watch 全回归)。遵循 cordis 组合包补丁模型(`cordis.patch.yml` + `dsh.bundle.patch`),运行时不 import 任何 `@deepseek-ai/*` 内部模块。
54
55
 
55
56
  ## 安装
56
57
 
Binary file
package/lib/client.js CHANGED
@@ -51,7 +51,7 @@ const EMPTY = {
51
51
  };
52
52
 
53
53
  const CSS = [
54
- ".dshe-settings{display:grid;gap:14px;max-width:900px;padding:8px 2px 32px;color:var(--dsw-alias-fg-primary,#26231f)}",
54
+ ".dshe-settings{display:grid;gap:14px;max-width:900px;padding:8px 2px 32px;color:var(--dsw-alias-fg-primary,#26231f);color-scheme:light dark}",
55
55
  ".dshe-header{display:grid;gap:4px;padding:8px 2px}",
56
56
  ".dshe-header h2{font-size:22px;letter-spacing:-.02em;margin:0}",
57
57
  ".dshe-header p{max-width:640px;margin:4px 0 0;color:var(--dsw-alias-fg-muted,#77736d);font-size:13px;line-height:1.55}",
@@ -60,7 +60,8 @@ const CSS = [
60
60
  ".dshe-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}",
61
61
  ".dshe-field{display:grid;gap:6px}",
62
62
  ".dshe-field label{font-size:12px;font-weight:600;color:var(--dsw-alias-fg-primary,#26231f)}",
63
- ".dshe-field input[type=text],.dshe-field input[type=password],.dshe-field input[type=number],.dshe-field select{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-subtle,#dedbd5);border-radius:9px;background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;font:inherit;font-size:13px}",
63
+ ".dshe-field input[type=text],.dshe-field input[type=password],.dshe-field input[type=number],.dshe-field select,.dshe-field textarea{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-subtle,#dedbd5);border-radius:9px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-fg-primary,#26231f);font:inherit;font-size:13px}",
64
+ ".dshe-field select option{background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-fg-primary,#26231f)}",
64
65
  ".dshe-check{display:flex;gap:8px;align-items:center;font-size:13px}",
65
66
  ".dshe-actions{display:flex;gap:8px;flex-wrap:wrap}",
66
67
  ".dshe-btn{display:inline-flex;align-items:center;height:32px;padding:0 14px;border-radius:999px;border:1px solid var(--dsw-alias-border-subtle,#dedbd5);background:var(--dsw-alias-bg-layer-1,#fff);color:inherit;font-size:13px;font-weight:600;cursor:pointer}",
@@ -73,7 +74,7 @@ const CSS = [
73
74
  ".dshe-details summary{font-size:12px;font-weight:600;cursor:pointer;color:var(--dsw-alias-fg-muted,#77736d)}",
74
75
  ".dshe-hint{font-size:12px;color:var(--dsw-alias-fg-muted,#77736d);line-height:1.5}",
75
76
  ".dshe-whale-root{position:fixed;right:20px;bottom:20px;z-index:2147483000;pointer-events:none}",
76
- ".dshe-whale-card{pointer-events:auto;position:relative;width:300px;border:1px solid var(--dsw-alias-border-subtle,#dedbd5);border-radius:14px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 10px 30px rgba(0,0,0,.18);overflow:hidden;animation:dshe-whale-in .35s ease-out;font-size:13px;color:var(--dsw-alias-fg-primary,#26231f)}",
77
+ ".dshe-whale-card{pointer-events:auto;position:relative;width:300px;border:1px solid var(--dsw-alias-border-subtle,#dedbd5);border-radius:14px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 10px 30px rgba(0,0,0,.18);overflow:hidden;animation:dshe-whale-in .35s ease-out;font-size:13px;color:var(--dsw-alias-fg-primary,#26231f);color-scheme:light dark}",
77
78
  "@keyframes dshe-whale-in{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}",
78
79
  ".dshe-whale-img{display:block;width:100%;height:120px;object-fit:cover;object-position:center top;background:#eaf3f8}",
79
80
  ".dshe-whale-body{padding:10px 12px 12px;display:grid;gap:6px}",
@@ -219,6 +220,7 @@ function EmailSettingsSection() {
219
220
  placeholder: "不是登录密码;QQ/163 在邮箱设置里生成授权码",
220
221
  onChange: (e) => update({ password: e.target.value }),
221
222
  }),
223
+ h("div", { className: "dshe-hint" }, "不想明文保存?此栏留空,改用环境变量 DSH_EMAIL_PASSWORD(仅单账号时生效)。"),
222
224
  ]),
223
225
  h("div", { className: "dshe-field" }, [
224
226
  h("label", null, "收件文件夹(默认 INBOX)"),
@@ -120,7 +120,7 @@ export class EmailPool {
120
120
  return this.enqueue(name, () => this.imapRun(name, cfg, folder, run));
121
121
  }
122
122
  createImap(cfg) {
123
- return new ImapFlow({
123
+ const client = new ImapFlow({
124
124
  host: cfg.imap.host,
125
125
  port: cfg.imap.port,
126
126
  secure: cfg.imap.secure,
@@ -130,6 +130,19 @@ export class EmailPool {
130
130
  greetingTimeout: 30000,
131
131
  socketTimeout: cfg.imap.socketTimeoutMs ?? 60000,
132
132
  });
133
+ // ImapFlow emits 'error' on socket timeouts/drops; without a listener Node
134
+ // escalates it to an uncaught exception and kills the whole DSH process
135
+ // (issue #4). Swallow it here and reap the dead connection when idle —
136
+ // in-flight calls fail through their own promise paths instead.
137
+ client.on('error', () => {
138
+ for (const [name, entry] of this.imaps) {
139
+ if (entry.client === client && entry.inUse === 0) {
140
+ void this.evictImap(name);
141
+ return;
142
+ }
143
+ }
144
+ });
145
+ return client;
133
146
  }
134
147
  async imapRun(name, cfg, folder, run) {
135
148
  let entry = this.imaps.get(name);
package/lib/settings.js CHANGED
@@ -83,7 +83,9 @@ export function toEmailConfig(value, user) {
83
83
  if (user === null || user?.imap !== undefined) {
84
84
  const fields = user === null ? value.imap : (user.imap ?? {});
85
85
  const imap = {};
86
- if (fields.host !== undefined)
86
+ // Empty host means "use the provider preset" — never project it, or the
87
+ // preset gets shadowed by '' (issues #3 / #6).
88
+ if (fields.host !== undefined && value.imap.host !== '')
87
89
  imap.host = value.imap.host;
88
90
  if (fields.port !== undefined)
89
91
  imap.port = value.imap.port;
@@ -94,7 +96,7 @@ export function toEmailConfig(value, user) {
94
96
  if (user === null || user?.smtp !== undefined) {
95
97
  const fields = user === null ? value.smtp : (user.smtp ?? {});
96
98
  const smtp = {};
97
- if (fields.host !== undefined)
99
+ if (fields.host !== undefined && value.smtp.host !== '')
98
100
  smtp.host = value.smtp.host;
99
101
  if (fields.port !== undefined)
100
102
  smtp.port = value.smtp.port;
package/lib/web.js CHANGED
@@ -13,6 +13,8 @@ const SKIN_PACKAGES = [
13
13
  '@dsh-external/dsh-client-ui-skin-maid-atelier',
14
14
  ];
15
15
  const WHALE_CREDIT = '鲸鱼娘:一创 上善(pixiv 62155430)· 二创 Small-tailqwq / dsh-deep-whale · CC BY-NC-SA 4.0(非商业)';
16
+ /** Credit shown with the bundled fallback artwork (community fan character). */
17
+ const FALLBACK_CREDIT = '鲸鱼娘:社区同人形象,版权归原作者,仅供个人非商业使用;如有异议请提 Issue 移除';
16
18
  let whaleCache;
17
19
  function pickFromDir(dir) {
18
20
  let names = [];
@@ -36,10 +38,12 @@ function contentTypeOf(file) {
36
38
  return 'image/png';
37
39
  }
38
40
  /**
39
- * Locate the whale-girl artwork at runtime. It is never bundled: the skin art
40
- * is CC BY-NC-SA 4.0, so dsh-email only serves it from the user's own
41
- * installed dsh-deep-whale skin package (with the attribution chain shown in
42
- * the widget); otherwise the bundled MIT fallback is served.
41
+ * Locate the whale-girl artwork at runtime. Skin art is never bundled: it is
42
+ * CC BY-NC-SA 4.0, so dsh-email only serves it from the user's own installed
43
+ * dsh-deep-whale skin package (with the attribution chain shown in the
44
+ * widget). Without the skin, a bundled community whale-girl artwork is
45
+ * served with its own credit line (copyright stays with the original author;
46
+ * personal non-commercial use, removed on request via issue).
43
47
  */
44
48
  function findWhaleAsset() {
45
49
  if (whaleCache !== undefined)
@@ -60,7 +64,7 @@ function findWhaleAsset() {
60
64
  }
61
65
  const fallback = join(packageDir, 'assets', 'whale-fallback.png');
62
66
  if (existsSync(fallback)) {
63
- whaleCache = { file: fallback, contentType: 'image/png', skin: false, credit: '' };
67
+ whaleCache = { file: fallback, contentType: 'image/png', skin: false, credit: FALLBACK_CREDIT };
64
68
  return whaleCache;
65
69
  }
66
70
  return whaleCache;
@@ -130,6 +134,16 @@ export class EmailSettingsBackend {
130
134
  await pool.withImap(settings.defaultAccount, null, async () => 'connected');
131
135
  return { ok: true, ms: Date.now() - started };
132
136
  }
137
+ catch (error) {
138
+ // imapflow reports failed LOGIN as a bare "Command failed"; surface an
139
+ // actionable hint instead of the opaque message.
140
+ const raw = messageOf(error, 'unknown error');
141
+ const lower = raw.toLowerCase();
142
+ if (lower.includes('command failed') || lower.includes('authentication') || lower.includes('login')) {
143
+ throw new Error('邮箱登录失败:请检查邮箱地址与授权码(' + raw + ')');
144
+ }
145
+ throw error;
146
+ }
133
147
  finally {
134
148
  pool.dispose();
135
149
  }
@@ -231,8 +245,8 @@ export class EmailSettingsBackend {
231
245
  res.setHeader('Content-Type', asset.contentType);
232
246
  res.setHeader('Content-Length', String(bytes.length));
233
247
  res.setHeader('Cache-Control', 'no-store');
234
- if (asset.credit !== '')
235
- res.setHeader('X-Whale-Credit', asset.credit);
248
+ // No credit header: HTTP header values must be latin-1, and the widget
249
+ // already shows the credit from the snapshot JSON.
236
250
  res.writeHead(200);
237
251
  res.end(bytes);
238
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-email",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "IMAP/SMTP email tools for DeepSeek Harness: list, read, search and send mail, with QQ/163/126/Sina/Aliyun/Gmail/Outlook/iCloud presets.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",