dsh-connect 0.8.0 → 0.9.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.
Files changed (94) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +134 -17
  3. package/README.zh.md +110 -17
  4. package/client/client.js +510 -80
  5. package/client/client.js.map +4 -4
  6. package/client/locale.mjs +227 -0
  7. package/client/panel-state.mjs +54 -0
  8. package/client/settings-client.mjs +272 -60
  9. package/docs/images/settings-advanced-en.png +0 -0
  10. package/docs/images/settings-advanced-zh.png +0 -0
  11. package/docs/images/settings-defaults-en.png +0 -0
  12. package/docs/images/settings-defaults-zh.png +0 -0
  13. package/docs/images/settings-overview-en.png +0 -0
  14. package/docs/images/settings-overview-zh.png +0 -0
  15. package/lib/binding.d.ts.map +1 -1
  16. package/lib/binding.js +2 -2
  17. package/lib/binding.js.map +1 -1
  18. package/lib/channels/dingtalk/message.d.ts.map +1 -1
  19. package/lib/channels/dingtalk/message.js +7 -0
  20. package/lib/channels/dingtalk/message.js.map +1 -1
  21. package/lib/channels/feishu/adapter.d.ts +6 -12
  22. package/lib/channels/feishu/adapter.d.ts.map +1 -1
  23. package/lib/channels/feishu/adapter.js +2 -17
  24. package/lib/channels/feishu/adapter.js.map +1 -1
  25. package/lib/channels/feishu/i18n.d.ts +2 -0
  26. package/lib/channels/feishu/i18n.d.ts.map +1 -1
  27. package/lib/channels/feishu/i18n.js +2 -0
  28. package/lib/channels/feishu/i18n.js.map +1 -1
  29. package/lib/channels/feishu/index.d.ts +23 -2
  30. package/lib/channels/feishu/index.d.ts.map +1 -1
  31. package/lib/channels/feishu/index.js +32 -6
  32. package/lib/channels/feishu/index.js.map +1 -1
  33. package/lib/channels/telegram/adapter.d.ts.map +1 -1
  34. package/lib/channels/telegram/adapter.js +7 -1
  35. package/lib/channels/telegram/adapter.js.map +1 -1
  36. package/lib/chat-key.d.ts +29 -0
  37. package/lib/chat-key.d.ts.map +1 -0
  38. package/lib/chat-key.js +38 -0
  39. package/lib/chat-key.js.map +1 -0
  40. package/lib/index.d.ts +15 -5
  41. package/lib/index.d.ts.map +1 -1
  42. package/lib/index.js +201 -31
  43. package/lib/index.js.map +1 -1
  44. package/lib/runner.d.ts +54 -0
  45. package/lib/runner.d.ts.map +1 -1
  46. package/lib/runner.js +158 -23
  47. package/lib/runner.js.map +1 -1
  48. package/lib/scheduler.d.ts.map +1 -1
  49. package/lib/scheduler.js +2 -2
  50. package/lib/scheduler.js.map +1 -1
  51. package/lib/service.d.ts +16 -0
  52. package/lib/service.d.ts.map +1 -1
  53. package/lib/service.js +46 -2
  54. package/lib/service.js.map +1 -1
  55. package/lib/settings/channel-runtime.d.ts +75 -0
  56. package/lib/settings/channel-runtime.d.ts.map +1 -0
  57. package/lib/settings/channel-runtime.js +165 -0
  58. package/lib/settings/channel-runtime.js.map +1 -0
  59. package/lib/settings/channels.d.ts +11 -0
  60. package/lib/settings/channels.d.ts.map +1 -1
  61. package/lib/settings/channels.js +31 -0
  62. package/lib/settings/channels.js.map +1 -1
  63. package/lib/settings/credential-store.d.ts +52 -6
  64. package/lib/settings/credential-store.d.ts.map +1 -1
  65. package/lib/settings/credential-store.js +92 -18
  66. package/lib/settings/credential-store.js.map +1 -1
  67. package/lib/settings/namespace.d.ts +175 -0
  68. package/lib/settings/namespace.d.ts.map +1 -0
  69. package/lib/settings/namespace.js +200 -0
  70. package/lib/settings/namespace.js.map +1 -0
  71. package/lib/settings/secret-disclosure.d.ts +50 -0
  72. package/lib/settings/secret-disclosure.d.ts.map +1 -0
  73. package/lib/settings/secret-disclosure.js +132 -0
  74. package/lib/settings/secret-disclosure.js.map +1 -0
  75. package/lib/settings/settings-model.d.ts +17 -0
  76. package/lib/settings/settings-model.d.ts.map +1 -1
  77. package/lib/settings/settings-model.js +8 -0
  78. package/lib/settings/settings-model.js.map +1 -1
  79. package/lib/settings/settings-rpc.d.ts +71 -3
  80. package/lib/settings/settings-rpc.d.ts.map +1 -1
  81. package/lib/settings/settings-rpc.js +167 -24
  82. package/lib/settings/settings-rpc.js.map +1 -1
  83. package/lib/settings/settings-service.d.ts +9 -1
  84. package/lib/settings/settings-service.d.ts.map +1 -1
  85. package/lib/settings/settings-service.js +74 -6
  86. package/lib/settings/settings-service.js.map +1 -1
  87. package/lib/state-dir.d.ts +28 -0
  88. package/lib/state-dir.d.ts.map +1 -0
  89. package/lib/state-dir.js +33 -0
  90. package/lib/state-dir.js.map +1 -0
  91. package/lib/stream.d.ts.map +1 -1
  92. package/lib/stream.js +3 -2
  93. package/lib/stream.js.map +1 -1
  94. package/package.json +11 -9
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Which channel cards the settings pane shows open, and which fields hide behind
3
+ * each card's "advanced" fold.
4
+ *
5
+ * Dependency-free and separate from the React component for the same reason
6
+ * `locale.mjs` is: these rules are the interesting part, they are pure, and a
7
+ * test can pin them without a DOM. The bundle test in particular cannot — it
8
+ * renders the component with pre-supplied hook values, so it can never observe
9
+ * what the component *initializes* to. Only this module can answer that.
10
+ */
11
+
12
+ /**
13
+ * Fields that are set once and then left alone, grouped by channel.
14
+ *
15
+ * These are what makes the pane tall: with every channel expanded the pane
16
+ * renders 20 field blocks, each a label + input + preview + hint. Folding the
17
+ * rarely-touched ones behind a second-level disclosure keeps the common case —
18
+ * credentials plus the handful of behavioural switches — on one screen.
19
+ * `test/panel-state.test.mjs` asserts every key here really exists in
20
+ * `CHANNEL_CONFIG_FIELDS`, because a typo would silently drop a field from the
21
+ * UI rather than fail anything.
22
+ */
23
+ export const ADVANCED_KEYS = {
24
+ feishu: ['webhookPort', 'webhookPath'],
25
+ telegram: ['pollingTimeoutSeconds', 'baseUrl'],
26
+ dingtalk: ['defaultAt'],
27
+ web: ['pollIntervalMs'],
28
+ };
29
+
30
+ /** True when `key` is one of `channel`'s low-frequency fields. */
31
+ export function isAdvanced(channel, key) {
32
+ return (ADVANCED_KEYS[channel] ?? []).includes(key);
33
+ }
34
+
35
+ /**
36
+ * The channels whose card starts open: the enabled ones, or — when nothing is
37
+ * enabled at all — the first channel, so the pane never opens as a wall of
38
+ * collapsed headers with no contents in sight.
39
+ *
40
+ * Unknown names are filtered out rather than trusted: `enabled` comes from the
41
+ * host and would otherwise open a card that does not exist.
42
+ */
43
+ export function initialOpenChannels(enabled, all) {
44
+ const open = (enabled ?? []).filter((ch) => all.includes(ch));
45
+ return new Set(open.length > 0 ? open : all.slice(0, 1));
46
+ }
47
+
48
+ /** Toggle one key in a Set, returning a new Set (never mutating the argument). */
49
+ export function toggleInSet(set, key) {
50
+ const next = new Set(set);
51
+ if (next.has(key)) next.delete(key);
52
+ else next.add(key);
53
+ return next;
54
+ }
@@ -8,46 +8,93 @@
8
8
  * `settings-model.js` / `rpc-client.js` helpers; this component is a thin
9
9
  * renderer. Build the lib first (`pnpm --filter dsh-connect build`), then
10
10
  * bundle this in a `dsh web` shell (Vite).
11
+ *
12
+ * The pane is an accordion, not a flat list. Four channels with every field
13
+ * visible is ~2000px of content in a ~690px scroller, so the channel cards
14
+ * collapse, rarely-touched fields hide behind a second-level "advanced" fold,
15
+ * and a tab strip at the top jumps to (and opens) a channel. The rules for
16
+ * which cards start open live in `panel-state.mjs` — pure, and testable without
17
+ * a DOM, which matters because the bundle test's React stub can only supply a
18
+ * component's hook values, never observe what it initializes them to.
11
19
  */
12
20
  import * as React from 'react';
13
21
 
14
22
  import { SETTINGS_RPC_CHANNEL } from '../lib/settings/settings-rpc.js';
15
23
  import { loadSettings, saveSettings, saveCredentials } from '../lib/settings/rpc-client.js';
16
24
  import { snapshotToForm, buildConfigSave, buildCredentialSaves, CHANNEL_SECRET_FIELDS, CHANNEL_CONFIG_FIELDS, CHANNEL_DEFAULT_FIELDS, coerceConfigValue } from '../lib/settings/settings-model.js';
25
+ // Shared with the host, so the pane and the masking it displays can never
26
+ // disagree about which keys are confidential.
27
+ import { isMaskedSecret } from '../lib/settings/secret-disclosure.js';
28
+ import { LOCALES, tr, optionalText } from './locale.mjs';
29
+ import { initialOpenChannels, toggleInSet, isAdvanced } from './panel-state.mjs';
17
30
 
18
31
  export const name = 'dsh-connect-settings';
19
32
  export const inject = ['slots', 'connection', 'locale'];
20
33
  export const NS = 'dsh-connect';
21
34
 
22
- export const locale = {
23
- zh: { title: '连接设置', channels: '渠道', save: '保存', saved: '已保存', error: '保存失败', loading: '加载中', defaults: '公共默认(channelDefaults)', statePath: '设置文件', secret: '密钥', config: '配置', reachable: '已连接凭据', unreachable: '未配置凭据' },
24
- en: { title: 'Connection Settings', channels: 'Channels', save: 'Save', saved: 'Saved', error: 'Save failed', loading: 'Loading', defaults: 'Defaults (channelDefaults)', statePath: 'Settings file', secret: 'Secret', config: 'Config', reachable: 'Credential set', unreachable: 'Credential missing' },
25
- };
35
+ export const locale = LOCALES;
26
36
 
27
37
  const h = React.createElement;
28
38
 
39
+ /** The channels the pane knows about, in the order the cards are rendered. */
40
+ const ALL_CHANNELS = Object.keys(CHANNEL_SECRET_FIELDS);
41
+
29
42
  // Self-contained scoped stylesheet for the pane (injected once). The host's own
30
43
  // settings panes use per-feature CSS modules we can't import into this separate
31
44
  // bundle, so we ship our own consistent, theme-aware styles instead. Everything
32
45
  // is scoped under `.dsh-connect-settings` so nothing leaks into the shell.
46
+ //
47
+ // Two things here are load-bearing rather than cosmetic:
48
+ //
49
+ // 1. The `box-sizing` reset. Neither this bundle nor the host shell ships a
50
+ // global one, so under the default `content-box` a `.ds-input` with
51
+ // `width:100%` is ~20px (padding + border) *wider* than the grid column it
52
+ // sits in, and paints over the neighbouring column's text. That overflow is
53
+ // what "文字跟窗口重叠" actually was.
54
+ // 2. The host's theme tokens, with our old hex values as fallbacks. The host
55
+ // puts `--dsw-alias-*` on `body` / `body[data-ds-dark-theme]`, so inheriting
56
+ // them follows the user's in-app theme. A `prefers-color-scheme` block — what
57
+ // used to be here — ignores that choice entirely and repaints the pane black
58
+ // on a light shell for anyone whose OS is dark. Do not bring it back.
33
59
  const STYLE = `
34
- .dsh-connect-settings{--ds-bg:#ffffff;--ds-bg-sub:#f6f7f9;--ds-text:#1f2329;--ds-muted:#646a73;--ds-border:#e2e4e8;--ds-border-2:#c8cbd0;--ds-accent:#3b82f6;--ds-accent-fg:#ffffff;--ds-focus:rgba(59,130,246,.22);display:flex;flex-direction:column;gap:14px;min-width:280px;max-width:760px;color:var(--ds-text);font:13px/1.6 -apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;padding:4px 2px}
35
- @media(prefers-color-scheme:dark){.dsh-connect-settings{--ds-bg:#1a1b1e;--ds-bg-sub:#232428;--ds-text:#e8eaed;--ds-muted:#9aa0a6;--ds-border:#3b3d42;--ds-border-2:#5c5f66;--ds-accent:#5b9dff;--ds-accent-fg:#0f1115;--ds-focus:rgba(91,157,255,.25)}}
36
- .dsh-connect-settings .ds-card{border:1px solid var(--ds-border);border-radius:10px;background:var(--ds-bg);padding:12px 14px;display:flex;flex-direction:column;gap:10px}
60
+ .dsh-connect-settings,.dsh-connect-settings *,.dsh-connect-settings *::before,.dsh-connect-settings *::after{box-sizing:border-box}
61
+ .dsh-connect-settings{--ds-bg:var(--dsw-alias-bg-layer-3,#ffffff);--ds-bg-sub:var(--dsw-alias-bg-layer-1,#f6f7f9);--ds-text:var(--dsw-alias-label-primary,#1f2329);--ds-muted:var(--dsw-alias-label-tertiary,#646a73);--ds-border:var(--dsw-alias-border-l2,#e2e4e8);--ds-border-2:var(--dsw-alias-border-l3,#c8cbd0);--ds-accent:var(--dsw-alias-state-business-primary,#3b82f6);--ds-hover:var(--dsw-alias-interactive-bg-hover,#2631480f);display:flex;flex-direction:column;gap:12px;max-width:760px;color:var(--ds-text);font:13px/1.6 -apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
62
+ .dsh-connect-settings .ds-card{display:flex;flex-direction:column;gap:10px;border:1px solid var(--ds-border);border-radius:10px;background:var(--ds-bg);padding:12px 14px}
37
63
  .dsh-connect-settings .ds-card-title{margin:0;font-size:13px;font-weight:600}
38
- .dsh-connect-settings .ds-channel{border:1px dashed var(--ds-border-2);border-radius:8px;padding:8px 12px;margin:0;background:var(--ds-bg-sub)}
39
- .dsh-connect-settings .ds-legend{padding:0 6px}
40
- .dsh-connect-settings .ds-legend label{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600}
41
- .dsh-connect-settings .ds-badge{margin-left:4px;font-size:11px;font-weight:500;padding:1px 8px;border-radius:99px;background:var(--ds-bg);color:var(--ds-muted);border:1px solid var(--ds-border-2)}
42
- .dsh-connect-settings .ds-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px 14px;margin-top:8px}
43
- .dsh-connect-settings .ds-field{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--ds-muted)}
44
- .dsh-connect-settings .ds-field.ds-check-field{flex-direction:row;align-items:center;gap:6px}
45
- .dsh-connect-settings .ds-check{width:16px;height:16px;accent-color:var(--ds-accent)}
46
- .dsh-connect-settings .ds-input{height:30px;width:100%;padding:0 9px;border:1px solid var(--ds-border-2);border-radius:6px;background:var(--ds-bg);color:var(--ds-text);font:inherit}
64
+ .dsh-connect-settings .ds-note{margin:0;font-size:11px;line-height:1.5;color:var(--ds-muted)}
65
+ .dsh-connect-settings .ds-tabs{display:flex;flex-wrap:wrap;gap:6px}
66
+ .dsh-connect-settings .ds-tab{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border:1px solid var(--ds-border);border-radius:14px;background:transparent;color:var(--ds-text);font:inherit;font-size:12px;cursor:pointer}
67
+ .dsh-connect-settings .ds-tab:hover{background:var(--ds-hover)}
68
+ .dsh-connect-settings .ds-tab[aria-expanded=true]{border-color:var(--ds-accent);color:var(--ds-accent)}
69
+ .dsh-connect-settings .ds-dot{width:6px;height:6px;border-radius:50%;background:var(--ds-border-2);flex:none}
70
+ .dsh-connect-settings .ds-dot[data-on="1"]{background:var(--ds-accent)}
71
+ .dsh-connect-settings .ds-channel{border:1px solid var(--ds-border);border-radius:8px;background:var(--ds-bg-sub)}
72
+ .dsh-connect-settings .ds-channel-head{display:flex;align-items:center;gap:8px;padding:5px 10px}
73
+ .dsh-connect-settings .ds-enable{display:flex;align-items:center;flex:none}
74
+ .dsh-connect-settings .ds-channel-toggle{flex:1;min-width:0;display:flex;align-items:center;gap:8px;padding:5px 6px;border:0;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:12px;font-weight:600;text-align:left;cursor:pointer}
75
+ .dsh-connect-settings .ds-channel-toggle:hover{background:var(--ds-hover);color:var(--ds-accent)}
76
+ .dsh-connect-settings .ds-channel-name{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
77
+ .dsh-connect-settings .ds-channel-body{display:flex;flex-direction:column;gap:8px;padding:0 10px 10px}
78
+ .dsh-connect-settings .ds-chevron{width:7px;height:7px;margin:-3px 4px 0 auto;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg);transition:transform .15s ease;flex:none}
79
+ .dsh-connect-settings [aria-expanded=true]>.ds-chevron,.dsh-connect-settings [aria-expanded=true] .ds-chevron{transform:rotate(-135deg);margin-top:2px}
80
+ .dsh-connect-settings .ds-badge{flex:none;font-size:11px;font-weight:500;padding:1px 8px;border-radius:99px;background:var(--ds-bg);color:var(--ds-muted);border:1px solid var(--ds-border-2)}
81
+ .dsh-connect-settings .ds-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px 14px}
82
+ .dsh-connect-settings .ds-field{display:flex;flex-direction:column;gap:4px;min-width:0}
83
+ .dsh-connect-settings .ds-control{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--ds-muted)}
84
+ .dsh-connect-settings .ds-control.ds-check-field{flex-direction:row;align-items:center;gap:6px}
85
+ .dsh-connect-settings .ds-hint{margin:0;font-size:11px;line-height:1.5;color:var(--ds-muted)}
86
+ .dsh-connect-settings .ds-preview{margin:0;font-size:11px;line-height:1.5;color:var(--ds-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;word-break:break-all}
87
+ .dsh-connect-settings .ds-preview b{font-weight:500;font-family:inherit;opacity:.75}
88
+ .dsh-connect-settings .ds-check{flex:none;width:16px;height:16px;accent-color:var(--ds-accent)}
89
+ .dsh-connect-settings .ds-input{height:30px;width:100%;min-width:0;padding:0 9px;border:1px solid var(--ds-border-2);border-radius:6px;background:var(--ds-bg);color:var(--ds-text);font:inherit}
47
90
  .dsh-connect-settings select.ds-input{cursor:pointer}
48
- .dsh-connect-settings .ds-input:focus{outline:none;border-color:var(--ds-accent);box-shadow:0 0 0 2px var(--ds-focus)}
49
- .dsh-connect-settings .ds-actions{display:flex;align-items:center;gap:12px;margin-top:2px}
50
- .dsh-connect-settings .ds-btn{height:32px;padding:0 18px;border:none;border-radius:6px;background:var(--ds-accent);color:var(--ds-accent-fg);font:inherit;font-weight:500;cursor:pointer}
91
+ .dsh-connect-settings .ds-input:focus{outline:none;border-color:var(--ds-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--ds-accent) 25%,transparent)}
92
+ .dsh-connect-settings .ds-adv{display:flex;flex-direction:column;gap:8px}
93
+ .dsh-connect-settings .ds-advanced-toggle{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border:1px dashed var(--ds-border-2);border-radius:99px;background:transparent;color:var(--ds-muted);font:inherit;font-size:11px;cursor:pointer}
94
+ .dsh-connect-settings .ds-advanced-toggle:hover{background:var(--ds-hover);color:var(--ds-text)}
95
+ .dsh-connect-settings .ds-footer{position:sticky;bottom:0;z-index:1;display:flex;align-items:center;gap:12px;margin:0 -14px -12px;padding:10px 14px;border-top:1px solid var(--ds-border);border-radius:0 0 9px 9px;background:var(--ds-bg)}
96
+ .dsh-connect-settings .ds-btn{height:32px;padding:0 18px;border:0;border-radius:6px;background:var(--dsw-alias-button-primary-fill,var(--ds-accent));color:var(--dsw-alias-label-primary-foreground,#ffffff);font:inherit;font-weight:500;cursor:pointer}
97
+ .dsh-connect-settings .ds-btn:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--ds-accent))}
51
98
  .dsh-connect-settings .ds-btn:disabled{opacity:.55;cursor:default}
52
99
  .dsh-connect-settings .ds-status{font-size:12px;color:var(--ds-muted)}
53
100
  `;
@@ -61,29 +108,138 @@ function injectStyles() {
61
108
  document.head.appendChild(el);
62
109
  }
63
110
 
64
- // Render a single config field control per its `kind` (text / number / boolean / select).
65
- function renderConfigField(field, value, onChange) {
66
- const label = field.label ?? field.key;
111
+ // Render one non-secret config field: its control, then its explanation.
112
+ //
113
+ // Label and hint are looked up by *config key* (`f.<key>`), so a field added
114
+ // without its text shows a legible fallback (`field.label`) instead of a raw
115
+ // identifier — and the locale-coverage test fails, which is the real guard.
116
+ function renderConfigField(field, value, onChange, t) {
117
+ const label = tr(t, `f.${field.key}`, field.label ?? field.key);
118
+ const hint = optionalText(t, `f.${field.key}.hint`);
119
+ let control;
67
120
  if (field.kind === 'boolean') {
68
- return h('label', { className: 'ds-field ds-check-field', key: `cfg-${field.key}` },
121
+ control = h('label', { className: 'ds-control ds-check-field' },
69
122
  h('input', { className: 'ds-check', type: 'checkbox', checked: !!value, onChange: (e) => onChange(e.target.checked) }),
70
123
  ' ' + label);
71
- }
72
- if (field.kind === 'select') {
73
- return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
74
- label + ' ',
124
+ } else if (field.kind === 'select') {
125
+ control = h('label', { className: 'ds-control' }, label + ' ',
75
126
  h('select', { className: 'ds-input', value: value ?? '', onChange: (e) => onChange(e.target.value) },
76
- h('option', { value: '' }, '(default)'),
77
- ...(field.options ?? []).map((opt) => h('option', { key: opt, value: opt }, opt))));
78
- }
79
- if (field.kind === 'number') {
80
- return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
81
- label + ' ',
127
+ h('option', { value: '' }, tr(t, 'o.default', label)),
128
+ ...(field.options ?? []).map((opt) => h('option', { key: opt, value: opt }, tr(t, `o.${opt}`, opt)))));
129
+ } else if (field.kind === 'number') {
130
+ control = h('label', { className: 'ds-control' }, label + ' ',
82
131
  h('input', { className: 'ds-input', type: 'number', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
132
+ } else {
133
+ control = h('label', { className: 'ds-control' }, label + ' ',
134
+ h('input', { className: 'ds-input', type: 'text', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
83
135
  }
84
- return h('label', { className: 'ds-field', key: `cfg-${field.key}` },
85
- label + ' ',
86
- h('input', { className: 'ds-input', type: 'text', value: value ?? '', onChange: (e) => onChange(e.target.value) }));
136
+ return h('div', { className: 'ds-field', key: `cfg-${field.key}` },
137
+ control,
138
+ hint ? h('p', { className: 'ds-hint' }, hint) : null);
139
+ }
140
+
141
+ // Render one secret field: a write-only input, plus a read-only preview of the
142
+ // stored value so the user can confirm what they configured.
143
+ //
144
+ // The preview is plain text, never the input's `value`. The host already masked
145
+ // it, and seeding an input with a mask would let a save write the mask *back* as
146
+ // the credential. Keeping the input blank also keeps "empty" meaning "leave the
147
+ // stored value alone", which is what a password field is expected to do.
148
+ function renderSecretField(ch, field, form, onChange, t) {
149
+ const preview = form.secretPreviews?.[ch]?.[field] ?? '';
150
+ const hint = optionalText(t, `s.${ch}.${field}.hint`);
151
+ return h('div', { className: 'ds-field', key: `sec-${ch}-${field}` },
152
+ h('label', { className: 'ds-control' },
153
+ tr(t, `s.${ch}.${field}`, field),
154
+ h('input', {
155
+ className: 'ds-input',
156
+ // Confidential keys stay masked while typing; identifiers (appId,
157
+ // clientId) don't, so a typo is visible before it is ever saved.
158
+ type: isMaskedSecret(field) ? 'password' : 'text',
159
+ autoComplete: 'off',
160
+ placeholder: preview ? t('configured') : t('notConfigured'),
161
+ value: form.secrets?.[ch]?.[field] ?? '',
162
+ onChange: (e) => onChange(e.target.value),
163
+ })),
164
+ h('p', { className: 'ds-preview' },
165
+ h('b', null, t('current')),
166
+ preview === '' ? t('notConfigured') : preview),
167
+ hint ? h('p', { className: 'ds-hint' }, hint) : null);
168
+ }
169
+
170
+ // One channel card: an enable checkbox, a header that folds the card, and — when
171
+ // open — its credentials and settings.
172
+ //
173
+ // A plain function rather than a child component, deliberately: the bundle test
174
+ // supplies hook values *positionally*, so every `useState` in the tree has to
175
+ // live in `ConnectSettingsTab` in a fixed order. Nothing in here may call a hook.
176
+ function renderChannel(ch, ctx) {
177
+ const { form, creds, t, open, advOverride, setChannels, setField, setChannelConfig, toggleOpen, toggleAdvanced } = ctx;
178
+ const name = tr(t, `channel.${ch}`, ch);
179
+ const channelHint = optionalText(t, `channel.${ch}.hint`);
180
+ const isOpen = open.has(ch);
181
+ const advOpen = advOverride?.has(ch) ?? false;
182
+ const configFields = CHANNEL_CONFIG_FIELDS[ch] ?? [];
183
+ // The split is what keeps a common case on one screen: two credentials and the
184
+ // behavioural switches, with the set-once fields one disclosure deeper.
185
+ const common = configFields.filter((f) => !isAdvanced(ch, f.key));
186
+ const advanced = configFields.filter((f) => isAdvanced(ch, f.key));
187
+
188
+ const configField = (field) => renderConfigField(
189
+ field,
190
+ form.channelConfigs?.[ch]?.[field.key],
191
+ (raw) => setChannelConfig(ch, field.key, raw),
192
+ t,
193
+ );
194
+
195
+ return h('div', { className: 'ds-channel', key: ch, id: `ds-ch-${ch}` },
196
+ // The checkbox sits beside the header button, not inside it: a button may not
197
+ // contain another interactive element, and browsers disagree about what to do
198
+ // when one does.
199
+ h('div', { className: 'ds-channel-head' },
200
+ h('label', { className: 'ds-enable' },
201
+ h('input', {
202
+ className: 'ds-check',
203
+ type: 'checkbox',
204
+ 'aria-label': name,
205
+ checked: form.channels.includes(ch),
206
+ onChange: (e) => setChannels(ch, e.target.checked),
207
+ })),
208
+ h('button', {
209
+ type: 'button',
210
+ className: 'ds-channel-toggle',
211
+ 'aria-expanded': isOpen,
212
+ // Only while the body exists: pointing at an id that is not in the
213
+ // document is worse than not pointing at all.
214
+ 'aria-controls': isOpen ? `ds-ch-${ch}-body` : undefined,
215
+ 'aria-label': `${name} — ${isOpen ? t('collapse') : t('expand')}`,
216
+ onClick: () => toggleOpen(ch),
217
+ },
218
+ h('span', { className: 'ds-channel-name' }, name),
219
+ h('span', { className: 'ds-badge' }, creds[ch] ? t('reachable') : t('unreachable')),
220
+ // Drawn in CSS, never a text node: the bundle test reads rendered text as
221
+ // user-visible copy, and a `▾` here would be collected as a stray string.
222
+ h('span', { className: 'ds-chevron' }))),
223
+ // Collapsed means *not rendered*, not `display:none`. The inputs are
224
+ // controlled from the parent's `form.secrets`, so folding a card cannot lose
225
+ // an unsaved secret — keeping it mounted would buy nothing.
226
+ isOpen ? h('div', { className: 'ds-channel-body', id: `ds-ch-${ch}-body` },
227
+ channelHint ? h('p', { className: 'ds-note' }, channelHint) : null,
228
+ h('div', { className: 'ds-fields' },
229
+ ...CHANNEL_SECRET_FIELDS[ch].map((field) => renderSecretField(ch, field, form, (value) => setField(ch, field, value), t)),
230
+ ...common.map(configField)),
231
+ advanced.length === 0 ? null : h('div', { className: 'ds-adv' },
232
+ h('button', {
233
+ type: 'button',
234
+ className: 'ds-advanced-toggle',
235
+ 'aria-expanded': advOpen,
236
+ 'aria-controls': advOpen ? `ds-ch-${ch}-adv` : undefined,
237
+ onClick: () => toggleAdvanced(ch),
238
+ },
239
+ h('span', null, t('advanced')),
240
+ h('span', { className: 'ds-chevron' })),
241
+ advOpen ? h('div', { className: 'ds-fields', id: `ds-ch-${ch}-adv` }, ...advanced.map(configField)) : null),
242
+ ) : null);
87
243
  }
88
244
 
89
245
  // Thin React renderer over the tested settings-model helpers.
@@ -91,6 +247,8 @@ export function ConnectSettingsTab({ rpcCall, t }) {
91
247
  const [form, setForm] = React.useState(null);
92
248
  const [status, setStatus] = React.useState('loading');
93
249
  const [creds, setCreds] = React.useState({});
250
+ const [openOverride, setOpenOverride] = React.useState(null);
251
+ const [advOverride, setAdvOverride] = React.useState(null);
94
252
  const rpc = (endpoint, payload) => rpcCall(endpoint, payload);
95
253
 
96
254
  React.useEffect(() => {
@@ -108,13 +266,25 @@ export function ConnectSettingsTab({ rpcCall, t }) {
108
266
  if (!form) return;
109
267
  setStatus('saving');
110
268
  try {
111
- await saveSettings(rpc, buildConfigSave(form));
112
- for (const c of buildCredentialSaves(form)) await saveCredentials(rpc, c.channel, c.values);
269
+ // The last snapshot in the chain wins: it is the one that reflects every
270
+ // write this save performed. Re-seeding the form from it also drops the
271
+ // typed secret values (their inputs are write-only by design) and picks up
272
+ // the new presence flags.
273
+ let snap = await saveSettings(rpc, buildConfigSave(form));
274
+ for (const c of buildCredentialSaves(form)) snap = await saveCredentials(rpc, c.channel, c.values);
275
+ setForm(snapshotToForm(snap));
276
+ setCreds(snap.credentials ?? {});
113
277
  setStatus('saved');
114
278
  } catch { setStatus('error'); }
115
279
  };
116
280
 
117
- const setChannels = (ch, on) => setForm((f) => ({ ...f, channels: on ? [...f.channels, ch] : f.channels.filter((x) => x !== ch) }));
281
+ const setChannels = (ch, on) => {
282
+ setForm((f) => ({ ...f, channels: on ? [...f.channels, ch] : f.channels.filter((x) => x !== ch) }));
283
+ // Enabling a channel opens it, and only ever opens: the user just said they
284
+ // care about this one. Ticking it back off leaves the fold alone rather than
285
+ // snapping shut under the cursor.
286
+ if (on && !open.has(ch)) setOpenOverride(new Set(open).add(ch));
287
+ };
118
288
  const setField = (ch, field, value) => setForm((f) => ({ ...f, secrets: { ...f.secrets, [ch]: { ...(f.secrets[ch] ?? {}), [field]: value } } }));
119
289
  const setChannelConfig = (ch, key, raw) => setForm((f) => {
120
290
  const descriptor = (CHANNEL_CONFIG_FIELDS[ch] ?? []).find((x) => x.key === key);
@@ -132,35 +302,77 @@ export function ConnectSettingsTab({ rpcCall, t }) {
132
302
  });
133
303
 
134
304
  if (!form) return h('div', { className: 'dsh-connect-settings' }, t('loading'));
305
+
306
+ // Which cards are open is *derived*, not stored: the first render happens
307
+ // before the host's snapshot arrives, and the test stub cannot run effects, so
308
+ // an effect here would be the only place the default could ever be set — and
309
+ // it would never run. The override records only what the user has since done,
310
+ // on top of that default (`toggleInSet(open, …)` rather than a bare toggle),
311
+ // so opening DingTalk does not silently drop the enabled Feishu the user was
312
+ // already looking at.
313
+ const open = openOverride ?? initialOpenChannels(form.channels, ALL_CHANNELS);
314
+
315
+ const toggleOpen = (ch) => setOpenOverride(toggleInSet(open, ch));
316
+ // Advanced folds default to closed for every channel, so an empty set is the
317
+ // derived default here and no counterpart to `initialOpenChannels` is needed.
318
+ const toggleAdvanced = (ch) => setAdvOverride(toggleInSet(advOverride ?? new Set(), ch));
319
+ const focusChannel = (ch) => {
320
+ // A tab expands and scrolls, but never collapses: "show me Telegram" must not
321
+ // close the channel the user is halfway through editing.
322
+ if (!open.has(ch)) setOpenOverride(new Set(open).add(ch));
323
+ if (typeof document === 'undefined' || !document.getElementById) return;
324
+ // The header is rendered whether or not the card is open, so this resolves
325
+ // before React has re-rendered, and lands in the same place either way.
326
+ document.getElementById(`ds-ch-${ch}`)?.scrollIntoView?.({ block: 'nearest' });
327
+ };
328
+
135
329
  return h('div', { className: 'dsh-connect-settings' },
136
330
  h('section', { className: 'ds-card' },
137
331
  h('h4', { className: 'ds-card-title' }, t('channels')),
332
+ // Explains the masking before the user meets a truncated value and wonders
333
+ // whether their stored secret is corrupt.
334
+ h('p', { className: 'ds-note' }, t('previewNote')),
335
+ // A tab strip, but not `role=tablist`: several channels can be open at
336
+ // once, so there is no single "selected" tab to report. These are buttons
337
+ // that open and jump to a channel, and `aria-expanded` says so honestly.
338
+ h('nav', { className: 'ds-tabs', 'aria-label': t('tabsAria') },
339
+ ...ALL_CHANNELS.map((ch) => h('button', {
340
+ key: `tab-${ch}`,
341
+ type: 'button',
342
+ className: 'ds-tab',
343
+ 'aria-expanded': open.has(ch),
344
+ 'aria-controls': open.has(ch) ? `ds-ch-${ch}-body` : undefined,
345
+ onClick: () => focusChannel(ch),
346
+ },
347
+ h('span', { className: 'ds-dot', 'data-on': form.channels.includes(ch) ? '1' : '0' }),
348
+ tr(t, `channel.${ch}`, ch)))),
138
349
  // One card per built-in channel: enable toggle + cred badge + secret + config fields.
139
- ...Object.keys(CHANNEL_SECRET_FIELDS).map((ch) => h('fieldset', { className: 'ds-channel', key: ch },
140
- h('legend', { className: 'ds-legend' },
141
- h('label', null,
142
- h('input', { className: 'ds-check', type: 'checkbox', checked: form.channels.includes(ch), onChange: (e) => setChannels(ch, e.target.checked) }),
143
- h('span', null, ch),
144
- h('span', { className: 'ds-badge' }, creds[ch] ? t('reachable') : t('unreachable'))),
145
- ),
146
- h('div', { className: 'ds-fields' },
147
- ...CHANNEL_SECRET_FIELDS[ch].map((field) => h('label', { className: 'ds-field', key: `sec-${field}` },
148
- field,
149
- h('input', { className: 'ds-input', type: 'password', placeholder: field, value: form.secrets?.[ch]?.[field] ?? '', onChange: (e) => setField(ch, field, e.target.value) }))),
150
- ...(CHANNEL_CONFIG_FIELDS[ch] ?? []).map((field) => renderConfigField(field, form.channelConfigs?.[ch]?.[field.key], (raw) => setChannelConfig(ch, field.key, raw))),
151
- ),
152
- )),
153
- ),
350
+ ...ALL_CHANNELS.map((ch) => renderChannel(ch, {
351
+ form, creds, t, open, advOverride,
352
+ setChannels, setField, setChannelConfig, toggleOpen, toggleAdvanced,
353
+ }))),
154
354
  h('section', { className: 'ds-card' },
155
355
  h('h4', { className: 'ds-card-title' }, t('defaults')),
356
+ h('p', { className: 'ds-note' }, t('defaultsHint')),
156
357
  h('div', { className: 'ds-fields' },
157
- ...CHANNEL_DEFAULT_FIELDS.map((field) => renderConfigField(field, form.channelDefaults?.[field.key], (raw) => setDefault(field.key, raw))),
158
- h('label', { className: 'ds-field' }, t('statePath'),
159
- h('input', { className: 'ds-input', value: form.settingsStatePath ?? '', onChange: (e) => setForm((f) => ({ ...f, settingsStatePath: e.target.value })) })),
358
+ ...CHANNEL_DEFAULT_FIELDS.map((field) => renderConfigField(field, form.channelDefaults?.[field.key], (raw) => setDefault(field.key, raw), t)),
359
+ // Only meaningful on the fallback plane: it names the file the pane
360
+ // persists to. With the namespace live that path is not consulted (and
361
+ // is not part of the section), so showing an editable field for it would
362
+ // silently swallow edits.
363
+ form.live ? null : h('div', { className: 'ds-field' },
364
+ h('label', { className: 'ds-control' }, t('statePath'),
365
+ h('input', { className: 'ds-input', value: form.settingsStatePath ?? '', onChange: (e) => setForm((f) => ({ ...f, settingsStatePath: e.target.value })) })),
366
+ h('p', { className: 'ds-hint' }, t('statePathHint'))),
160
367
  ),
161
- h('div', { className: 'ds-actions' },
162
- h('button', { className: 'ds-btn', onClick: onSave, disabled: status === 'saving' }, t('save')),
163
- h('span', { className: 'ds-status' }, status === 'saved' ? t('saved') : status === 'error' ? t('error') : status),
368
+ h('div', { className: 'ds-status' }, form.live ? t('livePlane') : t('filePlane')),
369
+ // Sticky to the bottom of the host's scrolling region, so Save stays in
370
+ // reach however far the channel list has been unfolded.
371
+ h('div', { className: 'ds-footer' },
372
+ h('button', { className: 'ds-btn', type: 'button', onClick: onSave, disabled: status === 'saving' }, t('save')),
373
+ // Rendering `status` directly leaks the raw state ids (`idle`, `saving`)
374
+ // into the UI; every state has a locale entry instead.
375
+ h('span', { className: 'ds-status' }, tr(t, `status.${status}`, status)),
164
376
  ),
165
377
  ),
166
378
  );
@@ -1 +1 @@
1
- {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAUA,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC9C,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,qFAAqF;QACrF,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,gEAAgE;IAChE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AA0BD,+CAA+C;AAC/C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;AAE5G,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;gBAExD,QAAQ,CAAC,EAAE,MAAM;IAM7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAOrD;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;;OAGG;IACH,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC;IAIxC;;OAEG;IACH,IAAI,IAAI,WAAW,EAAE;IAIrB;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE;IAUjD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI9D,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAQ/B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAU9C,OAAO,CAAC,IAAI;IAoCZ,OAAO,CAAC,IAAI;CAQb"}
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAYA,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC9C,oGAAoG;IACpG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,qFAAqF;QACrF,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,gEAAgE;IAChE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AA0BD,+CAA+C;AAC/C,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,KAAK,IAAI,CAAC;AAE5G,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAkC;IACtD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;gBAExD,QAAQ,CAAC,EAAE,MAAM;IAK7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAOrD;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;;OAGG;IACH,OAAO,IAAI,gBAAgB,CAAC,WAAW,CAAC;IAIxC;;OAEG;IACH,IAAI,IAAI,WAAW,EAAE;IAIrB;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE;IAUjD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI9D,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAQ/B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAU9C,OAAO,CAAC,IAAI;IAoCZ,OAAO,CAAC,IAAI;CAQb"}
package/lib/binding.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { dirname, resolve } from "node:path";
10
+ import { resolveStateDir } from "./state-dir.js";
10
11
  function keyOf(channel, chatKey) {
11
12
  return `${channel}\u0000${chatKey}`;
12
13
  }
@@ -35,8 +36,7 @@ export class BindingStore {
35
36
  file;
36
37
  changeListeners = new Set();
37
38
  constructor(stateDir) {
38
- const dir = stateDir ?? process.env.DSH_CONNECT_STATE_DIR ?? ".dsh-connect";
39
- this.file = resolve(dir, "bindings.json");
39
+ this.file = resolve(resolveStateDir({ stateDir }), "bindings.json");
40
40
  this.load();
41
41
  }
42
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/channels/dingtalk/message.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAC/D,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,gCAAgC,CAAC;AAC/D,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,SAAa,GAAG,cAAc,GAAG,SAAS,CAY7G;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,mDAAmD;AACnD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzF;AAED,6CAA6C;AAC7C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAE/E"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/channels/dingtalk/message.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAC/D,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,gCAAgC,CAAC;AAC/D,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,YAAY,CAAC;AAE9C,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,kBAAkB,EAAE,OAAO,SAAa,GAAG,cAAc,GAAG,SAAS,CAkB7G;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,mDAAmD;AACnD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzF;AAED,6CAA6C;AAC7C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAE/E"}
@@ -19,6 +19,13 @@ export function normalizeBotMessage(raw, channel = "dingtalk") {
19
19
  if (typeof raw.senderStaffId !== "string" || raw.senderStaffId === "")
20
20
  return undefined;
21
21
  const text = raw.msgType === "text" ? (raw.text?.content ?? "") : "";
22
+ // Nothing for the agent to act on: a non-text payload (image, file, sticker,
23
+ // recall notice) normalizes to an empty body, and stream mode carries no
24
+ // attachment download. Running a turn on it would burn a model call and post
25
+ // a reply to a message the user never sent. Same guard the Telegram adapter
26
+ // applies when it has no media either.
27
+ if (text.trim() === "")
28
+ return undefined;
22
29
  return {
23
30
  channel,
24
31
  chatKey: raw.conversationId,
@@ -1 +1 @@
1
- {"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/channels/dingtalk/message.ts"],"names":[],"mappings":"AAyBA,oEAAoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,2BAA2B,CAAC;AAC/D,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,6BAA6B,CAAC;AAC/D,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAE9C,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,GAAuB;IACnD,OAAO,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAuB,EAAE,OAAO,GAAG,UAAU;IAC/E,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,QAAQ,IAAI,GAAG,CAAC,cAAc,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1F,IAAI,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,GAAG,CAAC,aAAa,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACxF,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrE,OAAO;QACL,OAAO;QACP,OAAO,EAAE,GAAG,CAAC,cAAc;QAC3B,QAAQ,EAAE,GAAG,CAAC,gBAAgB,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;QACxD,SAAS,EAAE,GAAG,CAAC,aAAa;QAC5B,IAAI;QACJ,GAAG,CAAC,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtF,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,OAAe;IAC/D,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAChF,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,KAAa,EAAE,IAAY;IAC/E,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AACxF,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,YAAoB;IACrE,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5E,CAAC"}
1
+ {"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/channels/dingtalk/message.ts"],"names":[],"mappings":"AAyBA,oEAAoE;AACpE,MAAM,CAAC,MAAM,mBAAmB,GAAG,2BAA2B,CAAC;AAC/D,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,6BAA6B,CAAC;AAC/D,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAE9C,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,GAAuB;IACnD,OAAO,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAuB,EAAE,OAAO,GAAG,UAAU;IAC/E,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,QAAQ,IAAI,GAAG,CAAC,cAAc,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1F,IAAI,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,GAAG,CAAC,aAAa,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACxF,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrE,6EAA6E;IAC7E,yEAAyE;IACzE,6EAA6E;IAC7E,4EAA4E;IAC5E,uCAAuC;IACvC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACzC,OAAO;QACL,OAAO;QACP,OAAO,EAAE,GAAG,CAAC,cAAc;QAC3B,QAAQ,EAAE,GAAG,CAAC,gBAAgB,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK;QACxD,SAAS,EAAE,GAAG,CAAC,aAAa;QAC5B,IAAI;QACJ,GAAG,CAAC,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtF,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,OAAe;IAC/D,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAChF,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,KAAa,EAAE,IAAY;IAC/E,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AACxF,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,YAAoB;IACrE,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5E,CAAC"}
@@ -27,18 +27,6 @@ export declare function buildSelectMenu(options: readonly ChoiceOption[], placeh
27
27
  * @param defaultColumns - Default number of columns per row (default: 2)
28
28
  */
29
29
  export declare function buildChoiceElements(prompt: ChoicePrompt, defaultColumns?: number): unknown[];
30
- /**
31
- * Encode a chatKey with an optional thread id. With `threadIsolation` on, a
32
- * group message inside a thread gets `chatId:thread=<rootId>` as its chatKey,
33
- * so each thread binds its own DSH session while outbound sends still target
34
- * the base chat id (replies land in the thread via replyRef).
35
- */
36
- export declare function encodeChatKey(chatId: string, threadId?: string): string;
37
- /** Decode a possibly thread-scoped chatKey back to its base chat id. */
38
- export declare function decodeChatKey(chatKey: string): {
39
- chatId: string;
40
- threadId?: string;
41
- };
42
30
  /** Classify a local file name for the Feishu upload API (image vs stream file). */
43
31
  export declare function classifyFeishuFile(filename: string): "image" | "file";
44
32
  /** Strip path separators / control chars from a downloaded file's name. */
@@ -66,6 +54,12 @@ export interface FeishuConfig {
66
54
  dmMode?: "open" | "allowlist" | "pair" | "disabled";
67
55
  /** Bind one DSH session per group thread instead of one per chat (default false). */
68
56
  threadIsolation?: boolean;
57
+ /**
58
+ * Allow the interactive one-click onboarding flow when no credentials are
59
+ * configured (default true). Set `false` to never start it — the adapter then
60
+ * just warns, which is what a supervisor-managed deployment wants.
61
+ */
62
+ onboarding?: boolean;
69
63
  /** User-facing message language: `zh` (default) or `en`. */
70
64
  language?: Language;
71
65
  }