Live testing showed the voice-to-text text is delivered by an async push
(Event 0x210 subType 61), not the request response — so the previous
poll-by-resend never got it. Captured a real push and decoded its protobuf:
outer { f1, f2:item }; the item echoes the request msgId at field 1 (the
correlation key) and the recognised text at field 8.
- proto-defs: PttTransPush / PttTransPushItem (live-verified).
- protocol: Event0x210SubType.PttTransResult = 61 + decoder → internal
`ptt_trans_result` event { msgId, text } (replaces the temp hex dump).
- core: translatePttToText now sends ONE trigger and returns the inline text
only if present (already-transcribed); empty = pending.
- onebot: ptt-trans-waiter (keyed by selfUin:msgId), event-pipeline subscribes
`ptt_trans_result` to resolve it; fetch_ptt_text registers the waiter,
fires the trigger, and awaits the push (20s timeout).
Decoded one real push as text "12311123。" end to end.
Live testing showed the voice-to-text text is delivered via an async push
Event 0x210 subType 61 (0x3D), not the sync pttTrans.TransC2CPttReq response
(which is a fixed ~137B ack). That push has no static sys_msg_0x210_0x3d
handler — its protobuf is registered dynamically by the ptt-trans subsystem,
so RE through the std::function callback chain is impractical.
Dump the raw subType-61 body (fromUin + len + hex) once so the wire format
can be decoded from ground truth, then implement the real decoder +
request/await correlation. Temporary — to be replaced by the actual decoder.
Debugging from user-supplied logs was impossible: a successful action left
one terse, over-summarized debug line and nothing about what it sent QQ or
what QQ returned. Add full-chain tracing that stays off disk so it scales to
hundreds of groups.
common:
- New `trace` level (below debug). Never written to file — it lives only in
an in-memory ring + the WebUI stream, so the high-volume full chain costs no
disk. `emit` early-returns for trace before format/render (and before any
lazy producer) when trace isn't active → ~free at the default level.
- Dedicated O(1) circular ring for trace (default 5000, SNOWLUMA_TRACE_BUFFER),
separate from the normal 1000-entry buffer (also switched off array .shift);
getRecentLogs merges both by id.
- request-context.ts: AsyncLocalStorage request id. The logger auto-stamps
`[req#N]` on every line in a request's async chain — across packages, zero
signature threading — and records it on the entry.
- log-summary.renderParamsVerbose: deep render of params (nested structure,
per-string cap, total budget, token/password/secret redaction).
chokepoints (auto-cover every action without touching 100+ handlers):
- onebot api-handler: wrap each action in a request id (only when trace is
live); trace the full params on entry and status+elapsed on exit; the terse
debug breadcrumb still persists to file, failures still warn.
- core bridge.sendRawPacket: trace on success (cmd, sizes, elapsed); warn on
errorCode!=0 / no response (cmd, code, message) so disk keeps real problems.
- protocol highway: fold the bare console.log lines into the logger (trace).
webui: trace as the lowest filterable level; render the `[req#N]` tag and let
search match it. Server needs no change — LOG_LEVELS, getRecentLogs and the
SSE stream carry it through.
set_group_whole_ban false (unmute) failed with `OIDB error 1007` while
enabling worked (issue #70). OIDB (0x89A, 0) is shared by several commands
(SetSearch / SetAddOption / mute-all) disambiguated by body shape. Unmute
sends muteState.state=0, but proton omits zero scalars (proto3), leaving an
empty `muteState` the server can't classify. Enable sends 0xFFFFFFFF, which
is non-zero and survives — hence the asymmetry.
Add a `pb_optional<N, T>` marker (proto3 explicit presence): a scalar so
marked serialises even at its zero/default value. It is opt-in per field via
a NEW `explicitPresence` flag set ONLY by this marker — never reused from the
TS-`?`-derived `isOptional` (true for nearly every field). The encoder routes
explicit-presence scalars to a dedicated block; every `pb<>` / `pb_repeated<>`
field takes the untouched branches, so existing wire output is byte-identical
(proton's 99 existing tests, incl. byte-level round-trips, stay green).
Mark OidbMuteAllState.state `pb_optional<17, uint_32>` so unmute now puts
field 17 (=0) on the wire. New tests: proton pb_optional encode/round-trip
(incl. that sibling pb<> fields still omit zero), and a byte-level assertion
that mute-all unmute carries the state field.
Note: proton ships as a built Vite plugin (dist, gitignored); rebuild it
(`pnpm --filter @snowluma/proton build`) for the change to reach consumers.
loadBinarySource issued a header-less fetch(source) for remote media.
Anti-hotlink / anti-bot front-ends reset such requests, surfacing as
undici `TypeError: fetch failed` (not a clean 403) — so quoting a
command and sending an image/GIF whose source is a guarded URL bounced
with send_group_msg retcode 1200 "fetch failed".
Mirror NapCat's download path: send a browser User-Agent on every
request and retry once with a Referer pointing at the resource when the
first attempt is refused (network error or 403/4xx). Size-limit
rejections are tagged noRetry so an oversized body isn't re-downloaded.
Also route set_group_notice's image fetch through loadBinarySource so it
inherits the same hardening (plus http/file/base64 handling).
Remove the in-tree @snowluma/sqlite wrapper and vendored better-sqlite3 prebuilts; migrate consumers to node:sqlite's synchronous API. Replaced imports and types (Database/Statement -> DatabaseSync/StatementSync), updated PRAGMA calls to use exec, and adjusted code in onebot (media/message/reaction stores) and protocol (identity service). Vite config no longer bundles sqlite native binaries and references to the sqlite workspace were removed. Also delete the packages/sqlite package and its native prebuild artifacts, and add a couple of unrelated AST improvements in proton: use a module-level shared wrapper cache and add an inheritance cache to avoid repeated scans. Minor import reordering/cleanup applied where needed.
Reorder and normalize imports, add missing type imports, and fix whitespace/formatting across multiple files. Notable updates: eslint.config.ts import ordering; core bridge APIs (group-album, misc, web) now import Json types; tests/highway pipeline import ordering and added type imports; protocol changes (element-builder, highway client/pipeline) reorganized imports and added proto type imports; many oidb service modules moved protobuf encode/decode imports for consistency; web group modules (album, essence, honor, notice, request-util) reordered imports, adjusted typings/indentation, and small whitespace fixes. These edits are stylistic and typing improvements and should not change runtime behavior.
Enable @typescript-eslint/no-explicit-any as an error in the ESLint config to tighten typing across the codebase, and add an override to disable this rule for test files. Also apply non-functional cleanup in packages/proton/src/runtime.ts: reorder/imports and adjust formatting/indentation and string alignment for readability — no behavioral changes.
The package previously named "bridge" houses the protocol-byte layer
(OIDB cmds, msg-push decoders, highway uploaders, element-builder,
event bus). The name "bridge" suggested transport/lifecycle, which it
isn't — that role lives in core/hook and core/bridge/manager. Rename
to match what's actually inside.
This is a pure rename: zero logic changes, all 73 import sites updated
mechanically and TS-verified. core/onebot dependencies key updated.
ESLint override path adjusted for the moved oidb-services directory.
Rename detection threshold: git log --find-renames=20% recommended
when following history across this commit — content-replacement
(@snowluma/bridge → @snowluma/protocol) dropped per-file similarity
below git's default 50% on import-heavy files.