dsh-connect 0.7.2 → 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 (153) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +196 -43
  3. package/README.zh.md +171 -42
  4. package/client/client.js +817 -0
  5. package/client/client.js.map +7 -0
  6. package/client/locale.mjs +227 -0
  7. package/client/panel-state.mjs +54 -0
  8. package/client/settings-client.mjs +389 -0
  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/examples/minimal.config.json +16 -0
  16. package/lib/binding.d.ts.map +1 -1
  17. package/lib/binding.js +2 -2
  18. package/lib/binding.js.map +1 -1
  19. package/lib/channels/dingtalk/adapter.d.ts +59 -0
  20. package/lib/channels/dingtalk/adapter.d.ts.map +1 -0
  21. package/lib/channels/dingtalk/adapter.js +123 -0
  22. package/lib/channels/dingtalk/adapter.js.map +1 -0
  23. package/lib/channels/dingtalk/i18n.d.ts +14 -0
  24. package/lib/channels/dingtalk/i18n.d.ts.map +1 -0
  25. package/lib/channels/dingtalk/i18n.js +16 -0
  26. package/lib/channels/dingtalk/i18n.js.map +1 -0
  27. package/lib/channels/dingtalk/index.d.ts +127 -0
  28. package/lib/channels/dingtalk/index.d.ts.map +1 -0
  29. package/lib/channels/dingtalk/index.js +144 -0
  30. package/lib/channels/dingtalk/index.js.map +1 -0
  31. package/lib/channels/dingtalk/message.d.ts +49 -0
  32. package/lib/channels/dingtalk/message.d.ts.map +1 -0
  33. package/lib/channels/dingtalk/message.js +50 -0
  34. package/lib/channels/dingtalk/message.js.map +1 -0
  35. package/lib/channels/dingtalk/stomp.d.ts +36 -0
  36. package/lib/channels/dingtalk/stomp.d.ts.map +1 -0
  37. package/lib/channels/dingtalk/stomp.js +107 -0
  38. package/lib/channels/dingtalk/stomp.js.map +1 -0
  39. package/lib/channels/dingtalk/stream.d.ts +53 -0
  40. package/lib/channels/dingtalk/stream.d.ts.map +1 -0
  41. package/lib/channels/dingtalk/stream.js +199 -0
  42. package/lib/channels/dingtalk/stream.js.map +1 -0
  43. package/lib/channels/dingtalk/webhook.d.ts +84 -0
  44. package/lib/channels/dingtalk/webhook.d.ts.map +1 -0
  45. package/lib/channels/dingtalk/webhook.js +143 -0
  46. package/lib/channels/dingtalk/webhook.js.map +1 -0
  47. package/lib/channels/feishu/adapter.d.ts +133 -0
  48. package/lib/channels/feishu/adapter.d.ts.map +1 -0
  49. package/lib/channels/feishu/adapter.js +662 -0
  50. package/lib/channels/feishu/adapter.js.map +1 -0
  51. package/lib/channels/feishu/i18n.d.ts +30 -0
  52. package/lib/channels/feishu/i18n.d.ts.map +1 -0
  53. package/lib/channels/feishu/i18n.js +42 -0
  54. package/lib/channels/feishu/i18n.js.map +1 -0
  55. package/lib/channels/feishu/index.d.ts +82 -0
  56. package/lib/channels/feishu/index.d.ts.map +1 -0
  57. package/lib/channels/feishu/index.js +103 -0
  58. package/lib/channels/feishu/index.js.map +1 -0
  59. package/lib/channels/feishu/onboard.d.ts +16 -0
  60. package/lib/channels/feishu/onboard.d.ts.map +1 -0
  61. package/lib/channels/feishu/onboard.js +86 -0
  62. package/lib/channels/feishu/onboard.js.map +1 -0
  63. package/lib/channels/telegram/adapter.d.ts +70 -0
  64. package/lib/channels/telegram/adapter.d.ts.map +1 -0
  65. package/lib/channels/telegram/adapter.js +460 -0
  66. package/lib/channels/telegram/adapter.js.map +1 -0
  67. package/lib/channels/telegram/client.d.ts +115 -0
  68. package/lib/channels/telegram/client.d.ts.map +1 -0
  69. package/lib/channels/telegram/client.js +198 -0
  70. package/lib/channels/telegram/client.js.map +1 -0
  71. package/lib/channels/telegram/i18n.d.ts +14 -0
  72. package/lib/channels/telegram/i18n.d.ts.map +1 -0
  73. package/lib/channels/telegram/i18n.js +18 -0
  74. package/lib/channels/telegram/i18n.js.map +1 -0
  75. package/lib/channels/telegram/index.d.ts +46 -0
  76. package/lib/channels/telegram/index.d.ts.map +1 -0
  77. package/lib/channels/telegram/index.js +54 -0
  78. package/lib/channels/telegram/index.js.map +1 -0
  79. package/lib/channels/web/adapter.d.ts +103 -0
  80. package/lib/channels/web/adapter.d.ts.map +1 -0
  81. package/lib/channels/web/adapter.js +161 -0
  82. package/lib/channels/web/adapter.js.map +1 -0
  83. package/lib/channels/web/index.d.ts +47 -0
  84. package/lib/channels/web/index.d.ts.map +1 -0
  85. package/lib/channels/web/index.js +59 -0
  86. package/lib/channels/web/index.js.map +1 -0
  87. package/lib/chat-key.d.ts +29 -0
  88. package/lib/chat-key.d.ts.map +1 -0
  89. package/lib/chat-key.js +38 -0
  90. package/lib/chat-key.js.map +1 -0
  91. package/lib/index.d.ts +78 -11
  92. package/lib/index.d.ts.map +1 -1
  93. package/lib/index.js +312 -9
  94. package/lib/index.js.map +1 -1
  95. package/lib/runner.d.ts +55 -2
  96. package/lib/runner.d.ts.map +1 -1
  97. package/lib/runner.js +158 -23
  98. package/lib/runner.js.map +1 -1
  99. package/lib/scheduler.d.ts.map +1 -1
  100. package/lib/scheduler.js +2 -2
  101. package/lib/scheduler.js.map +1 -1
  102. package/lib/service.d.ts +17 -1
  103. package/lib/service.d.ts.map +1 -1
  104. package/lib/service.js +47 -3
  105. package/lib/service.js.map +1 -1
  106. package/lib/settings/channel-runtime.d.ts +75 -0
  107. package/lib/settings/channel-runtime.d.ts.map +1 -0
  108. package/lib/settings/channel-runtime.js +165 -0
  109. package/lib/settings/channel-runtime.js.map +1 -0
  110. package/lib/settings/channels.d.ts +62 -0
  111. package/lib/settings/channels.d.ts.map +1 -0
  112. package/lib/settings/channels.js +132 -0
  113. package/lib/settings/channels.js.map +1 -0
  114. package/lib/settings/credential-store.d.ts +85 -0
  115. package/lib/settings/credential-store.d.ts.map +1 -0
  116. package/lib/settings/credential-store.js +142 -0
  117. package/lib/settings/credential-store.js.map +1 -0
  118. package/lib/settings/index.d.ts +14 -0
  119. package/lib/settings/index.d.ts.map +1 -0
  120. package/lib/settings/index.js +14 -0
  121. package/lib/settings/index.js.map +1 -0
  122. package/lib/settings/namespace.d.ts +175 -0
  123. package/lib/settings/namespace.d.ts.map +1 -0
  124. package/lib/settings/namespace.js +200 -0
  125. package/lib/settings/namespace.js.map +1 -0
  126. package/lib/settings/rpc-client.d.ts +36 -0
  127. package/lib/settings/rpc-client.d.ts.map +1 -0
  128. package/lib/settings/rpc-client.js +45 -0
  129. package/lib/settings/rpc-client.js.map +1 -0
  130. package/lib/settings/secret-disclosure.d.ts +50 -0
  131. package/lib/settings/secret-disclosure.d.ts.map +1 -0
  132. package/lib/settings/secret-disclosure.js +132 -0
  133. package/lib/settings/secret-disclosure.js.map +1 -0
  134. package/lib/settings/settings-model.d.ts +124 -0
  135. package/lib/settings/settings-model.d.ts.map +1 -0
  136. package/lib/settings/settings-model.js +118 -0
  137. package/lib/settings/settings-model.js.map +1 -0
  138. package/lib/settings/settings-rpc.d.ts +130 -0
  139. package/lib/settings/settings-rpc.d.ts.map +1 -0
  140. package/lib/settings/settings-rpc.js +233 -0
  141. package/lib/settings/settings-rpc.js.map +1 -0
  142. package/lib/settings/settings-service.d.ts +35 -0
  143. package/lib/settings/settings-service.d.ts.map +1 -0
  144. package/lib/settings/settings-service.js +171 -0
  145. package/lib/settings/settings-service.js.map +1 -0
  146. package/lib/state-dir.d.ts +28 -0
  147. package/lib/state-dir.d.ts.map +1 -0
  148. package/lib/state-dir.js +33 -0
  149. package/lib/state-dir.js.map +1 -0
  150. package/lib/stream.d.ts.map +1 -1
  151. package/lib/stream.js +3 -2
  152. package/lib/stream.js.map +1 -1
  153. package/package.json +56 -12
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Host-side credential store for dsh-connect channels. Adapts the DSH
3
+ * `credentials` provider (`resolve/describe/set/unset`, env-style refs) into a
4
+ * per-channel interface, so secrets live in the DSH credential store instead of
5
+ * the plugin config (mirroring dsh-im's `credential-store.mjs`). The web
6
+ * settings pane reports `configured` booleans and may echo store-backed secret
7
+ * values (e.g. an appId) so an upgraded user can confirm them, but secrets
8
+ * never persist to the settings state file — the credential store is the only
9
+ * place secret values are held.
10
+ *
11
+ * @module dsh-connect/settings/credential-store
12
+ */
13
+ import { CHANNELS } from "./channels.js";
14
+ /** Unwrap either resolution shape to a non-empty string, else undefined. */
15
+ function resolveValue(resolved) {
16
+ const value = typeof resolved === "string" ? resolved : resolved?.value;
17
+ return typeof value === "string" && value.length > 0 ? value : undefined;
18
+ }
19
+ /** Maps each channel's secret *config key* to its credential ref, so the
20
+ * settings service and activation can enrich a channel config from the store.
21
+ *
22
+ * This is the **authoritative list of refs a channel owns**: every ref that
23
+ * `save` can write appears here. A ref reachable only through
24
+ * {@link CREDENTIAL_GROUPS} would be writable-but-unclearable — `save` and
25
+ * `clear` both derive from this table for exactly that reason. */
26
+ export const CHANNEL_SECRET_KEYS = Object.freeze({
27
+ feishu: { appId: "DSH_CONNECT_FEISHU_APP_ID", appSecret: "DSH_CONNECT_FEISHU_APP_SECRET" },
28
+ telegram: { botToken: "DSH_CONNECT_TELEGRAM_BOT_TOKEN" },
29
+ dingtalk: {
30
+ webhookUrl: "DSH_CONNECT_DINGTALK_WEBHOOK_URL",
31
+ secret: "DSH_CONNECT_DINGTALK_SECRET",
32
+ clientId: "DSH_CONNECT_DINGTALK_CLIENT_ID",
33
+ clientSecret: "DSH_CONNECT_DINGTALK_CLIENT_SECRET",
34
+ },
35
+ web: {},
36
+ });
37
+ /**
38
+ * Credential sets that satisfy a channel, as groups-of-refs.
39
+ *
40
+ * Within a group every ref must be configured; between groups any one group
41
+ * being complete is enough. The grouping exists for DingTalk, which has two
42
+ * mutually exclusive transports — webhook push (url + signing secret) and
43
+ * stream mode (client id + secret). A flat all-of list would report a
44
+ * webhook-only user as "未配置凭据" because their stream refs are empty.
45
+ *
46
+ * An empty group list means the channel needs no credentials at all, so it is
47
+ * always satisfied — `web` mirrors the DSH Web UI in-process and holds no
48
+ * secret. Reporting that as unconfigured was pure noise in the pane's badge.
49
+ */
50
+ export const CREDENTIAL_GROUPS = Object.freeze({
51
+ feishu: [["DSH_CONNECT_FEISHU_APP_ID", "DSH_CONNECT_FEISHU_APP_SECRET"]],
52
+ telegram: [["DSH_CONNECT_TELEGRAM_BOT_TOKEN"]],
53
+ dingtalk: [
54
+ ["DSH_CONNECT_DINGTALK_WEBHOOK_URL", "DSH_CONNECT_DINGTALK_SECRET"],
55
+ ["DSH_CONNECT_DINGTALK_CLIENT_ID", "DSH_CONNECT_DINGTALK_CLIENT_SECRET"],
56
+ ],
57
+ web: [],
58
+ });
59
+ /**
60
+ * Flat, de-duplicated view of every ref named by {@link CREDENTIAL_GROUPS},
61
+ * in declaration order. Kept because existing callers and tests import it;
62
+ * new code should prefer `CHANNEL_SECRET_KEYS` for the refs a channel owns.
63
+ */
64
+ export const CREDENTIAL_REFS = Object.freeze(Object.fromEntries(CHANNELS.map((channel) => [channel, groupRefs(channel)])));
65
+ /** De-duplicated union of every ref named by a channel's groups. */
66
+ function groupRefs(channel) {
67
+ const seen = new Set();
68
+ for (const group of CREDENTIAL_GROUPS[channel] ?? []) {
69
+ for (const ref of group)
70
+ seen.add(ref);
71
+ }
72
+ return Object.freeze([...seen]);
73
+ }
74
+ /**
75
+ * Every ref a channel may have written, i.e. the union of the refs named by its
76
+ * groups and its config-key table. `clear` uses this so it cannot leave behind
77
+ * a ref that `save` was able to write.
78
+ */
79
+ function ownedRefs(channel) {
80
+ const seen = new Set(groupRefs(channel));
81
+ for (const ref of Object.values(CHANNEL_SECRET_KEYS[channel] ?? {}))
82
+ seen.add(ref);
83
+ return [...seen];
84
+ }
85
+ function assertProvider(provider) {
86
+ for (const method of ["resolve", "describe", "set", "unset"]) {
87
+ if (typeof provider[method] !== "function") {
88
+ throw new TypeError(`A DSH credential provider with ${method}() is required`);
89
+ }
90
+ }
91
+ }
92
+ /** Build a per-channel credential store over a DSH `credentials` provider. */
93
+ export function createCredentialStore(provider) {
94
+ assertProvider(provider);
95
+ return Object.freeze({
96
+ async configured(channel) {
97
+ const groups = CREDENTIAL_GROUPS[channel] ?? [];
98
+ // No credential requirement at all (e.g. the in-process Web mirror):
99
+ // nothing to be missing, so the channel counts as configured.
100
+ if (groups.length === 0)
101
+ return true;
102
+ for (const group of groups) {
103
+ const described = await Promise.all(group.map((ref) => provider.describe(ref)));
104
+ if (described.every((d) => d.configured === true))
105
+ return true;
106
+ }
107
+ return false;
108
+ },
109
+ async save(channel, values) {
110
+ // Iterate the config-key table, not the group union: it is the complete
111
+ // list of refs this channel owns. Deriving the write set from the groups
112
+ // silently dropped DingTalk's stream credentials, because only the
113
+ // webhook group was represented there — the pane reported `ok: true`
114
+ // while the clientId/clientSecret the user typed were never stored.
115
+ for (const ref of Object.values(CHANNEL_SECRET_KEYS[channel] ?? {})) {
116
+ const value = values[ref];
117
+ if (value !== undefined)
118
+ await provider.set(ref, value);
119
+ }
120
+ },
121
+ async get(channel) {
122
+ const map = CHANNEL_SECRET_KEYS[channel] ?? {};
123
+ const entries = await Promise.all(Object.entries(map).map(async ([configKey, ref]) => {
124
+ const value = resolveValue(await provider.resolve(ref));
125
+ return [configKey, value];
126
+ }));
127
+ const out = {};
128
+ for (const [configKey, value] of entries) {
129
+ if (value !== undefined)
130
+ out[configKey] = value;
131
+ }
132
+ return out;
133
+ },
134
+ async clear(channel) {
135
+ const outcomes = await Promise.allSettled(ownedRefs(channel).map((ref) => provider.unset(ref)));
136
+ if (outcomes.some((o) => o.status === "rejected")) {
137
+ throw new Error(`Unable to clear ${channel} credentials.`);
138
+ }
139
+ },
140
+ });
141
+ }
142
+ //# sourceMappingURL=credential-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/settings/credential-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Barrel for the dsh-connect web-settings stack: channel orchestration, the DSH
3
+ * credential-store adapter, the host RPC facade, the persistence service, and
4
+ * the client-side model/RPC helpers. Imported by the merged entry; also the
5
+ * `dsh-connect/settings` subpath export.
6
+ * @module dsh-connect/settings
7
+ */
8
+ export * from "./channels.js";
9
+ export * from "./credential-store.js";
10
+ export * from "./settings-rpc.js";
11
+ export * from "./settings-service.js";
12
+ export * from "./settings-model.js";
13
+ export * from "./rpc-client.js";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Barrel for the dsh-connect web-settings stack: channel orchestration, the DSH
3
+ * credential-store adapter, the host RPC facade, the persistence service, and
4
+ * the client-side model/RPC helpers. Imported by the merged entry; also the
5
+ * `dsh-connect/settings` subpath export.
6
+ * @module dsh-connect/settings
7
+ */
8
+ export * from "./channels.js";
9
+ export * from "./credential-store.js";
10
+ export * from "./settings-rpc.js";
11
+ export * from "./settings-service.js";
12
+ export * from "./settings-model.js";
13
+ export * from "./rpc-client.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/settings/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The `dsh-connect` user-settings namespace.
3
+ *
4
+ * DSH ships a first-party user-settings seam (`ctx.settings`, backed by
5
+ * `$DSH_HOME/settings.yaml` via `@deepseek-ai/dsh-settings-file`, hot-reloaded,
6
+ * atomic, file-locked, comment-preserving). Registering a namespace is how a
7
+ * plugin makes its configuration user-editable *and effective*: the official
8
+ * Plugins page renders the fields from the schema, writes go through the
9
+ * provider, and `scope.watch()` tells the plugin to re-read.
10
+ *
11
+ * Two properties of that seam shape this module:
12
+ *
13
+ * - **`installSection` is the consumer entry point.** It registers the plugin's
14
+ * own composition entry as the schema's *base* layer while the settings
15
+ * service is present, and falls back to that entry if the service detaches.
16
+ * Bare `register` would make `$DSH_HOME/settings.yaml` the only source and
17
+ * silently drop a YAML-configured profile.
18
+ * - **The schema is applied to `mergeLayers(base, userSection)` at registration
19
+ * time**, so a stored section that fails validation throws out of
20
+ * `installSection`. Every install here is therefore guarded: a hand-edited
21
+ * `settings.yaml` with a typo must degrade to the plugin config, not abort
22
+ * the plugin's `apply`.
23
+ *
24
+ * **Secrets are deliberately not part of this schema.** They live in the DSH
25
+ * credential store (`ctx.credentials`), which is where the onboarding flow and
26
+ * the `FEISHU_*`-style env layering already put them; `settings.yaml` is a
27
+ * plain document users are invited to paste into bug reports. `sectionOf()`
28
+ * projects secret keys *out* of the base for the same reason — schemastery
29
+ * preserves undeclared keys verbatim, so anything left in the base rides out
30
+ * through `describe()` and the settings RPC. The same projection guards the
31
+ * write path (`LiveConnectSection.write`), which is the mirror hazard: an
32
+ * undeclared key in a *submitted* section would be preserved into the document.
33
+ *
34
+ * `installSection` returns nothing, so the write handle it hands back is built
35
+ * here: `setSource` already wires a thunk onto `scope.get()`, which reads the
36
+ * registration's resolved value at call time — live in both directions.
37
+ *
38
+ * @module dsh-connect/settings/namespace
39
+ */
40
+ import z from "@deepseek-ai/schemastery";
41
+ import { type ChannelName, type ChannelsConfig, type LoggerLike } from "./channels.js";
42
+ /** The settings namespace this plugin owns. */
43
+ export declare const CONNECT_SETTINGS_NS = "dsh-connect";
44
+ /** Channel-agnostic defaults section of the namespace. */
45
+ export type ConnectSectionDefaults = Record<string, unknown> & {
46
+ language?: string;
47
+ notifyLevel?: string;
48
+ };
49
+ /**
50
+ * The resolved namespace value. Every field is optional: a user who only ever
51
+ * edits `channels` leaves the rest absent, and each adapter applies its own
52
+ * default for anything missing.
53
+ */
54
+ export interface ConnectSection {
55
+ channels?: ChannelName[];
56
+ channelDefaults?: ConnectSectionDefaults;
57
+ feishu?: Record<string, unknown>;
58
+ telegram?: Record<string, unknown>;
59
+ dingtalk?: Record<string, unknown>;
60
+ web?: Record<string, unknown>;
61
+ }
62
+ /**
63
+ * The namespace schema, derived from the same field tables the settings pane
64
+ * renders. Deriving rather than restating them is the point: a field added to
65
+ * `CHANNEL_CONFIG_FIELDS` becomes user-editable and hot-appliable in one edit,
66
+ * and a field that the pane exposes can never be silently unrepresentable in
67
+ * `settings.yaml`.
68
+ *
69
+ * `required(false)` on every field means an absent key stays absent — it does
70
+ * *not* materialize a default. That matters for `dmMode`, whose first option is
71
+ * the permissive one: a schema-provided default would silently open DMs for
72
+ * every user who never touched the field.
73
+ */
74
+ export declare const ConnectSectionSchema: z<Schemastery.ObjectS<{
75
+ channels: z<ChannelName[], ChannelName[]>;
76
+ channelDefaults: z<any, any>;
77
+ feishu: z<any, any>;
78
+ telegram: z<any, any>;
79
+ dingtalk: z<any, any>;
80
+ web: z<any, any>;
81
+ }>, Schemastery.ObjectT<{
82
+ channels: z<ChannelName[], ChannelName[]>;
83
+ channelDefaults: z<any, any>;
84
+ feishu: z<any, any>;
85
+ telegram: z<any, any>;
86
+ dingtalk: z<any, any>;
87
+ web: z<any, any>;
88
+ }>>;
89
+ /**
90
+ * Project a channel config onto the namespace's schema: only declared,
91
+ * non-secret keys, with `channels` always present.
92
+ *
93
+ * `channels` is emitted unconditionally because an absent array resolves to
94
+ * `[]` (schemastery's array default), which would read as "activate nothing".
95
+ *
96
+ * Secret keys are dropped here, not by the schema: `redactSecrets` only strips
97
+ * nodes declared with `role("secret")`, and we declare none — so the projection
98
+ * is what keeps an `appSecret` from a legacy profile out of the settings
99
+ * document and off the RPC.
100
+ *
101
+ * The argument is typed `unknown` because this doubles as the *write* guard:
102
+ * a section submitted through the settings RPC is untrusted JSON, and it must
103
+ * go through the same projection before it can reach the document.
104
+ */
105
+ export declare function sectionOf(config: unknown): ConnectSection;
106
+ /**
107
+ * The slice of the settings service this module calls. Declared structurally so
108
+ * the plugin keeps no dependency on `@deepseek-ai/dsh-settings` — the service is
109
+ * a `dsh-base` row, present whenever dsh-connect loads, and a missing method
110
+ * degrades to "no settings support" rather than a crash.
111
+ */
112
+ export interface SettingsProviderLike {
113
+ installSection?(owner: unknown, ns: string, schema: unknown, entry: unknown, hooks: {
114
+ setSource: (source: () => unknown) => void;
115
+ onChange: () => void;
116
+ }): void;
117
+ /** Resolved value of a registered namespace, `undefined` while unregistered. */
118
+ get?(ns: string): unknown;
119
+ /**
120
+ * Replace a registered namespace's *user* section wholesale (validated by the
121
+ * provider, persisted by whichever file provider is wired up). Resolves after
122
+ * the new value is committed, so a read straight after it is fresh.
123
+ *
124
+ * Replace rather than merge, because the settings pane always submits its
125
+ * complete visible config: a merge would make a cleared field un-clearable
126
+ * (the stale value stays in the user layer and reappears on the next read).
127
+ * The declared-key set is identical for both writers of this namespace (this
128
+ * plugin's pane and the host's own Plugins page, which renders the same
129
+ * schema), so a replace cannot drop anything either surface can produce.
130
+ */
131
+ replace?(ns: string, section: unknown, expectedRevision?: number): Promise<unknown>;
132
+ }
133
+ /** A live handle onto the installed namespace: read the effective section, write a new one. */
134
+ export interface LiveConnectSection {
135
+ /** The section in force right now (base layered under the user's section). */
136
+ read(): ConnectSection;
137
+ /**
138
+ * Replace the user's section with the declared-key projection of `config`.
139
+ * Rejects if the provider's schema refuses the result — the caller is
140
+ * expected to surface that rather than swallow it.
141
+ */
142
+ write(config: unknown): Promise<void>;
143
+ }
144
+ /** Outcome of wiring the namespace. */
145
+ export interface InstallConnectSectionResult {
146
+ /** True when this fiber owns the namespace and receives live updates. */
147
+ live: boolean;
148
+ /** The section in force at install time (resolved, or the entry fallback). */
149
+ section: ConnectSection;
150
+ /**
151
+ * Read/write onto the registration. Present only when this fiber owns a live
152
+ * namespace *and* the provider exposes a write method; otherwise the caller
153
+ * keeps its own fallback (the plugin config, or the legacy state file).
154
+ */
155
+ handle?: LiveConnectSection;
156
+ }
157
+ export interface InstallConnectSectionOptions<Ctx extends LoggerLike> {
158
+ /** Context whose unload releases the registration. */
159
+ owner: Ctx;
160
+ settings: SettingsProviderLike | undefined;
161
+ /** The plugin's own config: the schema's `base` layer and the fallback. */
162
+ entry: ChannelsConfig | null | undefined;
163
+ /** Called with the in-force section now and on every change. */
164
+ onChange: (section: ConnectSection) => void;
165
+ }
166
+ /**
167
+ * Register `dsh-connect` and report the section in force.
168
+ *
169
+ * Never throws: a missing settings service, an already-claimed namespace, or a
170
+ * `settings.yaml` section that fails validation all fall back to the plugin's
171
+ * own config with a warning. A user's typo in a settings document must not be
172
+ * able to stop the bridge from starting.
173
+ */
174
+ export declare function installConnectSection<Ctx extends LoggerLike>(options: InstallConnectSectionOptions<Ctx>): InstallConnectSectionResult;
175
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../src/settings/namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,CAAC,MAAM,0BAA0B,CAAC;AACzC,OAAO,EAAY,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAOjG,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAEjD,0DAA0D;AAC1D,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3G;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAyBD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;GAO/B,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,CAoBzD;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,cAAc,CAAC,CACb,KAAK,EAAE,OAAO,EACd,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,OAAO,EACd,KAAK,EAAE;QAAE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,OAAO,KAAK,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;KAAE,GAC1E,IAAI,CAAC;IACR,gFAAgF;IAChF,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrF;AAED,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,IAAI,IAAI,cAAc,CAAC;IACvB;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,uCAAuC;AACvC,MAAM,WAAW,2BAA2B;IAC1C,yEAAyE;IACzE,IAAI,EAAE,OAAO,CAAC;IACd,8EAA8E;IAC9E,OAAO,EAAE,cAAc,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B,CAAC,GAAG,SAAS,UAAU;IAClE,sDAAsD;IACtD,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC3C,2EAA2E;IAC3E,KAAK,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,gEAAgE;IAChE,QAAQ,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,SAAS,UAAU,EAC1D,OAAO,EAAE,4BAA4B,CAAC,GAAG,CAAC,GACzC,2BAA2B,CAmE7B"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * The `dsh-connect` user-settings namespace.
3
+ *
4
+ * DSH ships a first-party user-settings seam (`ctx.settings`, backed by
5
+ * `$DSH_HOME/settings.yaml` via `@deepseek-ai/dsh-settings-file`, hot-reloaded,
6
+ * atomic, file-locked, comment-preserving). Registering a namespace is how a
7
+ * plugin makes its configuration user-editable *and effective*: the official
8
+ * Plugins page renders the fields from the schema, writes go through the
9
+ * provider, and `scope.watch()` tells the plugin to re-read.
10
+ *
11
+ * Two properties of that seam shape this module:
12
+ *
13
+ * - **`installSection` is the consumer entry point.** It registers the plugin's
14
+ * own composition entry as the schema's *base* layer while the settings
15
+ * service is present, and falls back to that entry if the service detaches.
16
+ * Bare `register` would make `$DSH_HOME/settings.yaml` the only source and
17
+ * silently drop a YAML-configured profile.
18
+ * - **The schema is applied to `mergeLayers(base, userSection)` at registration
19
+ * time**, so a stored section that fails validation throws out of
20
+ * `installSection`. Every install here is therefore guarded: a hand-edited
21
+ * `settings.yaml` with a typo must degrade to the plugin config, not abort
22
+ * the plugin's `apply`.
23
+ *
24
+ * **Secrets are deliberately not part of this schema.** They live in the DSH
25
+ * credential store (`ctx.credentials`), which is where the onboarding flow and
26
+ * the `FEISHU_*`-style env layering already put them; `settings.yaml` is a
27
+ * plain document users are invited to paste into bug reports. `sectionOf()`
28
+ * projects secret keys *out* of the base for the same reason — schemastery
29
+ * preserves undeclared keys verbatim, so anything left in the base rides out
30
+ * through `describe()` and the settings RPC. The same projection guards the
31
+ * write path (`LiveConnectSection.write`), which is the mirror hazard: an
32
+ * undeclared key in a *submitted* section would be preserved into the document.
33
+ *
34
+ * `installSection` returns nothing, so the write handle it hands back is built
35
+ * here: `setSource` already wires a thunk onto `scope.get()`, which reads the
36
+ * registration's resolved value at call time — live in both directions.
37
+ *
38
+ * @module dsh-connect/settings/namespace
39
+ */
40
+ import z from "@deepseek-ai/schemastery";
41
+ import { CHANNELS } from "./channels.js";
42
+ import { CHANNEL_CONFIG_FIELDS, CHANNEL_DEFAULT_FIELDS, } from "./settings-model.js";
43
+ /** The settings namespace this plugin owns. */
44
+ export const CONNECT_SETTINGS_NS = "dsh-connect";
45
+ /** schemastery schema for one non-secret config field. */
46
+ function fieldSchema(field) {
47
+ switch (field.kind) {
48
+ case "number":
49
+ return z.number().required(false);
50
+ case "boolean":
51
+ return z.boolean().required(false);
52
+ case "select":
53
+ return field.options !== undefined && field.options.length > 0
54
+ ? z.union(field.options.map((option) => z.const(option))).required(false)
55
+ : z.string().required(false);
56
+ default:
57
+ return z.string().required(false);
58
+ }
59
+ }
60
+ /** Build an object schema from a field table (unknown keys pass through). */
61
+ function objectSchema(fields) {
62
+ return z.object(Object.fromEntries(fields.map((field) => [field.key, fieldSchema(field)])));
63
+ }
64
+ /**
65
+ * The namespace schema, derived from the same field tables the settings pane
66
+ * renders. Deriving rather than restating them is the point: a field added to
67
+ * `CHANNEL_CONFIG_FIELDS` becomes user-editable and hot-appliable in one edit,
68
+ * and a field that the pane exposes can never be silently unrepresentable in
69
+ * `settings.yaml`.
70
+ *
71
+ * `required(false)` on every field means an absent key stays absent — it does
72
+ * *not* materialize a default. That matters for `dmMode`, whose first option is
73
+ * the permissive one: a schema-provided default would silently open DMs for
74
+ * every user who never touched the field.
75
+ */
76
+ export const ConnectSectionSchema = z.object({
77
+ channels: z.array(z.union(CHANNELS.map((name) => z.const(name)))).required(false),
78
+ channelDefaults: objectSchema(CHANNEL_DEFAULT_FIELDS).required(false),
79
+ feishu: objectSchema(CHANNEL_CONFIG_FIELDS.feishu).required(false),
80
+ telegram: objectSchema(CHANNEL_CONFIG_FIELDS.telegram).required(false),
81
+ dingtalk: objectSchema(CHANNEL_CONFIG_FIELDS.dingtalk).required(false),
82
+ web: objectSchema(CHANNEL_CONFIG_FIELDS.web).required(false),
83
+ });
84
+ /**
85
+ * Project a channel config onto the namespace's schema: only declared,
86
+ * non-secret keys, with `channels` always present.
87
+ *
88
+ * `channels` is emitted unconditionally because an absent array resolves to
89
+ * `[]` (schemastery's array default), which would read as "activate nothing".
90
+ *
91
+ * Secret keys are dropped here, not by the schema: `redactSecrets` only strips
92
+ * nodes declared with `role("secret")`, and we declare none — so the projection
93
+ * is what keeps an `appSecret` from a legacy profile out of the settings
94
+ * document and off the RPC.
95
+ *
96
+ * The argument is typed `unknown` because this doubles as the *write* guard:
97
+ * a section submitted through the settings RPC is untrusted JSON, and it must
98
+ * go through the same projection before it can reach the document.
99
+ */
100
+ export function sectionOf(config) {
101
+ const source = (config !== null && typeof config === "object" ? config : {});
102
+ const section = { channels: (source.channels ?? CHANNELS) };
103
+ const defaults = {};
104
+ const rawDefaults = (source.channelDefaults ?? {});
105
+ for (const field of CHANNEL_DEFAULT_FIELDS) {
106
+ if (rawDefaults[field.key] !== undefined)
107
+ defaults[field.key] = rawDefaults[field.key];
108
+ }
109
+ if (Object.keys(defaults).length > 0)
110
+ section.channelDefaults = defaults;
111
+ for (const name of CHANNELS) {
112
+ const raw = (source[name] ?? {});
113
+ const projected = {};
114
+ for (const field of CHANNEL_CONFIG_FIELDS[name]) {
115
+ if (raw[field.key] !== undefined)
116
+ projected[field.key] = raw[field.key];
117
+ }
118
+ if (Object.keys(projected).length > 0)
119
+ section[name] = projected;
120
+ }
121
+ return section;
122
+ }
123
+ /**
124
+ * Register `dsh-connect` and report the section in force.
125
+ *
126
+ * Never throws: a missing settings service, an already-claimed namespace, or a
127
+ * `settings.yaml` section that fails validation all fall back to the plugin's
128
+ * own config with a warning. A user's typo in a settings document must not be
129
+ * able to stop the bridge from starting.
130
+ */
131
+ export function installConnectSection(options) {
132
+ const { settings, entry, onChange } = options;
133
+ const entrySection = sectionOf(entry);
134
+ const warn = (message) => {
135
+ options.owner.logger?.warn?.(`connect: ${message}`);
136
+ };
137
+ if (settings?.installSection === undefined) {
138
+ // No settings service: the plugin config is the only source, as it was
139
+ // before this namespace existed.
140
+ onChange(entrySection);
141
+ return { live: false, section: entrySection };
142
+ }
143
+ let source = () => entrySection;
144
+ try {
145
+ settings.installSection(options.owner, CONNECT_SETTINGS_NS, ConnectSectionSchema, entrySection, {
146
+ setSource: (fn) => {
147
+ source = () => fn();
148
+ },
149
+ onChange: () => onChange(source()),
150
+ });
151
+ }
152
+ catch (error) {
153
+ // Two reachable causes, both handled the same way:
154
+ // - a stored section that fails the schema (a hand-edited settings.yaml);
155
+ // - the namespace is already registered by an earlier fiber. `register`
156
+ // is an effect on the *settings service*, which outlives a plugin
157
+ // reload, so a live config reload lands here.
158
+ // `get()` returns the previously resolved value in the second case, which
159
+ // keeps the user's saved settings effective — only the live watch is gone
160
+ // until the process restarts.
161
+ const resolved = readRegistered(settings);
162
+ if (resolved !== undefined) {
163
+ warn(`settings namespace "${CONNECT_SETTINGS_NS}" is already registered ` +
164
+ `(the plugin reloaded without a host restart); using the saved section — ` +
165
+ `restart dsh to re-enable live settings updates`);
166
+ onChange(resolved);
167
+ return { live: false, section: resolved };
168
+ }
169
+ warn(`settings namespace "${CONNECT_SETTINGS_NS}" unavailable (${String(error)}); ` +
170
+ `falling back to the plugin config`);
171
+ onChange(entrySection);
172
+ return { live: false, section: entrySection };
173
+ }
174
+ // `source` is reassigned (not rebound) by `setSource`, so a thunk closing
175
+ // over the variable reads whatever the provider's `scope.get()` returns now.
176
+ const read = () => source();
177
+ const replace = settings.replace?.bind(settings);
178
+ return {
179
+ live: true,
180
+ section: source(),
181
+ ...(replace === undefined
182
+ ? {}
183
+ : {
184
+ handle: {
185
+ read,
186
+ write: async (config) => {
187
+ await replace(CONNECT_SETTINGS_NS, sectionOf(config));
188
+ },
189
+ },
190
+ }),
191
+ };
192
+ }
193
+ /** Read a namespace an earlier fiber registered, if any. */
194
+ function readRegistered(settings) {
195
+ const value = settings.get?.(CONNECT_SETTINGS_NS);
196
+ if (value === null || typeof value !== "object")
197
+ return undefined;
198
+ return value;
199
+ }
200
+ //# sourceMappingURL=namespace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../src/settings/namespace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Client-side RPC helper for the dsh-connect web settings pane.
3
+ *
4
+ * The frontend talks to the host over the `/dsh-connect` channel; the channel
5
+ * returns `{ ok, value }`/`{ ok:false, error }` envelopes (see `settings-rpc.js`).
6
+ * This helper unwraps the envelope and throws a typed error on failure, so the
7
+ * UI can catch it and render a message. Pure + dependency-free for easy testing.
8
+ *
9
+ * @module dsh-connect/settings/rpc-client
10
+ */
11
+ import type { SettingsSnapshot } from "./settings-rpc.js";
12
+ /** The `ctx.connection.rpc.call` signature the settings pane uses. */
13
+ export type RpcCall = (endpoint: string, payload?: unknown, signal?: AbortSignal) => Promise<{
14
+ ok: boolean;
15
+ value?: unknown;
16
+ error?: {
17
+ code: string;
18
+ message: string;
19
+ };
20
+ }>;
21
+ /** A typed RPC failure, surfaced to the settings UI. */
22
+ export declare class RpcError extends Error {
23
+ readonly code: string;
24
+ constructor(code: string, message: string);
25
+ }
26
+ /** Invoke one endpoint and unwrap the envelope (throws `RpcError` on failure). */
27
+ export declare function callRpc<T = unknown>(rpcCall: RpcCall, endpoint: string, payload?: unknown, signal?: AbortSignal): Promise<T>;
28
+ /** Load the current settings snapshot via `settings.get`. */
29
+ export declare function loadSettings(rpcCall: RpcCall): Promise<SettingsSnapshot>;
30
+ /** Persist the settings via `settings.save`. */
31
+ export declare function saveSettings(rpcCall: RpcCall, config: Record<string, unknown>): Promise<SettingsSnapshot>;
32
+ /** Persist channel secrets to the credential store via `credentials.save`. */
33
+ export declare function saveCredentials(rpcCall: RpcCall, channel: string, values: Record<string, string>): Promise<SettingsSnapshot>;
34
+ /** Ask the host for a fresh status snapshot via `settings.status`. */
35
+ export declare function loadStatus(rpcCall: RpcCall): Promise<SettingsSnapshot>;
36
+ //# sourceMappingURL=rpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/settings/rpc-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,sEAAsE;AACtE,MAAM,MAAM,OAAO,GAAG,CACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC;AAE1F,wDAAwD;AACxD,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,CAAC,GAAG,OAAO,EACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAExE;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEzG;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtE"}