Commit Graph
1191 Commits
Author SHA1 Message Date
DrSmoothl da26d6796f feat(core): aggregate receive health by account
Track receive health for every process attached to an account while treating unarmed monitoring as compatible rather than degraded.

Report the account as healthy while any attached process remains healthy, and mark it degraded only after every attached process has been confirmed stale. Keep health ownership synchronized with process attach, migration, and detach transitions.

Fail fast when process ownership and health state diverge instead of silently presenting a healthy status. Add multi-process regression coverage for partial degradation, full degradation, and recovery.

Refs #233.
2026-07-15 16:47:26 +08:00
DrSmoothl e8cb49ced9 feat(bridge): detect stalled receive paths
Arm receive monitoring only after an existing QQ heartbeat has been observed, preserving compatibility with sessions that do not expose the signal.

Require 90 seconds of receive silence plus a 15-second confirmation window before reporting degradation. Any later inbound frame refreshes or restores health, including non-routable frames that still prove the receive path is alive.

Bind watchdog state to the active account epoch so direct login switches and packet-driven account migration cannot inherit stale deadlines from a previous account.

Forward health transitions to the bridge layer without reconnecting, reinjecting, or otherwise changing session recovery behavior. Add regression coverage for arming, recovery, sleep-resume confirmation, account migration, and invalid-UIN frames.

Refs #233.
2026-07-15 16:46:16 +08:00
DrSmoothl bd9236e3fa feat(webui): expose transactional backup restore
Replace route-local restore writes with the core transaction and map preflight, rollback, and post-commit cleanup failures to precise HTTP responses. Treat only ENOENT as a missing export file and return transaction, restored, skipped, migrated, and restart facts.

Update the settings panel to describe the full sensitive configuration group, avoid restart guidance for empty plans, and reload settings after both successful and failed imports so the UI reflects committed disk state. Add HTTP regression coverage for export and restore error semantics.
2026-07-15 16:12:51 +08:00
DrSmoothl ccbee78cc7 feat(core): execute config restores as a transaction
Move restore execution into a dedicated module with explicit preflight, snapshot, stage, commit, rollback, and cleanup phases. Keep live files untouched until staging completes, roll back partial commits, and retain the transaction directory when rollback or cleanup cannot finish.

Report the transaction ID, active phase, failed files, commit state, rollback result, and recovery path without logging configuration contents. Cover every filesystem boundary with a focused fault matrix, including unreadable current files and cleanup failures.
2026-07-15 16:12:03 +08:00
DrSmoothl 2fea3ca4d9 feat(core): validate backup restores before mutation
Route selected backup entries through owner-owned config parsers before any filesystem write. Reject unsupported paths, mismatched encodings, malformed base64, lossy normalization, invalid effective overlays, and decoded payloads larger than 32 MiB.

Treat WebUI state, private keys, and every OneBot config as a single sensitive group. Skip unselected sensitive entries without inspecting their contents, preserve recognized migrations, and assign restrictive modes to credential files.
2026-07-15 16:11:44 +08:00
DrSmoothl 6a278721b2 docs(readme): fix build status badge
Point the badge at the dev build workflow and active development branch so Shields reports the current continuous build status.
2026-07-14 21:34:34 +08:00
DrSmoothl be05d47dd4 docs(readme): redesign project landing page
Add project-native hero and runtime flow visuals with responsive SVG layouts and decorative motion.

Expand setup, integration, architecture, contribution, and licensing guidance for current releases.
2026-07-14 21:29:34 +08:00
DrSmoothl f9d251d1fa fix(protocol): decode group member operators correctly
Handle member joins and ordinary removals using their direct operator identity while preserving the special nested form used when the bot is removed.

Reject malformed values with bounded diagnostic context and add regression coverage for #228 and related member-decrease events.
2026-07-14 20:08:29 +08:00
DrSmoothl a3467a8d89 chore(release): v1.12.2 2026-07-14 07:57:08 +08:00
DrSmoothl 21431e525d docs(mcp): refresh group notice action schema
Regenerate the MCP catalog from the OneBot action definitions. Document the new parameters, defaults, validation invariants, and merged notice readback.
2026-07-14 03:39:58 +08:00
DrSmoothl 9e60de32fe feat(sdk): expose group announcement controls
Add typed client options for pinned, popup, new-member, edit-card, and confirmation behavior. Return typed notice metadata and cover the HTTP parameter mapping.
2026-07-14 03:39:41 +08:00
DrSmoothl 5885187e7d test(group-notice): cover publishing options and readback
Verify endpoint selection, option validation, image metadata parsing, response merging, and OneBot forwarding. Cover the type-20 new-member path and fail-fast conflict behavior.
2026-07-14 03:39:27 +08:00
DrSmoothl 51eb1f733d feat(group-notice): support advanced announcement controls
Route regular and new-member announcements through their correct web endpoints. Validate option conflicts, preserve existing defaults, merge returned notice collections, and surface transport and upload failures.
2026-07-14 03:39:11 +08:00
DrSmoothl 216213e102 ci(release): redesign cross-platform release notes
Present Windows x64, Linux x64, and Linux arm64 as fully supported release targets in a concise download matrix.

Add quick-start and unattended-consent guidance, link commits and version comparisons, and filter merge and release housekeeping from the change list.

Refresh the adjacent packaging documentation so it matches current platform support.
2026-07-13 23:15:20 +08:00
DrSmoothl 74939da3d7 feat(webui): support environment agreement consent
Allow unattended deployments to accept the EULA and Privacy Notice through SNOWLUMA_ACCEPT_EULA and SNOWLUMA_ACCEPT_PRIVACY.

Require both values, reject invalid configuration, keep environment consent non-persistent, and log whether acceptance is complete or partial.

Document the deployment contract and add regression coverage for parsing, persistence isolation, and stored-consent compatibility.
2026-07-13 23:15:10 +08:00
DrSmoothl defe15694b feat(onebot): implement mark all as read
Replace the compatibility no-op with a real batched read action for current groups and observed incoming private conversations.

Exclude stale groups, group temporary sessions, and sent-message echoes from private targets, then expose the functional action through the generated MCP catalog.

Add action and message-store regressions for target selection and delegation.
2026-07-13 23:02:08 +08:00
DrSmoothl 4d93c727bb fix(protocol): retain C2C peer identities
Carry the stable sender identity through decoded private-message events and persist the UID/UIN pair at the receive boundary.

This keeps later conversation operations independent of stale message rows or fallible profile lookups, with regression coverage for decoding and identity indexing.
2026-07-13 23:01:54 +08:00
DrSmoothl 03aec3b052 fix(core): verify conversation read updates
Replace message-header sequence forwarding with a server-guided read flow for group and private conversations.

Batch read reports conservatively, validate per-conversation results and cursor advancement, and fail with actionable errors when the server does not apply an update.

Add regression coverage for mixed batches, pagination, already-current sessions, malformed responses, and private sequence-domain mismatches.
2026-07-13 23:01:39 +08:00
DrSmoothl 6f484e06e6 fix(protocol): model read report responses
Define the server response envelopes for group and private read reports, including per-conversation result codes and current/latest cursors.

This lets callers distinguish transport success from an actual read-state update.
2026-07-13 23:00:55 +08:00
DrSmoothl 939d740beb test(mcp): stabilize upload cancellation cleanup 2026-07-13 19:05:33 +08:00
DrSmoothl 4f175afab5 chore(mcp): refresh group system message metadata 2026-07-13 18:52:01 +08:00
DrSmoothl b5452c59e6 fix(onebot): return categorized friend lists 2026-07-13 18:51:45 +08:00
DrSmoothl 8cd7d51d80 fix(protocol): preserve friend category metadata 2026-07-13 18:51:11 +08:00
DrSmoothl 2ae9a47249 fix(onebot): clarify invalid JSON segment errors
Validate the public OneBot data.data field before converting JSON segments to the internal message element shape. Invalid payloads still return retcode 1400, but now identify the correct public field without reaching the QQ bridge.

Add Action-level regression coverage for issue #220.
2026-07-12 16:43:05 +08:00
DrSmoothl a824e0b46a fix(onebot): repair group system message queries 2026-07-10 19:11:15 +08:00
DrSmoothl b53004ee23 fix(proton): accept sign-extended uint32 field values 2026-07-10 18:20:11 +08:00
DrSmoothl c04864dd21 chore(release): v1.12.1 2026-07-10 14:00:40 +08:00
DrSmoothl a67c95407e feat(webui): show saved and applied config state
Surface whether a OneBot configuration was persisted, applied live, or left degraded. Keep failed adapters and apply errors visible in the config and overview pages instead of reporting a false success.
2026-07-10 13:49:49 +08:00
DrSmoothl 852004291b feat(onebot): reconcile and drain network adapters
Serialize desired network plans, isolate per-adapter apply failures, restore prior bindings when possible, and report saved versus applied state. Quiesce Action and event ingress, await in-flight dispatches and transport closes, retain failed generations for retry, and propagate shutdown failures.
2026-07-10 13:49:17 +08:00
DrSmoothl 01688c7567 fix(protocol): enforce message element contracts end to end
Replace the open message bag with a validated discriminated union and preflight complete messages before uploads or sends. Return BAD_REQUEST for invalid or unsendable segments instead of dropping fields, sending partial content, or silently changing private messages.
2026-07-10 13:47:06 +08:00
DrSmoothl aff6ad9179 refactor(onebot): make config persistence explicit
Persist user saves as authoritative per-account snapshots while retaining legacy overlay compatibility. Validate deterministic topology errors before writing and replace configuration files atomically.
2026-07-10 13:46:09 +08:00
DrSmoothl 588fdab8de refactor(onebot): compile and seal the action registry
Compile canonical names, aliases, stream kinds, documentation, and raw reservations into one immutable registry. Reject duplicate, undeclared, mismatched, or late handler registration before requests can be dispatched.
2026-07-10 13:45:32 +08:00
DrSmoothl 4825434ffa fix(onebot): await malformed stream responses
Wait for asynchronous transports to flush bad-request frames before processStreamRequest returns. This prevents malformed Stream API requests from losing their terminal error response.
2026-07-10 13:43:31 +08:00
DrSmoothl 9f7f34cd8f fix(core): bound merged-forward decompression
Cap long-message gunzip output at 32 MiB and fail before an oversized QQ response can exhaust process memory. Include the resource id, compressed size, and configured limit in observable errors.
2026-07-10 13:42:34 +08:00
DrSmoothl ffede67aed fix(websocket): cap decompressed message payloads
Apply the configured payload limit while per-message deflate is producing output, including fragmented frames. Abort oversized expansion before it can allocate unbounded memory.
2026-07-10 13:41:36 +08:00
DrSmoothl 920f75718a fix(protocol): bound rich message decoding
Reject malformed nested payloads, cap compressed card expansion, and enforce per-message decode budgets. Preserve unknown-wire metadata in diagnostics so unsupported QQ elements remain observable without exhausting memory.
2026-07-10 13:41:19 +08:00
DrSmoothl b7d648ea0f fix(proton): harden generated protobuf decoders
Reject malformed varints, out-of-bounds fields, and invalid groups before they escape their parent frame. Aggregate unknown-field metadata and decode signed int32 values while preserving SnowLuma's existing outbound encoding bytes.
2026-07-10 13:40:53 +08:00
DrSmoothl bb56030979 feat(mcp): support stream actions 2026-07-10 03:06:13 +08:00
DrSmoothl 4425bee612 feat(protocol): make identity persistence availability-first 2026-07-10 03:06:00 +08:00
DrSmoothl 369a82b5e4 fix(core): enforce bridge PID ownership 2026-07-10 03:05:50 +08:00
DrSmoothl 35958a7fc2 fix(onebot): use canonical group request flags 2026-07-10 02:42:07 +08:00
DrSmoothl 3c73d1d5b0 feat(onebot): reply into group temp sessions (临时会话) (#212)
send_private_msg / send_msg can now reply to a group temp-session message
by passing the source group_id. Inbound temp messages surface their
source group on sender.group_id; a reply is only made into a temp session
the bot has an inbound record of. Group temp replies route through
RoutingHead.grpTmp (field 3).
2026-07-10 02:29:18 +08:00
DrSmoothl b37b17f7d8 fix(protocol): guard identity.findGroup in group decoder (#212)
9b470d0 added ctx.identity.findGroup() to the group-message decoder for
the group_name cache fallback, but several suites mock a minimal identity
without findGroup, so decodeGroupMessage threw at runtime once the
typecheck gate (fixed in 5e73d69) let CI reach the test phase — forward /
packet-pipeline-stranger-resolve / msg-push-handler. Optional-chain the
call: the real IdentityService always has findGroup; only test mocks omit
it, and an absent cache simply yields no fallback name.
2026-07-09 23:13:40 +08:00
DrSmoothl 5e73d69663 fix(test): add groupName to GroupMessage test fixtures (#212 CI)
core's typecheck covers its own tests (protocol/onebot typecheck src
only), so the newly-required GroupMessage.groupName field failed the
Dev Build typecheck on test fixtures that construct group_message
events. Add groupName to every such fixture across core/onebot/protocol
tests.
2026-07-09 19:18:09 +08:00
DrSmoothl 9b470d0412 feat(onebot): add group_name to group message events (#212)
Group message events (message + message_sent) now carry group_name — a
non-standard but common OneBot extension. The name comes from the
message's own wire field (ResponseGrp field 7, accurate even right after
a rename), falling back to the identity group cache, else '' when neither
is available. It's resolved in the group-message decoder, where both the
wire head and the identity cache are already at hand, so the converter
just passes it through (no ConverterContext change).

Verified live (tempserver): real inbound group messages from a non-self
account carry group_name set to the actual group name.
2026-07-09 18:50:54 +08:00
DrSmoothl 7ab91c81e9 perf(highway): single-connection pipelined upload — supersede #211 concurrency
The #211 concurrency attempt (c17fff6) was disproven on a live endpoint.
QQ's highway server requires blocks to arrive in offset order and rejects
an out-of-order PUT with error_code=102902 — ~20% failure under a real
account even after deferring the finalize block. Parallel connections
can't guarantee arrival order, so that approach is unshippable.

Replace it with a single persistent connection that PUTs blocks in strict
offset order, reusing the socket across the whole file via HTTP keep-alive:
- In order: the server never sees an out-of-order block, so no 102902.
- One warm connection: the TCP congestion window stays open across the
  file instead of paying slow-start on a fresh connection per 1 MiB block.
  That per-block cold start was the real throughput cap behind #211.

Adaptive to QQ's edge nodes FIN'ing between chunks (the reason #118 went
one-connection-per-chunk): on a transport failure, or a peer half-close
after a response, drop the socket, reconnect, and re-send the SAME block
(re-PUTting an offset is idempotent). Worst case degrades to one
connection per block — no worse than #118, and still strictly in order.

Also fixes a latent bug on the reconnect path, surfaced by review: a
response truncated mid-stream (header arrived, Content-Length unmet, then
FIN) was resolved as a partial frame and then fatally rejected by
unpackHighwayFrame OUTSIDE the retry loop, turning a recoverable mid-body
FIN into a failed upload. A truncated response now rejects as a retryable
transport failure so the block is re-sent. Added an idle read timeout so
a silent peer can't hang the upload forever (the persistent socket widens
that exposure).

Verified live (tempserver, repeated 48 MiB group-file uploads): 1
connection per file, 0 failures across dense repeats, ~1.4x throughput
over the old per-chunk-connection serial path in a low-RTT datacenter —
the win is larger on high-RTT home links where per-block slow-start hurt
most (the original #211 report). Unit tests cover keep-alive reuse,
FIN-after-each-response degradation, mid-stream reconnect+resume, body-
truncation retry, in-order exactly-once coverage, and close-once.
2026-07-09 13:55:25 +08:00
DrSmoothl c17fff6e2f perf(core): parallelize highway chunk uploads (#211)
Group/private file uploads (and all highway media) topped out around
500 KB/s regardless of link speed. Two costs stacked up in
uploadHighwayHttp: chunks were sent strictly one at a time (throughput
capped at one 1 MiB block per round-trip), and each block opened a fresh
connection that paid TCP slow-start from a cold congestion window — so a
1 MiB block finished before the window ramped and never reached line
rate.

Drive the (already offset-addressed, self-contained) blocks through a
small worker pool instead of a sequential loop. Highway frames carry
their own dataOffset/chunkMd5/fileMd5, so blocks are independent and the
server reassembles by offset; a handful of connections overlap the
round-trip stalls and ramp their windows together. Concurrency is kept
modest (4) to stay friendly to the edge nodes' rate limiting, and the
one-connection-per-block invariant from #118 is preserved — parallelism
just puts several of those short-lived connections in flight at once.

Correctness preserved and covered by new tests:
- offset claim is synchronous (no await between read and advance of
  nextOffset), so every byte range is uploaded exactly once — no skip,
  no duplicate, ragged final block correct;
- concurrent ChunkSource reads are race-free (positional FileHandle
  reads into per-call buffers; read-only Buffer subarrays);
- first permanent block failure aborts the whole upload and rejects;
  peers stop claiming new blocks and drain the one in flight;
- workers never reject, so Promise.all settles all in-flight
  reads/sockets before the source is closed exactly once — no
  close-during-read, no masked upload error;
- 0-byte source still opens zero connections; peak memory bounded to
  concurrency x ~1 MiB, never the whole file.
2026-07-09 01:20:26 +08:00
DrSmoothl 90f65654c7 feat(webui): animate log rows — entrance + scroll edge fade (#185)
Give the virtualized log viewer motion without hurting readability or choking at
high rate. A genuinely new tail line fades and rises in (200ms, ease-out-expo);
detection lives in the SSE flush (real arrivals only), is bounded by a 260ms
timer, and never replays when an old row scrolls back into view or when a filter
toggles. A firehose batch (more than 4 new lines in one flush) suppresses the
per-row entrance so a 100 line/s stream never strobes — the scroll flow and edge
fade carry it instead.

Scrolling gets a soft, scroll-position-aware edge fade: a boundary dissolves only
when there is actually hidden content past it (a ResizeObserver + scroll listener
keep it in sync), so a fully-scrolled top/bottom row is never dimmed. The entrance
animation lives on an inner element so it never collides with the virtual row's
own translateY positioning or perturbs measureElement. All of it is gated on the
motion switches: the entrance on reduce/disable-motion, the static edge fade on
the hard 关闭全部动效 only.
2026-07-07 23:27:46 +08:00
DrSmoothl b6fb5bd530 chore(release): v1.12.0 2026-07-07 22:38:02 +08:00
DrSmoothl cd04271b69 fix(webui): confirm before loading from the process-probe dialog
The 加载 button added in #189 injected directly, unlike the process-list
load which routes through the confirm dialog. Fire the same
setConfirm({kind:'load'}) flow so both entry points share one
confirmation step.
2026-07-07 22:36:19 +08:00