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.
- package/README.i18n.yaml +2 -2
- package/README.md +134 -17
- package/README.zh.md +110 -17
- package/client/client.js +510 -80
- package/client/client.js.map +4 -4
- package/client/locale.mjs +227 -0
- package/client/panel-state.mjs +54 -0
- package/client/settings-client.mjs +272 -60
- package/docs/images/settings-advanced-en.png +0 -0
- package/docs/images/settings-advanced-zh.png +0 -0
- package/docs/images/settings-defaults-en.png +0 -0
- package/docs/images/settings-defaults-zh.png +0 -0
- package/docs/images/settings-overview-en.png +0 -0
- package/docs/images/settings-overview-zh.png +0 -0
- package/lib/binding.d.ts.map +1 -1
- package/lib/binding.js +2 -2
- package/lib/binding.js.map +1 -1
- package/lib/channels/dingtalk/message.d.ts.map +1 -1
- package/lib/channels/dingtalk/message.js +7 -0
- package/lib/channels/dingtalk/message.js.map +1 -1
- package/lib/channels/feishu/adapter.d.ts +6 -12
- package/lib/channels/feishu/adapter.d.ts.map +1 -1
- package/lib/channels/feishu/adapter.js +2 -17
- package/lib/channels/feishu/adapter.js.map +1 -1
- package/lib/channels/feishu/i18n.d.ts +2 -0
- package/lib/channels/feishu/i18n.d.ts.map +1 -1
- package/lib/channels/feishu/i18n.js +2 -0
- package/lib/channels/feishu/i18n.js.map +1 -1
- package/lib/channels/feishu/index.d.ts +23 -2
- package/lib/channels/feishu/index.d.ts.map +1 -1
- package/lib/channels/feishu/index.js +32 -6
- package/lib/channels/feishu/index.js.map +1 -1
- package/lib/channels/telegram/adapter.d.ts.map +1 -1
- package/lib/channels/telegram/adapter.js +7 -1
- package/lib/channels/telegram/adapter.js.map +1 -1
- package/lib/chat-key.d.ts +29 -0
- package/lib/chat-key.d.ts.map +1 -0
- package/lib/chat-key.js +38 -0
- package/lib/chat-key.js.map +1 -0
- package/lib/index.d.ts +15 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +201 -31
- package/lib/index.js.map +1 -1
- package/lib/runner.d.ts +54 -0
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js +158 -23
- package/lib/runner.js.map +1 -1
- package/lib/scheduler.d.ts.map +1 -1
- package/lib/scheduler.js +2 -2
- package/lib/scheduler.js.map +1 -1
- package/lib/service.d.ts +16 -0
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +46 -2
- package/lib/service.js.map +1 -1
- package/lib/settings/channel-runtime.d.ts +75 -0
- package/lib/settings/channel-runtime.d.ts.map +1 -0
- package/lib/settings/channel-runtime.js +165 -0
- package/lib/settings/channel-runtime.js.map +1 -0
- package/lib/settings/channels.d.ts +11 -0
- package/lib/settings/channels.d.ts.map +1 -1
- package/lib/settings/channels.js +31 -0
- package/lib/settings/channels.js.map +1 -1
- package/lib/settings/credential-store.d.ts +52 -6
- package/lib/settings/credential-store.d.ts.map +1 -1
- package/lib/settings/credential-store.js +92 -18
- package/lib/settings/credential-store.js.map +1 -1
- package/lib/settings/namespace.d.ts +175 -0
- package/lib/settings/namespace.d.ts.map +1 -0
- package/lib/settings/namespace.js +200 -0
- package/lib/settings/namespace.js.map +1 -0
- package/lib/settings/secret-disclosure.d.ts +50 -0
- package/lib/settings/secret-disclosure.d.ts.map +1 -0
- package/lib/settings/secret-disclosure.js +132 -0
- package/lib/settings/secret-disclosure.js.map +1 -0
- package/lib/settings/settings-model.d.ts +17 -0
- package/lib/settings/settings-model.d.ts.map +1 -1
- package/lib/settings/settings-model.js +8 -0
- package/lib/settings/settings-model.js.map +1 -1
- package/lib/settings/settings-rpc.d.ts +71 -3
- package/lib/settings/settings-rpc.d.ts.map +1 -1
- package/lib/settings/settings-rpc.js +167 -24
- package/lib/settings/settings-rpc.js.map +1 -1
- package/lib/settings/settings-service.d.ts +9 -1
- package/lib/settings/settings-service.d.ts.map +1 -1
- package/lib/settings/settings-service.js +74 -6
- package/lib/settings/settings-service.js.map +1 -1
- package/lib/state-dir.d.ts +28 -0
- package/lib/state-dir.d.ts.map +1 -0
- package/lib/state-dir.js +33 -0
- package/lib/state-dir.js.map +1 -0
- package/lib/stream.d.ts.map +1 -1
- package/lib/stream.js +3 -2
- package/lib/stream.js.map +1 -1
- package/package.json +11 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/settings/credential-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/settings/credential-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of a stored secret the settings pane is allowed to show.
|
|
3
|
+
*
|
|
4
|
+
* The user has to be able to *confirm* what they configured — a pane that only
|
|
5
|
+
* ever says 「已配置」 cannot tell a correct appId from a typo — but a browser
|
|
6
|
+
* tab (or a screenshot of one) must never hold a usable credential. The two are
|
|
7
|
+
* reconciled here: the host masks the value **before it crosses the wire**, so
|
|
8
|
+
* what the pane receives is already unusable, and `maskSecret` is the only
|
|
9
|
+
* place that decides how much survives.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately pure and dependency-free: the host (`settings-service`) masks
|
|
12
|
+
* with it and the client bundle imports the same compiled module for the input
|
|
13
|
+
* `type`, so the two can never drift into disagreeing about which keys are
|
|
14
|
+
* confidential.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-connect/settings/secret-disclosure
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* - `full` — shown verbatim. For identifiers, not authenticators.
|
|
20
|
+
* - `mask` — head and tail survive, the middle does not.
|
|
21
|
+
* - `url` — a URL whose *query* carries the token, so only that part is masked.
|
|
22
|
+
*/
|
|
23
|
+
export type Disclosure = "full" | "mask" | "url";
|
|
24
|
+
/**
|
|
25
|
+
* Per-config-key policy, keyed by the channel config key (`CREDENTIAL`-style
|
|
26
|
+
* keys are unique across channels: no channel has two fields that share a name
|
|
27
|
+
* but want different treatment).
|
|
28
|
+
*
|
|
29
|
+
* `appId` / `clientId` are `full` on purpose. They are **identifiers**: they
|
|
30
|
+
* appear in the URL or body of every outbound API call, are readable in the
|
|
31
|
+
* vendor's own console, and grant nothing without the paired secret. Masking
|
|
32
|
+
* them would cost the user the one thing this pane is for — checking that the
|
|
33
|
+
* id they pasted is the one they meant — and buy no secrecy at all.
|
|
34
|
+
*/
|
|
35
|
+
export declare const SECRET_DISCLOSURE: Record<string, Disclosure>;
|
|
36
|
+
/** Policy for a config key; unknown keys are treated as confidential. */
|
|
37
|
+
export declare function disclosureOf(configKey: string): Disclosure;
|
|
38
|
+
/** True when the pane should render this key as a password input. */
|
|
39
|
+
export declare function isMaskedSecret(configKey: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The display preview for one stored secret. Never returns the input: for
|
|
42
|
+
* `mask` and `url` the returned string is lossy by construction, so a caller
|
|
43
|
+
* that echoes it to a browser cannot leak a usable credential.
|
|
44
|
+
*
|
|
45
|
+
* Surrounding whitespace is trimmed first — a pasted value commonly carries a
|
|
46
|
+
* trailing newline, and the mask should describe the stored value, not the
|
|
47
|
+
* paste.
|
|
48
|
+
*/
|
|
49
|
+
export declare function maskSecret(configKey: string, value: string): string;
|
|
50
|
+
//# sourceMappingURL=secret-disclosure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-disclosure.d.ts","sourceRoot":"","sources":["../../src/settings/secret-disclosure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAQvD,CAAC;AAkBH,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAE1D;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEzD;AAqDD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWnE"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much of a stored secret the settings pane is allowed to show.
|
|
3
|
+
*
|
|
4
|
+
* The user has to be able to *confirm* what they configured — a pane that only
|
|
5
|
+
* ever says 「已配置」 cannot tell a correct appId from a typo — but a browser
|
|
6
|
+
* tab (or a screenshot of one) must never hold a usable credential. The two are
|
|
7
|
+
* reconciled here: the host masks the value **before it crosses the wire**, so
|
|
8
|
+
* what the pane receives is already unusable, and `maskSecret` is the only
|
|
9
|
+
* place that decides how much survives.
|
|
10
|
+
*
|
|
11
|
+
* Deliberately pure and dependency-free: the host (`settings-service`) masks
|
|
12
|
+
* with it and the client bundle imports the same compiled module for the input
|
|
13
|
+
* `type`, so the two can never drift into disagreeing about which keys are
|
|
14
|
+
* confidential.
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-connect/settings/secret-disclosure
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Per-config-key policy, keyed by the channel config key (`CREDENTIAL`-style
|
|
20
|
+
* keys are unique across channels: no channel has two fields that share a name
|
|
21
|
+
* but want different treatment).
|
|
22
|
+
*
|
|
23
|
+
* `appId` / `clientId` are `full` on purpose. They are **identifiers**: they
|
|
24
|
+
* appear in the URL or body of every outbound API call, are readable in the
|
|
25
|
+
* vendor's own console, and grant nothing without the paired secret. Masking
|
|
26
|
+
* them would cost the user the one thing this pane is for — checking that the
|
|
27
|
+
* id they pasted is the one they meant — and buy no secrecy at all.
|
|
28
|
+
*/
|
|
29
|
+
export const SECRET_DISCLOSURE = Object.freeze({
|
|
30
|
+
appId: "full",
|
|
31
|
+
clientId: "full",
|
|
32
|
+
appSecret: "mask",
|
|
33
|
+
clientSecret: "mask",
|
|
34
|
+
botToken: "mask",
|
|
35
|
+
secret: "mask",
|
|
36
|
+
webhookUrl: "url",
|
|
37
|
+
});
|
|
38
|
+
/** Query parameters whose *value* is a credential and must not be echoed. */
|
|
39
|
+
const SECRET_QUERY_PARAM = /(secret|token|key|sign)/i;
|
|
40
|
+
/** Characters kept visible on each end of a masked value. */
|
|
41
|
+
const EDGE = 4;
|
|
42
|
+
/**
|
|
43
|
+
* Below this length there is no middle to hide — showing 4+4 of a 10-character
|
|
44
|
+
* value would print it in full — so short values collapse to a fixed-width
|
|
45
|
+
* mask, which also avoids leaking the length.
|
|
46
|
+
*/
|
|
47
|
+
const MIN_MASKABLE = 12;
|
|
48
|
+
/** The width of the all-bullets mask used for values too short to split. */
|
|
49
|
+
const BULLETS = "••••••";
|
|
50
|
+
/** Policy for a config key; unknown keys are treated as confidential. */
|
|
51
|
+
export function disclosureOf(configKey) {
|
|
52
|
+
return SECRET_DISCLOSURE[configKey] ?? "mask";
|
|
53
|
+
}
|
|
54
|
+
/** True when the pane should render this key as a password input. */
|
|
55
|
+
export function isMaskedSecret(configKey) {
|
|
56
|
+
return disclosureOf(configKey) !== "full";
|
|
57
|
+
}
|
|
58
|
+
/** `head…tail`, or the bullets mask when the value is too short to split. */
|
|
59
|
+
function maskMiddle(value) {
|
|
60
|
+
if (value.length < MIN_MASKABLE)
|
|
61
|
+
return BULLETS;
|
|
62
|
+
return value.slice(0, EDGE) + "…" + value.slice(-EDGE);
|
|
63
|
+
}
|
|
64
|
+
/** Escape a query-parameter name for use inside a `RegExp`. */
|
|
65
|
+
function escapeForRegExp(text) {
|
|
66
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Mask the credential-bearing query parameters of a URL, keeping the origin,
|
|
70
|
+
* path and the innocent parameters readable.
|
|
71
|
+
*
|
|
72
|
+
* Masking such a URL end-to-end would defeat the purpose: DingTalk's webhook is
|
|
73
|
+
* `https://oapi.dingtalk.com/robot/send?access_token=…`, so `https…bcde` tells
|
|
74
|
+
* the user nothing about whether they pasted the right robot. Falls back to the
|
|
75
|
+
* plain mask when the value isn't a parseable URL (a bare token, a typo, an
|
|
76
|
+
* empty-ish string).
|
|
77
|
+
*
|
|
78
|
+
* The masking is a surgical replace on the original text rather than a
|
|
79
|
+
* re-serialize through `URLSearchParams`, which would percent-encode the `…`
|
|
80
|
+
* into `%E2%80%A6` and render the preview unreadable.
|
|
81
|
+
*/
|
|
82
|
+
function maskUrl(value) {
|
|
83
|
+
let url;
|
|
84
|
+
try {
|
|
85
|
+
url = new URL(value);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return maskMiddle(value);
|
|
89
|
+
}
|
|
90
|
+
let masked = value;
|
|
91
|
+
for (const key of new Set(url.searchParams.keys())) {
|
|
92
|
+
if (!SECRET_QUERY_PARAM.test(key))
|
|
93
|
+
continue;
|
|
94
|
+
const pattern = new RegExp(`([?&]${escapeForRegExp(key)}=)([^&#]*)`, "g");
|
|
95
|
+
masked = masked.replace(pattern, (whole, prefix, held) => {
|
|
96
|
+
// Tolerate a percent-encoded token: decode to measure it, mask the
|
|
97
|
+
// decoded form, and splice the readable result back into the raw text.
|
|
98
|
+
let decoded = held;
|
|
99
|
+
try {
|
|
100
|
+
decoded = decodeURIComponent(held);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
/* not encoded — use as-is */
|
|
104
|
+
}
|
|
105
|
+
return decoded.length === 0 ? whole : prefix + maskMiddle(decoded);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return masked;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The display preview for one stored secret. Never returns the input: for
|
|
112
|
+
* `mask` and `url` the returned string is lossy by construction, so a caller
|
|
113
|
+
* that echoes it to a browser cannot leak a usable credential.
|
|
114
|
+
*
|
|
115
|
+
* Surrounding whitespace is trimmed first — a pasted value commonly carries a
|
|
116
|
+
* trailing newline, and the mask should describe the stored value, not the
|
|
117
|
+
* paste.
|
|
118
|
+
*/
|
|
119
|
+
export function maskSecret(configKey, value) {
|
|
120
|
+
const trimmed = value.trim();
|
|
121
|
+
if (trimmed.length === 0)
|
|
122
|
+
return "";
|
|
123
|
+
switch (disclosureOf(configKey)) {
|
|
124
|
+
case "full":
|
|
125
|
+
return trimmed;
|
|
126
|
+
case "url":
|
|
127
|
+
return maskUrl(trimmed);
|
|
128
|
+
default:
|
|
129
|
+
return maskMiddle(trimmed);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=secret-disclosure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-disclosure.js","sourceRoot":"","sources":["../../src/settings/secret-disclosure.ts"],"names":[],"mappings":"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"}
|
|
@@ -92,8 +92,25 @@ export interface SettingsForm {
|
|
|
92
92
|
channels: ChannelName[];
|
|
93
93
|
channelDefaults: Record<string, unknown>;
|
|
94
94
|
channelConfigs: Record<string, Record<string, unknown>>;
|
|
95
|
+
/** Secret inputs the user is *typing*: written on save, never seeded by a read. */
|
|
95
96
|
secrets: Record<string, Record<string, string>>;
|
|
97
|
+
/**
|
|
98
|
+
* Whether a value is already stored for a secret key, for the placeholder.
|
|
99
|
+
* Separate from `secrets` because the two have opposite lifetimes: presence
|
|
100
|
+
* comes from the snapshot and the typed values start empty and are cleared
|
|
101
|
+
* after a save — a read must never be able to populate an input.
|
|
102
|
+
*/
|
|
103
|
+
secretPresence: Record<string, Record<string, boolean>>;
|
|
104
|
+
/**
|
|
105
|
+
* Host-masked previews of the stored secrets, so the user can confirm what
|
|
106
|
+
* they configured. Rendered as read-only text and **never** fed into
|
|
107
|
+
* `secrets`: a masked value is not a credential, and treating one as a value
|
|
108
|
+
* to save would overwrite a working secret with its own mask.
|
|
109
|
+
*/
|
|
110
|
+
secretPreviews: Record<string, Record<string, string>>;
|
|
96
111
|
settingsStatePath?: string;
|
|
112
|
+
/** True when the section lives in the settings namespace: a save is immediate and durable. */
|
|
113
|
+
live: boolean;
|
|
97
114
|
}
|
|
98
115
|
/** Build the initial form from a snapshot. */
|
|
99
116
|
export declare function snapshotToForm(snapshot: SettingsSnapshot): SettingsForm;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-model.d.ts","sourceRoot":"","sources":["../../src/settings/settings-model.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,EAE7B,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsB8B,CAAC;AAEjE,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAE,SAAS,WAAW,EAGxD,CAAC;AAEF,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAclF;AAED,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"settings-model.d.ts","sourceRoot":"","sources":["../../src/settings/settings-model.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,EAE7B,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsB8B,CAAC;AAEjE,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,EAAE,SAAS,WAAW,EAGxD,CAAC;AAEF,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAclF;AAED,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8FAA8F;IAC9F,IAAI,EAAE,OAAO,CAAC;CACf;AAED,8CAA8C;AAC9C,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,YAAY,CAoBvE;AASD,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAU3E;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAAE,CAM9G"}
|