Make persisted log filtering predictable across supported startup settings while rejecting configurations that could silently retain more data than intended.
Keep the first WebUI login credential available without weakening normal log thresholds, clarify the operator-facing behavior, and strengthen regression coverage across isolated environments and explicit file-output disablement.
Enforce a global log quota with configurable retention and observable degradation. Add authenticated storage reporting and safe cleanup controls to the settings UI. Protect active stream transfers and isolate parallel test resources.
Phase 1 of the 4-package split called out in RoadMap #3. Moves the
"infrastructure" leaves — files with zero business dependencies that
@snowluma/bridge, @snowluma/core, and @snowluma/onebot all consume —
into a dedicated workspace package. Mirrors what #4 (proto-defs)
did for wire-protobuf definitions.
Why
---
- Cleaner layering: these were never bridge- or onebot-specific.
Logger / hex codec / runtime config sit at the bottom of every
module's import graph; giving them their own package makes that
graph readable and prevents future leaks the other direction.
- Sets up #3's bridge + onebot extractions: those need a clean leaf
to depend on. Without this, every cross-package import would have
to re-export through core, which defeats the split.
- Mirrors @snowluma/proto-defs layout — per-file subpath exports
(`@snowluma/common/logger`, `@snowluma/common/hex`, …) keep import
sites unambiguous and let bundlers tree-shake aggressively.
Layout
------
Moved (via `git mv` — history preserved):
packages/core/src/utils/logger.ts → @snowluma/common/logger
packages/core/src/utils/hex.ts → @snowluma/common/hex
packages/core/src/utils/log-file-transport.ts → @snowluma/common/log-file-transport
packages/core/src/utils/log-summary.ts → @snowluma/common/log-summary
packages/core/src/protocol/packet-sender.ts → @snowluma/common/packet-sender
packages/core/src/protocol/types.ts → @snowluma/common/protocol-types
packages/core/src/common/runtime.ts → @snowluma/common/runtime
Not moved (deferred to Phase 2): `packages/core/src/utils/event-format.ts`
— it has reverse imports on `bridge/events`, `bridge/identity-service`,
`onebot/message-store`, `onebot/types`. It moves into @snowluma/bridge
in Phase 2, with the MessageStore parameter decoupled via a structural
type so bridge doesn't end up depending on onebot.
Changes
-------
- New package `packages/common/` (package.json, tsconfig.json — no
src/index.ts barrel, callers use the subpath exports directly to
avoid pulling in `node:fs` when only the hex codec is needed).
- 7 `.ts` files moved via `git mv`.
- 50+ import sites rewritten via sed across `packages/core/src` and
`packages/core/tests`. Old shapes (`../utils/logger`,
`../../protocol/types`, `./common/runtime`, `../src/utils/hex`)
all collapse to `@snowluma/common/<sub>`.
- `@snowluma/core` depends on `@snowluma/common` (workspace:*).
- `packages/core/src/{protocol,common}/` directories removed (empty).
Verification
------------
- core typecheck: clean
- common typecheck: clean
- Full workspace `pnpm -r typecheck`: all 9 projects clean
- core test suite: 552 passed / 552 total (pre-existing
websocket-darwin-arm64.node addon failure unchanged — same baseline
as the previous refactor commits)
- Vite build with stubbed native binary: clean (143 modules, 2.84 MB
bundle, 224 KB gzip — same size envelope as before)
Note: `utils/event-format.ts` is the only file remaining under the
old `utils/` namespace. It moves into @snowluma/bridge alongside the
events / identity-service / packet-pipeline trio in Phase 2.
The console / subscriber level was captured at module load
(`const MIN_LEVEL = resolveMinLevel()`), so any change to
SNOWLUMA_LOG_LEVEL after Node started had no effect — restart was
the only way to flip levels, which makes "go to debug for 30s to
see what's happening" pretty painful.
logger.ts now keeps `currentLevel` mutable and exports
setLogLevel() / getLogLevel(). The env var still seeds it on
startup; nothing about the boot-time behavior changes. File
transport remains unaffected — it always sees debug-and-up.
WebUI gets two new endpoints, both behind the existing auth
middleware:
GET /api/logs/level → { level, levels }
POST /api/logs/level body → { level: 'debug' } updates it
Logs page picks up a "服务端级别" row of pills next to the
existing client-side filter pills, so an operator can flip the
console firehose on/off in one click without touching env vars
or restarting the bot. The label clarifies that file logging
stays at debug regardless.
Logger now supports deriving a UIN-bound child:
const log = createLogger('Bridge');
this.log = log.child({ uin: this.uin });
The child stamps a fixed-width [UIN] slot into rendered lines so the
[Scope] column stays vertically aligned, and routes the same line to
a per-UIN sub-file:
logs/snowluma-YYYY-MM-DD.log (shared, all UINs)
logs/<uin>/snowluma-YYYY-MM-DD.log (per-account)
Both pathways share daily rollover, size cap, and retention.
LogEntry now carries uin so SSE subscribers (WebUI) can filter later.
Other meta keys passed to child() are preserved in opts.meta for
forward compat (requestId / traceId etc.) but not rendered yet.
Module-level callers are unchanged; no callsites migrated in this PR.
SNOWLUMA_LOG_PER_UIN=0 disables the per-account sub-file while
keeping the shared one.