SNOWLUMA_ variables can set the factory OneBot listen ports, WebUI TLS,
packet and hook timeouts, the stream directory, file download timeout,
and notification HTTP timeout. The values apply in memory and are not
written back to saved config.
Use safer defaults for newly created service endpoints and guide operators toward suitable access credentials without disrupting existing deployments.
Apply the same policy at the save boundary, retain deliberate local-only setups and external client compatibility, and add regression coverage for editing, recovery, and trusted access paths.
Add an opt-in per-account login history backfill that waits for roster warmup and a 30-second grace period before silently filling local message history. The setting defaults to disabled and applies on the next account lifecycle.
Run one account at a time, prioritize known local gaps, and process at most three groups plus three friends with one 20-message page per session. Failures are logged and never retried during the same login.
Persist reconstructed history without dispatching OneBot events or changing QQ read state. Keep private metadata and event bodies atomic, honor recall tombstones, suppress live-only history side effects, avoid extra media requests, and stop queued, in-flight, or converting work when the account closes.
Share the persistence path with manual history actions and add lifecycle, cancellation, corruption, partial-failure, peer-identity, and storage-atomicity regression coverage. Refs #280.
Persist user saves as authoritative per-account snapshots while retaining legacy overlay compatibility. Validate deterministic topology errors before writing and replace configuration files atomically.
musicSignUrl is a deployment-wide signing-service endpoint (same for every
account), so it belongs in the global config/snowluma.json next to rkey, not in
per-UIN onebot_<uin>.json.
- GlobalSettings gains musicSignUrl (http(s)-validated, '' = built-in default);
consumption switches to globalSettings.musicSignUrl and hot-reloads via
applyGlobalSettings.
- One-shot copy-up at boot (migrateGlobalSettings, called first in main()):
lifts the first VALID legacy value from any config/onebot*.json into the
global store. Idempotent via a key-PRESENCE guard, so an intentional
clear-to-'' is never reverted by a stale legacy value.
- Remove musicSignUrl from per-UIN OneBotConfig (type + parse/serialize).
Phase 2 of the global-settings split. (WebUI form follows in Phase 3.)
The rkey fallback endpoint is deployment infra (one self-hosted server serves
all accounts; fallback rkeys are fungible), so it doesn't belong in per-UIN
onebot_<uin>.json. Introduce config/snowluma.json — a generic home for small
all-accounts SnowLuma settings — and move rkey.fallbackServers there.
- New onebot/global-config.ts: load/save/normalize with section-merge + atomic
write; http(s)+host URL validation; default [] (off). Lives in onebot (not
core) because the onebot manager constructs instances and must not depend on
core.
- OneBotInstance takes globalSettings; manager loads it on session start and
exposes reloadGlobalSettings() to hot-push edits to every live instance via
applyGlobalSettings → RKeyCache.setFallbackServers.
- Remove rkey from per-UIN OneBotConfig + the WebUI per-UIN round-trip (never
released, so no migration). Register snowluma.json in the backup allowlist.
Reworks the per-UIN rkey from 12b0190. Phase 1 of the global-settings split.
On accounts where the native OIDB 0x9067_202 rkey fetch persistently returns
nothing, every image was served as a bare multimedia.nt.qq.com.cn URL the CDN
rejects with invalid rkey — SnowLuma had no fallback, unlike NapCat.
Add config.rkey.fallbackServers (default []: OFF; no third-party server is
contacted unless the operator configures one). When the native fetch leaves the
cache without a usable image rkey, each configured HTTP endpoint is tried in
turn for { group_rkey, private_rkey, expired_time } (NapCat rkey-server format;
an OneBot { retcode, data } wrapper is also accepted), populating the group/
private image rkey types. Values keep any leading &rkey=/?rkey= since
resolveImageUrl strips before re-appending.
WebUI has no form yet but round-trips rkey.fallbackServers verbatim so a hand-
edited value survives an unrelated config save. Part B of #156 (cooldown is A).
* feat: make #sl status command trigger configurable (exact match, max 32 char)
The trigger word for the built-in #sl status command is now configurable
via statusCommand.trigger, with exact case-insensitive matching preserved.
* chore: reject control chars in trigger + add sync assertion
Global channel store at config/notifications.json, mirroring ui-config's
total-normalize + atomic-write + cached-load conventions: NotificationsConfig
(version, debounceSeconds clamp [0,3600] default 30, channels[]), a total
normalizeNotificationsConfig (drops malformed/non-http channels, dedupes by
id, strips unknown fields), and the pure renderTemplate ({key} substitution;
missing keys left as-is; no re-expansion of substituted values).
Per-UIN opt-in: OneBotConfig.notifications.channelIds (validated slugs,
last-write-wins merge across global+per-uin, deduped) in the onebot package.
Tests: 19 (core notifications-config) + 12 (onebot config), all green.
Adversarial review: 0 blockers / 0 should-fix.
A hardcoded `#sl` status command — deliberately NOT a plugin system.
Matched at the instance dispatch choke point: a pure-text message equal
to `#sl` (exact, trimmed, case-insensitive — no startsWith) replies with
SnowLuma version / platform / uptime in the same conversation.
- Anyone can trigger; the per-UIN `statusCommand.enabled` toggle is the
only gate (default on). Per-conversation cooldown (default 5s,
configurable) prevents flood/风控 from repeated triggers.
- `swallow` (default false / pass-through): when set, a matched `#sl` is
not forwarded to downstream adapters — still cached, logged, replied.
- Config on OneBotConfig.statusCommand (per-UIN): defaulted + clamped in
config.ts, mirrored/normalized in the WebUI; edits hot-reload via the
shared instance context.
- WebUI: a "内置命令" section in the 通用设置 tab (enable / swallow /
cooldown). Node enable toggle extracted into a shared ui/ToggleSwitch.
Pure matcher / cooldown / uptime / text helpers live in
modules/status-command with unit tests; config defaults + clamp covered.
Phase 3 of the 4-package split (RoadMap #3). Moves the entire
`packages/core/src/onebot/` subtree (43 source files + 20 test files
spanning network adapters, action handlers, event converters, message
parser, message store, media store, instance + manager) into the new
`@snowluma/onebot` workspace package. The remaining `@snowluma/core`
now hosts only the api-layer (Bridge class + 13 Api classes), the
hook tooling, the WebUI server, and the runnable `index.ts`.
Why
---
- The OneBot adapter is the public-facing OneBot11 protocol surface;
it has its own version cadence and its own consumers (third-party
bots talking OneBot WS / HTTP). Splitting it lets that cadence move
independently of the Api-on-ctx layer.
- The two were sharing the `packages/core/` build pipeline only
because they happened to live in the same directory. Nothing in
the api-layer reaches into OneBot internals.
- Mirrors NapCat / Lagrange.OneBot's repo split (their OneBot adapter
is a separate assembly from the Core protocol library).
Layout
------
Moved via `git mv` (history preserved):
packages/core/src/onebot/* → @snowluma/onebot/src/<x>
packages/core/tests/onebot/* (6 files) → @snowluma/onebot/tests/
packages/core/tests/{message-parser,message-store,event-pipeline,
media-store,media-url-resolver,event-converter,
http-server-hot-reload,http-server-content-type,
api-handler-logging,message-id,group-msg-emoji-like,
config,media-indexer,network-manager}.test.ts (14 files)
→ @snowluma/onebot/tests/
What stays in @snowluma/core
----------------------------
- `bridge/{bridge,bridge-interface,bridge-context,manager}.ts`
- `bridge/apis/*` (the 13 typed Api classes)
- `hook/*` (debugger/injector — tooling for the core protocol layer)
- `webui/*` (admin UI server — application wiring, not the OneBot
adapter itself)
- `index.ts` (application entry that wires BridgeManager +
OneBotManager + HookManager + WebUI together)
Changes
-------
- New package `packages/onebot/` (package.json with subpath exports
mirroring legacy `core/src/onebot/<x>` 1:1, tsconfig.json,
vitest.config.ts with `SNOWLUMA_LOG_LEVEL=debug` env so the action-
dispatch logging tests see debug-level subscribers — copied verbatim
from core's vitest.config.ts).
- @snowluma/core grows new subpath exports for the api-layer files
OneBot's type-only imports need: `./bridge`, `./bridge-interface`,
`./bridge-context`, `./manager`, `./apis`.
- `@snowluma/onebot` depends on `@snowluma/bridge`, `@snowluma/common`,
`@snowluma/core`, `@snowluma/proto-defs`, `@snowluma/proton`,
`@snowluma/websocket`.
- `@snowluma/core` depends on `@snowluma/onebot` (for index.ts +
webui/server.ts wiring).
- 100+ import sites rewritten via sed:
* OneBot → core: `'../bridge/bridge'` → `'@snowluma/core/bridge'`,
`'../bridge/manager'` → `'@snowluma/core/manager'`,
`'../bridge/bridge-interface'` → `'@snowluma/core/bridge-interface'`,
`'@/bridge/bridge'` (vite path alias) → `'@snowluma/core/bridge'`.
* Core → OneBot: `'./onebot/manager'` → `'@snowluma/onebot/manager'`,
`'../onebot/config'` → `'@snowluma/onebot/config'`, etc.
* Test relative paths: `'../../src/onebot/<x>'` → `'../src/<x>'`
after the test file move (one level shallower).
- New `packages/onebot/src/env.d.ts` declaring `__APP_VERSION__` and
`__BUILD_WEBUI__` (the vite-injected globals `info.ts` reads).
Workspace cycle
---------------
core ↔ onebot is allowed by pnpm workspaces. The runtime direction
is one-way: core → onebot (index.ts constructs OneBotManager,
webui/server reads OneBot config). OneBot only reaches into core for
TYPES (Bridge, BridgeInterface, BridgeManager, ClientKeyInfo,
DownloadRKeyInfo); those imports use `import type` so they compile
out at runtime and don't introduce a real cycle.
Verification
------------
- Full workspace `pnpm -r typecheck`: all 11 projects clean.
- `@snowluma/core` test suite: 302/302 pass (33 test files).
- `@snowluma/onebot` test suite: 250/250 pass (20 test files;
pre-existing `network-manager.test.ts` native-addon failure
unchanged — same baseline as before the split).
- Combined: 552/552 — identical pass count to the pre-Phase-3
baseline.
- Vite build with stubbed native binary: clean, 2.84 MB bundle /
224 KB gzip (identical envelope).