diff --git a/Configuration.md b/Configuration.md new file mode 100644 index 0000000..d49bee1 --- /dev/null +++ b/Configuration.md @@ -0,0 +1,163 @@ +# Configuration + +`Config` is a Schemastery schema plus a same-named interface. It is validated when the plugin loads; invalid config fails the load with an actionable error. A config edit hot-replaces the plugin. + +## Full schema + +```ts +import Schema from '@deepseek-ai/schemastery' + +export interface Config { + accessToken: string + userAccessToken?: string + groupId: number + apiVersion: string + language: 'ru' | 'en' | 'uk' + rateLimit: { rps: number; burst: number; maxConcurrent: number } + retry: { maxAttempts: number; baseDelayMs: number; maxDelayMs: number; retryOn: number[] } + captcha: { policy: 'fail' | 'ask'; solverTool?: string } + longpoll: { enabled: boolean; mode: 0 | 1 | 2; timeoutSec: number; batchMs: number } + callback: { enabled: boolean; serverUrl?: string; confirmToken?: string; secret?: string } + moderation: { enabled: boolean; autoApprove: boolean; warnThreshold: number; exemptUserIds: number[] } + safety: { requireConfirmForDestructive: boolean; dryRunDefault: boolean; rawApiEnabled: boolean } + notifications: { onMessage: boolean; onComment: boolean; onJoin: boolean; onLeave: boolean } + ui: { settingsTab: boolean; moderationQueue: boolean } +} +``` + +## Field reference + +### `accessToken` (required) + +The **community** token. Never logged, never placed in tool arguments, never returned by any tool. Read once in `apply`, held in a closure, redacted in the audit log as ``. + +### `userAccessToken` (optional) + +The admin's **user** token. Unlocks `vk_members_list`, `vk_stats_get`, and the complete `vk_ban_list`. Without it those tools return `VK_SCOPE_MISSING` naming the exact scope. + +### `groupId` + +The community id. Accepts either sign; normalized internally to a negative number for `owner_id` purposes. + +### `apiVersion` + +Default `'5.199'`. Sent on every request. Do not leave it unset — VK's behaviour without `v` is unpredictable. + +### `language` + +`'ru' | 'en' | 'uk'`. Passed as `lang` on requests that accept it and used for VK-generated error text. + +### `rateLimit` + +| Field | Default | Meaning | +|---|---|---| +| `rps` | 3 | sustained requests per second for the community token | +| `burst` | 5 | token-bucket depth | +| `maxConcurrent` | 2 | in-flight request cap | + +Raise `rps` only if you use a user token (limit ~20/s). Exceeding it yields `error 6`. + +### `retry` + +| Field | Default | Meaning | +|---|---|---| +| `maxAttempts` | 4 | total attempts including the first | +| `baseDelayMs` | 400 | first backoff delay | +| `maxDelayMs` | 8000 | backoff ceiling | +| `retryOn` | `[1, 6, 9, 10]` | VK error codes treated as transient | + +Everything not in `retryOn` fails fast. `5` (auth), `15` (access), `100` (bad param) must never be retried. + +### `captcha` + +`policy: 'fail'` (default) — return `VK_CAPTCHA_REQUIRED` with the `captcha_sid` and stop. `policy: 'ask'` — the pre-execute gate returns `ask` so a human can solve it and the call resumes with `captcha_key`. + +`solverTool` names an external tool that can solve the captcha; it is **not** built in. Automated captcha solving is out of scope (see Non-Goals). + +### `longpoll` + +| Field | Default | Meaning | +|---|---|---| +| `enabled` | false | start the background job on load | +| `mode` | 2 | 0 = no typing/online, 1 = messages only, 2 = messages + typing | +| `timeoutSec` | 25 | `wait` parameter | +| `batchMs` | 1500 | window over which events are coalesced into one agent message | + +### `callback` + +Off by default — needs a public HTTPS endpoint. When enabled, `serverUrl`, `confirmToken` and `secret` are required (validated by the schema). + +### `moderation` + +| Field | Default | Meaning | +|---|---|---| +| `enabled` | false | run the rule engine | +| `autoApprove` | false | auto-approve join requests (dangerous; opt-in only) | +| `warnThreshold` | 3 | warnings before an automatic ban | +| `exemptUserIds` | `[]` | users the engine never touches | + +### `safety` + +| Field | Default | Meaning | +|---|---|---| +| `requireConfirmForDestructive` | true | destructive tools return `ask` from pre-execute | +| `dryRunDefault` | true | `vk_community_update` defaults to `dryRun: true` | +| `rawApiEnabled` | false | enables `vk_api_raw` | + +### `notifications` + +Per-event-type switches for the LongPoll → agent bridge. `onJoin`/`onLeave` are off by default because a busy community generates a lot of noise. + +### `ui` + +`settingsTab` adds a settings panel; `moderationQueue` adds the moderation queue view. Both require the client half of the plugin to be installed in a Web profile. + +## Example: production profile + +```yaml +- insert: + - id: vk-group + name: dsh-vk-group + config: + groupId: -123456789 + accessToken: !!js process.env.VK_COMMUNITY_TOKEN + userAccessToken: !!js process.env.VK_USER_TOKEN + apiVersion: '5.199' + language: ru + rateLimit: { rps: 3, burst: 5, maxConcurrent: 2 } + longpoll: { enabled: true, mode: 2, timeoutSec: 25, batchMs: 1500 } + moderation: { enabled: true, autoApprove: false, warnThreshold: 3, exemptUserIds: [1] } + safety: { requireConfirmForDestructive: true, dryRunDefault: true, rawApiEnabled: false } + notifications: { onMessage: true, onComment: true, onJoin: false, onLeave: false } +``` + +## Example: read-only audit profile + +```yaml +- insert: + - id: vk-group + name: dsh-vk-group + config: + groupId: -123456789 + accessToken: !!js process.env.VK_COMMUNITY_TOKEN + longpoll: { enabled: false } + safety: { requireConfirmForDestructive: true, dryRunDefault: true, rawApiEnabled: false } + ui: { settingsTab: false, moderationQueue: false } +``` + +## Secrets + +Tokens come from the environment, never from the patch file. Use `!!js process.env.X` — DSH evaluates the expression against the injected context after the row's dependencies are ready. + +Never commit a `.env` with real tokens. The audit log redacts tokens as ``; if you add a new log line, redact there too. + +## Validation failures + +| Symptom | Cause | +|---|---| +| `accessToken: required` | missing token | +| `groupId: expected number` | id passed as a string | +| `callback.serverUrl: required` | `callback.enabled: true` without a URL | +| `rateLimit.rps: must be > 0` | zero or negative | + +Failures are loud by design — the plugin refuses to load rather than degrading silently. \ No newline at end of file