import { type ChannelName } from "./channels.js"; import type { ChannelConnectionStatus } from "./channel-status.js"; import type { SettingsSnapshot } from "./settings-rpc.js"; /** Secret config-keys per channel, for rendering secret inputs. */ export declare const CHANNEL_SECRET_FIELDS: Record; /** * A non-secret, user-editable config field. `kind` drives how the pane renders * the control and how `coerceConfigValue` normalizes the raw input (text → * string, number → finite number, boolean → false on empty, select → one of * `options`, list → a non-empty `string[]`). */ export interface ConfigField { /** * The field's identity **and** its path in the channel config. Most keys are * flat (`transport`), but a key may be dotted when the adapter reads it from a * sub-object (`stream.url`) — one rule, so no field needs a second lookup to * find where its value lives. The form stores values under this same key, and * only `snapshotToForm`/`buildConfigSave` translate to and from the nested * config shape. */ key: string; kind: 'text' | 'number' | 'boolean' | 'select' | 'list'; options?: string[]; /** Human label; defaults to the key if absent. */ label?: string; } /** * Non-secret per-channel fields the pane exposes, mirroring the keys used by * each channel adapter config (see `examples/profile-cordis.patch.yml`). Order * is the render order; keep secrets separate (`CHANNEL_SECRET_FIELDS`). */ export declare const CHANNEL_CONFIG_FIELDS: { readonly feishu: readonly [{ readonly key: "transport"; readonly kind: "select"; readonly options: ["websocket", "webhook"]; readonly label: "transport"; }, { readonly key: "requireMention"; readonly kind: "boolean"; readonly label: "requireMention"; }, { readonly key: "dmMode"; readonly kind: "select"; readonly options: ["open", "allowlist", "pair", "disabled"]; readonly label: "dmMode"; }, { readonly key: "language"; readonly kind: "select"; readonly options: ["zh", "en"]; readonly label: "language"; }, { readonly key: "threadIsolation"; readonly kind: "boolean"; readonly label: "threadIsolation"; }, { readonly key: "onboarding"; readonly kind: "boolean"; readonly label: "onboarding"; }, { readonly key: "webhookPort"; readonly kind: "number"; readonly label: "webhookPort"; }, { readonly key: "webhookPath"; readonly kind: "text"; readonly label: "webhookPath"; }, { readonly key: "allowUsers"; readonly kind: "list"; readonly label: "allowUsers"; }, { readonly key: "allowChats"; readonly kind: "list"; readonly label: "allowChats"; }]; readonly telegram: readonly [{ readonly key: "requireMention"; readonly kind: "boolean"; readonly label: "requireMention"; }, { readonly key: "language"; readonly kind: "select"; readonly options: ["zh", "en"]; readonly label: "language"; }, { readonly key: "pollingTimeoutSeconds"; readonly kind: "number"; readonly label: "pollingTimeoutSeconds"; }, { readonly key: "baseUrl"; readonly kind: "text"; readonly label: "baseUrl"; }, { readonly key: "allowUsers"; readonly kind: "list"; readonly label: "allowUsers"; }, { readonly key: "allowChats"; readonly kind: "list"; readonly label: "allowChats"; }]; readonly dingtalk: readonly [{ readonly key: "language"; readonly kind: "select"; readonly options: ["zh", "en"]; readonly label: "language"; }, { readonly key: "stream.url"; readonly kind: "text"; readonly label: "stream.url"; }, { readonly key: "stream.requireMention"; readonly kind: "boolean"; readonly label: "stream.requireMention"; }, { readonly key: "allowUsers"; readonly kind: "list"; readonly label: "allowUsers"; }, { readonly key: "allowChats"; readonly kind: "list"; readonly label: "allowChats"; }]; readonly web: readonly [{ readonly key: "pollIntervalMs"; readonly kind: "number"; readonly label: "pollIntervalMs"; }, { readonly key: "allowUsers"; readonly kind: "list"; readonly label: "allowUsers"; }, { readonly key: "allowChats"; readonly kind: "list"; readonly label: "allowChats"; }]; }; /** * Declared channel config keys the pane must carry through *without* editing — * dotted paths, read and written verbatim. * * These exist because a save is a whole-section replace: anything the host sent * that the pane does not put back is gone from the profile. That is the bug this * whole table exists to make impossible, and it applies just as much to a key * with no control as to one with a control. The completeness test in * `test/channel-config-coverage.test.mjs` proves every declared `Config` key is * a field, a secret, or listed here. */ export declare const CHANNEL_PRESERVED_KEYS: Record; /** Channel-agnostic keys applied to every channel that doesn't set its own. */ export declare const CHANNEL_DEFAULT_FIELDS: readonly ConfigField[]; /** * The plugin-wide defaults the pane edits outside any channel (通用设置) — the * same values the chat commands set. Grouped for rendering; `GENERAL_FIELDS` is * the flat view used by the projection and form code, which don't group. * * Every one of these is resolved once when the plugin starts and copied into * each runner as it is created, so a pane edit reaches the profile but is only * picked up by a **restart**. The pane says so on every row rather than * implying an immediacy it cannot deliver. `workDir`, `language` and * `autoMirror` can additionally be overridden by `dsh.shared.config.json`; the * pane notes that too, but only where it actually applies. */ export declare const GENERAL_FIELD_GROUPS: readonly { title: string; fields: readonly ConfigField[]; }[]; /** Flat view of the general settings fields, for projection and form code. */ export declare const GENERAL_FIELDS: readonly ConfigField[]; /** * Every path the pane owns for `channel`: its editable fields plus the keys it * carries through untouched. * * The save payload is built from exactly this list, which is what makes the * pane's surface and the persisted surface the same set — and what makes a * `Config` key that is in neither list a *test* failure (see the completeness * test) rather than a silent deletion at the next save. */ export declare function channelConfigPaths(channel: ChannelName): string[]; /** Normalize a raw form input for a field `kind` (empty → undefined = not saved). */ export declare function coerceConfigValue(kind: ConfigField['kind'], raw: unknown): unknown; /** The edit form the pane works on. */ export interface SettingsForm { channels: ChannelName[]; channelDefaults: Record; /** * Per-channel field values, keyed by `ConfigField.key` — which is the field's * path in the channel config, so a nested field is stored flat here under its * dotted key (`'stream.url'`) and only translated at the snapshot/save edges. * Values are always copies of what the host sent; the pane mutates this object * in place, and aliasing the snapshot would edit the host's own data. */ channelConfigs: Record>; /** Secret inputs the user is *typing*: written on save, never seeded by a read. */ secrets: Record>; /** * Whether a value is already stored for a secret key, for the placeholder. * Separate from `secrets` because the two have opposite lifetimes: presence * comes from the snapshot and the typed values start empty and are cleared * after a save — a read must never be able to populate an input. */ secretPresence: Record>; /** * Host-masked previews of the stored secrets, so the user can confirm what * they configured. Rendered as read-only text and **never** fed into * `secrets`: a masked value is not a credential, and treating one as a value * to save would overwrite a working secret with its own mask. */ secretPreviews: Record>; /** * Values for the general (plugin-wide) keys, keyed per `GENERAL_FIELDS`. * Flat rather than grouped — the grouping is a rendering concern. These are * written to the *top level* of the section, beside `channels`, because they * are the same keys the chat commands set (`/notice`, `/model`, …), not * per-channel options. */ general: Record; /** * General keys whose current value actually comes from `dsh.shared.config.json` * and therefore shadows whatever the profile holds. The pane prints a * provenance note on those rows and only those. * * A list rather than a boolean, and carried on the form rather than read at * render time: the pane already holds exactly one snapshot-derived object, and * a separate "just the snapshot, for this one flag" slot would be the ninth * hook — one more than the client bundle's stub is set up to feed. Empty on a * clean install, where there is no shared config, so the note simply never * appears. */ sharedOverrideKeys: string[]; /** * The DSH-owned default model, carried for a read-only display row. It is * *not* part of this form's config and must never reach a save payload: it * belongs to DSH, and the pane deliberately has no write path for it (see * the note on `renderModelRow` in the client). Absent when the host could * not read a selection, in which case the pane omits the row rather than * rendering an empty control. */ agentModel?: { provider: string; model: string; }; /** * Per-channel access state, as composed by the host (`channelStatus` in the * snapshot). Purely a display value: it is never part of a save payload, and * the pane refreshes it on a timer while the channels view is open. * * Absent when the host does not provide one — which the pane renders by simply * not drawing a status badge, rather than by inventing a 「未知」 state. This * is why it is attached conditionally: an `undefined` key and a missing key * would otherwise both survive a round trip and blur that distinction. */ channelStatus?: Record; settingsStatePath?: string; /** True when the section lives in the settings namespace: a save is immediate and durable. */ live: boolean; } /** Build the initial form from a snapshot. */ export declare function snapshotToForm(snapshot: SettingsSnapshot): SettingsForm; /** * Build the non-secret config to persist via `settings.save`. * * Sent as the *whole* visible config, and that is deliberate — see the note on * `SettingsForms.replace` in `settings/namespace.ts`. A payload that tried to * send only what changed would not shrink the profile entry by a single key * (`replace` merges the caller's section over the *inherited* one, so every * volatile field is written to the profile either way) and *would* quietly * revert anything the user had saved earlier: a field this save omits resolves * to its inherited value, not to its previously saved one. */ export declare function buildConfigSave(form: SettingsForm): Record; /** Build the `credentials.save` payloads for every channel with secret values. */ export declare function buildCredentialSaves(form: SettingsForm): { channel: string; values: Record; }[]; //# sourceMappingURL=settings-model.d.ts.map