dev2
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c16064d7e3 |
refactor(packages): rename @snowluma/bridge → @snowluma/protocol
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. |
||
|
|
f7a03ca05a |
refactor(bridge): extract protocol layer into @snowluma/bridge package
Phase 2 of the 4-package split (RoadMap #3). Moves ~35 files — events / event-bus / identity-service / packet-pipeline / element- builder / bridge-oidb / qq-info / msg-push/* / highway/* / web/* + the bridge-side formatters — out of `packages/core/src/bridge/` into the new `@snowluma/bridge` workspace package. Everything that translates QQ wire bytes ↔ typed events/elements now lives there. Why --- - `packages/core/src/bridge/` was actually two layers: a low-level "protocol decoder + I/O" set (msg-push, highway, packet-pipeline, identity, element-builder) AND the Api-class host (bridge.ts + apis/*). The Api layer can't move without the protocol layer moving first, but the two had grown into one directory. - Bridge-internal consumers (highway uploaders, OIDB envelope helper, element-builder, msg-push parsers) only ever needed `bridge.identity` + `bridge.sendRawPacket` + the upload-metadata cache. Splitting them out lets that minimal surface live in the bridge package as `BridgeContext` while the api-layer's extended context (with `apis: ApiHub`) stays in @snowluma/core. What stays in @snowluma/core (apis layer): - bridge.ts ── the god-class that composes the protocol building blocks + mounts apis - bridge-interface.ts ── public façade for OneBot consumers - manager.ts ── per-uin BridgeManager hub - apis/* ── the 13 typed Api classes - bridge-context.ts (NEW) ── extends @snowluma/bridge's slim BridgeContext with `apis: ApiHub` so the 13 Api classes keep their typed `this.ctx.apis.X.method()` access. Layout ------ Moved via `git mv` (history preserved): events / event-bus / identity-service / packet-pipeline / element-builder / bridge-oidb / qq-info / msg-push/* / highway/* / web/* → @snowluma/bridge/<sub> utils/event-format.ts → @snowluma/bridge/format.ts (the last one renamed because the destination directory only has bridge-related files; `format.ts` reads cleaner than `event-format`. Listed as a rename in git's `RM` output — content unchanged.) Layering fixes (functionality preserved, just type signatures) -------------------------------------------------------------- - `element-builder.SendContext.bridge` → typed as `BridgeContext` instead of `Bridge`. Reads only `bridge.identity` and the upload-cache (both on BridgeContext); the concrete `Bridge` instance still passes via free upcast. - `bridge-oidb.runOidb` signature widened from `Bridge` to `BridgeContext` — same reason. - `highway/{highway-client,pipeline,image-upload,ptt-upload, video-upload}.ts` — same. - `format.ts::formatReply` parameter typed as a new local `ReplyEventLookup` structural type (`{ findEvent(id): JsonObject | null }`) instead of `MessageStore` from @snowluma/onebot. The OneBot `MessageStore` is a structural superset; consumers pass it through unchanged. - JSON value types moved to a new `@snowluma/common/json` subpath (consumed by format.ts) — `onebot/types.ts` re-exports them so external `import { JsonValue } from '<…>/onebot/types'` paths keep working. - `UploadedFileMeta` interface moved from `core/src/bridge/bridge.ts` into `@snowluma/bridge/bridge-context.ts` — element-builder + the forward-builder both read the upload cache through BridgeContext, so the type has to live in @snowluma/bridge. `bridge.ts` keeps its re-export for back-compat. Changes ------- - New package `packages/bridge/` (package.json with per-file subpath exports mirroring legacy `core/src/bridge/<x>` layout 1:1, tsconfig.json, typecheck script). - 35 `.ts` files moved via `git mv` (3 files counted by git as RM — rename + modify because Bridge → BridgeContext type swap). - 150+ import sites rewritten across `packages/core/src` and `packages/core/tests`: `'../bridge/<x>'`, `'../../bridge/<x>'`, `'../src/bridge/<x>'`, `'@/bridge/web/<x>'`, and vitest `vi.mock(...)` literals all collapse to `@snowluma/bridge/<x>`. - `@snowluma/core` depends on `@snowluma/bridge` (workspace:*). - `@snowluma/bridge` depends on `@snowluma/common`, `@snowluma/proto-defs`, `@snowluma/proton`. - `packages/core/src/utils/` directory removed (empty). Verification ------------ - Full workspace `pnpm -r typecheck`: all 10 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, identical envelope to the pre-split build. Phase 3 (next): OneBot + WebUI move into @snowluma/onebot. The runnable `index.ts` stays in @snowluma/core as the application entry, importing `OneBotManager` from `@snowluma/onebot`. |
||
|
|
46b302f738 |
refactor(common): extract leaf utilities into @snowluma/common package
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. |
||
|
|
6c7e56d5a1 |
refactor(proto-defs): extract wire-protobuf definitions into standalone package
Move `packages/core/src/bridge/proto/proton/*.ts` (13 files, ~3.3k LOC) into a new `@snowluma/proto-defs` workspace package. The proton spike landed in the previous commit means bare-specifier imports (`@snowluma/proto-defs/oidb`, `…/message`, …) now resolve correctly through proton's vite plugin, so call-site replacement still fires. Why --- - Lets downstream consumers share the same wire definitions without copy-pasting them — multiple workspace packages can now depend on `@snowluma/proto-defs` directly. - Trims `@snowluma/core`'s surface: the bridge no longer owns the protocol schema, only its consumers. - Sets up future #5 (`message-parsing perf`) and #6 (`Api-on-ctx refactor`) — both want a thinner core. Layout ------ Per-file subpath exports (mirrors the legacy `proto/proton/<sub>` shape 1:1). The barrel `index.ts` intentionally skips `highway` and `oidb-actions/media` because the two files share six NTV2-prefixed type names — `export *` would silently drop one side. Subpath imports are unambiguous, which is also why every call site in core uses them. Changes ------- - New package `packages/proto-defs/` (package.json, tsconfig.json, src/index.ts). - 13 `.ts` files moved via `git mv` to preserve history. - 44 import sites across 26 source files + 15 tests rewritten via sed from `./[…/]proto/proton/<sub>` → `@snowluma/proto-defs/<sub>`. - `@snowluma/core` depends on `@snowluma/proto-defs` (workspace:*). Verification ------------ - proto-defs typecheck: clean - core typecheck: clean - core test suite: 587 passed (same baseline as before the refactor; one failure is the pre-existing websocket native addon, unrelated) - proton test suite: 99 passed (8 files including the new cross-package.test.ts from the spike) - Full workspace `pnpm -r typecheck`: all 8 projects clean Note: proton's `dist/index.js` MUST be rebuilt for the vite plugin to pick up the bare-specifier resolution. The previous commit's source change is only effective after `pnpm -F @snowluma/proton build`. |
||
|
|
5ea1c3a961 |
Remove bridge proto schemas and parity test
Delete proto schema modules under packages/core/src/bridge/proto (action.ts, element.ts, highway.ts, longmsg.ts, message.ts, notify.ts, oidb-action.ts, oidb.ts) and the related test packages/core/tests/proton-parity.test.ts. These files contained protobuf schema definitions for the bridge layer; remove them as part of a cleanup/refactor. Update any imports or references to these modules to their new locations or replacements. |
||
|
|
898898a507 |
feat(msg-push): handle three friend-related events we used to drop on the floor
Close three coverage gaps surfaced by the cross-Lagrange-family
audit (Lagrange V2 / LagrangeGo / acidify / lagrange-python):
- **0x210 subType 179 + 226 (NewFriend)** — mutual-accept friend
notice. 179 fires when bot sent a request and the other side
accepted; 226 fires for the opposite direction. Both decode
through the same wire shape. LagrangeGo handles the pair via a
`case 226: case 179:` fallthrough in `client/listener.go:248`.
We surface them as `kind: 'friend_add'`, which the existing
converter routes straight to OneBot `notice.friend_add` — bot
frameworks that wait for "auto-add a friend after they accept"
were previously stuck because nothing arrived.
- **0x210 subType 139 (self-recall)** — bot recalled its own
message sent to a friend. Same `FriendRecall` wire shape as 138
but the peer is on `toUid` instead of `fromUid` — acidify's
`parseFriendRecall:380` splits exactly this way. Without 139,
self-recalls disappeared and downstream `notice.friend_recall`
handlers thought the bot had never recalled anything.
- **PkgType 85 (bot self-joined a group)** — fires on the bot's
session once `set_group_add_request` is approved or an invite is
accepted, ported from lagrange-python's `server_push/msg.py:108`.
PkgType 33 (GroupMemberIncreaseNotice) only fires for *other*
members joining, so without 85 the bot never received its own
`notice.group_increase` until the first message arrived. We
decode `SelfJoinInGroup` and emit `kind: 'group_member_join'`
with `userUin = selfUin`; the existing converter picks
`sub_type: 'approve' | 'invite'` from the operator/user equality
so consumers see the standard OneBot shape.
While wiring 139, also fix a *pre-existing* prod bug spotted via
proton call-site analysis on the resulting bundle: `event-0x210.ts`
imported `FriendRecall` from both `proto/proton/notify` (as
`FriendRecallNotify`) *and* `events` (as the event interface).
Proton's import-resolver registers `importedTypeSources` keyed on
the *imported* name (last-write-wins), so the events.ts import
shadowed the proto one — `protobuf_decode<FriendRecall>(...)` lost
its codec, fell through to the runtime fallback throw, and was
silently swallowed by `MsgPushRegistry.decode`'s catch. Verified by
the substituted codec list in `dist/index.mjs`: pre-fix it had
`FriendRequest` / `GeneralGrayTipInfo` but no `FriendRecall`;
post-fix `FriendRecall` / `NewFriend` / `SelfJoinInGroup` are all
emitted as expected. Side-step the collision by importing the
proto types unaliased and re-deriving the colliding event type via
`Extract<QQEventVariant, { kind: 'friend_recall' }>`.
New proto interfaces in `proto/proton/notify.ts`:
- `NewFriend` + `NewFriendInfo` (uid/time/message/nickName fields,
fields 1-9; ported from
`LagrangeGo/.../notify.proto:25 NewFriend`)
- `SelfJoinInGroup` (groupUin / operatorUid + a few placeholder
fields; ported from
`lagrange-python/pb/status/group.py:170 PBSelfJoinInGroup`)
Regression coverage in `tests/msg-push-handler.test.ts`:
- subType 179/226 both produce identical friend_add events
- falls back to `routingHead.fromUin` when the new friend's uid
isn't in the local cache (matches LagrangeGo's
"emit-then-resolve" pattern)
- subType 138 keeps fromUid as the peer (other recalled their
own msg to us); subType 139 picks toUid (we recalled ours
sent to them)
- PkgType 85 emits group_member_join with userUin == selfUin,
operator resolved through identity when known
core: 635/635 tests pass (+7), typecheck clean. Production bundle
now contains substituted codecs for FriendRecall, NewFriend,
SelfJoinInGroup (cross-checked via `grep protobuf_decode_X` on
`dist/index.mjs`).
|
||
|
|
604dbc3f64 |
fix(msg-push): Event0x210 subType=38 is the troop-shortcut-bar push, not friend-related
Earlier this commit acknowledged subType 38 as a placeholder
"FriendGrayTipInfo" because none of the LagrangeDev-family projects
(Lagrange V2 / lagrange-python / LagrangeGo / acidify) have a
handler for it — they all just fall through with a generic
"unknown subType" debug log. The "best guess" gray-tip framing was
wrong.
A contributor traced it through the decompiled stock QQ Android
client to `com.tencent.imcore.message.ext.codec.decoder.msgType0x210
.SubType0x26` (tsuzcx/qq_apk @ afe46ef). The payload is
`submsgtype0x26.MsgBody` dispatching on `uint32_sub_cmd`:
- 0x1 → `UpdateAppUnreadNum`: a list of
`{group_code, app_id, unread_num}` keeping group-internal
"mini-app" badge counts (e.g. `appId=101846662` / `101896870`
for the troop shortcut bar) in sync.
- 0x3 → `UpdateDiscussAppInfo`: `{conf_uin, app_tip_notify.text}`
discussion-group app tips, routed through `getGAudioHandler()`.
- 0x4 → delegated to the troop online-push handler.
None of these have a meaningful OneBot mapping — they're
QQ-client-UI state pushes (badge counts, in-app tips), not
conversation events. Rename the enum entry to `GroupAppStatePush`
to reflect the actual semantics, cite the decompiled source
inline, and keep the silent-drop behaviour from the previous
attempt. Tests + decoder routing follow the rename.
628/628 tests pass (unchanged), typecheck clean.
|
||
|
|
6b236f70bf |
refactor(bridge): split MsgPush handler into per-PkgType decoders
The 885-line msg-push-handler.ts is replaced by bridge/msg-push/: 8 per-PkgType decoders plus MsgPushRegistry (single decoder per PkgType, catch + log on error), MsgPushContext (header decoded once at entry), and rich-body-decoder (one deep function hides ~400 lines of element conversion shared by all three message types). parseMsgPush(pkt, identity) signature preserved; existing tests pass with an import-path change only. |
||
|
|
ac10c663f1 |
refactor(identity): absorb QQInfo into IdentityService
QQInfo's in-memory state (self identity, friends, group rosters,
profile cache) and read methods (findFriend / findGroup /
findGroupMember / findUserProfile / nickname / uin / selfProfile) now
live directly on IdentityService. The QQInfo class is gone; its
interfaces stay in `qq-info.ts` as plain types shared across the
remember*/find* surface.
This eliminates a long-standing duplication: every observation used to
maintain both QQInfo's in-memory structures and IdentityService's
SQLite mirror in parallel, with the uidByUin/uinByUid Maps tracked
separately. With one Module owning all three layers, every remember*
hydrates one set of structures and `loadSnapshot` repopulates the
in-memory uid↔uin index from SQLite on startup — closing the cold-cache
gap that previously forced findUidByUin to fall through to a SQLite
query after every restart.
Knock-on changes:
- `Bridge` no longer holds a QQInfo; constructor takes an
`IdentityService`. The CmdParser signature on the cmd dispatch
table now passes `IdentityService` to parsers.
- `msg-push-handler.ts` drops its private `resolveUidToUin(qqInfo, …)`
helper that bypassed IdentityService's cascade — the parser hot
path now reads through `identity.findUinByUid(uid, groupId)` and
benefits from the O(1) `uinByUid` map even on freshly-loaded
sessions.
- `BridgeManager`'s session-started callback drops the QQInfo
parameter; OneBotInstance no longer carries a `qqInfo` field and
exposes a `nickname` getter for the WebUI listing.
- `contact-actions` and the per-theme bridge action files stop
importing QQInfo; they read everything via `bridge.identity.*`.
|
||
|
|
dd02535a73 |
chore(core): flatten single-file dirs + drop dead writeFailed surface
Two small cleanups that came out of the round-2 architecture review and are sub-five-minutes each — bundled because each one is too small to justify its own commit. #5 — flatten single-file directories packages/core/src/bridge/builders/element-builder.ts -> packages/core/src/bridge/element-builder.ts packages/core/src/bridge/handlers/msg-push-handler.ts -> packages/core/src/bridge/msg-push-handler.ts Both dirs had exactly one file in them — a smell that says "planned a sibling that never landed". Moving up one level removes the wrapper directory. Touch list: - bridge.ts: 2 import paths - bridge/actions/forward.ts: 2 import paths - msg-push-handler.ts: internal ../X -> ./X for events, proto/element, proto/message (the dynamic-import-typeof references too) - element-builder.ts: internal ../X -> ./X for bridge, events, highway/* - 4 test files: import paths (2 regular, 2 vi.mock) #6 — drop IdentityService.hasWriteFailure identity-service.ts had a `private writeFailed = false` field set by the runWrite catch block and exposed via `get hasWriteFailure()`, but a full-repo grep showed zero callers of the getter. It was a public API stub for a feature that hadn't been wired (a webui health indicator, presumably). Removed: - the field - the getter - the assignment in runWrite's catch block (the error log on the next line is the actual observable behavior; nothing else reads the flag) If we want a "did any write fail this session" surface later, it's trivial to add back with an explicit consumer in mind. Behavior preserved end-to-end. No public API change outside the moved files. 369 tests pass (unchanged). 3 stability runs. |
||
|
|
bf0a89e221 | fix(core): resolve group join member identity |