You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
WebSocket transport failures escape from this method instead of becoming OneBotResult.Unreachable. A disconnected socket throws IllegalStateException, and the per-call timeout throws TimeoutCancellationException, so both Channel.send and the public typed API can throw while the HTTP path returns a result. Convert transport-owned timeout/connection failures here while still rethrowing caller cancellation.
Validate numeric reply IDs before constructing MessageId
This blindly constructs a numeric MessageId, but this same mapper creates delivered refs named sent and async when no platform ID is available. Reusing either delivered ref as replyTo therefore throws before sending, rather than returning a delivery failure. Validate that the target is a numeric OneBot message ID and handle unavailable/synthetic refs explicitly.
The mapping discards OneBotFailure.retryable for HTTP failures. In particular, an HTTP 5xx result is retryable by OneBotFailure, but it reaches Delivery.NotDelivered as non-retryable UNKNOWN, preventing normal retries. Pass the failure's retryability through (and classify retryable HTTP status failures as transient if appropriate).
A group member's card is the sender's display name, not the group's title. Putting it into ChatInfo.title mislabels every group chat with whichever member spoke most recently. The event has no standard group-name field, so leave the title null unless it is obtained separately.
Serialize typed request IDs using numeric ID serializers
All typed request IDs are written as JSON strings here and in the remaining request classes, bypassing the ID serializers that deliberately emit a number when it fits (OneBotIds.kt:109-112). OneBot declares these parameters as numeric, so strict WS/JSON implementations can reject otherwise valid typed calls. Encode UserId, GroupId, and MessageId through their serializers (falling back to strings only when outside Long).
A valid JSON scalar or array is converted into a synthetic Unknown event and therefore accepted by the HTTP receiver as a report, even though events must be objects; its original payload is also lost. Reject non-object elements (and have each transport classify/ignore that decode failure) instead of fabricating an event with zero IDs and empty raw JSON.
The codec computes subType but does not pass it to GroupBan, so both ban and lift_ban events expose subType == null despite the public model having that property. Preserve the wire value as the other notice decoders do.
Optional segment parameters are emitted as explicit JSON null values. explicitNulls = false does not remove JsonNull already inserted into a manually built object, so a simple Image(file = ...) sends null type, url, flags, and timeout instead of omitting optional fields as required by the request contract. Add each optional key only when non-null; the same pattern recurs in the record, video, poke, share, location, music, and node branches.
Rejected reports have already been enqueued and counted as accepted before the callback's decision. This contradicts Rejected's contract and lets downstream processing act on a request answered with 4xx. Decide first, and only report/increment the accepted and quick-operation branches.
Rejection bodies are plain text, but this labels every non-204 body as JSON. HTTP clients that honor the media type will try to parse text such as the signature... as JSON and fail to expose the actual reason. Use text/plain; charset=utf-8 for detail, retaining JSON only for quick-operation objects.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved configuration, protocol, security, and WebSocket lifecycle defects can cause startup failure, data corruption, denial of service, and delayed calls.
Values shorter than four characters are deliberately left unmasked, although access tokens and signing secrets have no minimum length. A non-2xx implementation response can echo such a token into OneBotFailure.message, violating this API's no-secret guarantee. Redact every non-empty configured value instead.
A socket response always carries this client's echo, but that value is only a correlation key, not the platform's message ID; toString() also includes JSON quotes for a string echo. When a successful send omits message_id, this therefore exposes a fabricated quoted ID instead of the documented sent fallback. Do not use echo as a message reference.
CQ escaping treats plain text and parameter values differently: commas are escaped only inside parameter values. Because this shared function also encodes Text, ordinary text commas become non-standard ,; the matching decoder also corrupts a literal , received in plain text. Split text escaping/unescaping from parameter escaping/unescaping.
Fail pending calls when closing the forward WebSocket
Closing a forward WebSocket does not complete or clear client.awaiting. Cancelling the connection loop can prevent its post-onClosefailPending call, so callers can remain suspended until firstByteTimeoutMillis after close() has returned. Atomically stop new sends and fail all pending deferreds before cancelling the scope.
Reads and writes the v11 event, action, message and result shapes, and serves forward WebSocket, reverse WebSocket and HTTP POST transports with token auth.
Adds the channel front end and reply sink, the sender and reply registry, command permissions, approval questions, activity summaries and the restart-safe sent-message ledger.
Exchanges the app credentials for a token, keeps it fresh, decodes the open platform payloads, and runs the WebSocket gateway with identify, heartbeat, resume and reconnect.
Adds the reply sink and sender for passive C2C and group replies, single-chat streaming with append chunks, Markdown adaptation, typing state, message ids for quotes, and the words hook for non-streaming chats.
Asks approvals with Markdown keyboard buttons, publishes command menus and panels, uploads files in the platform's chunks, and adds the qqbot.act tool.
The platform can send a button press as soon as the approval message is delivered, which may be before sendApproval returns. Register the chat before the send, remove it when delivery fails, and hold the send in the integration test so the press arrives during it.
An approval can be asked by a delegated run, so close every stream open in the approval's chat instead of only the asking turn's. Serialize the reply's stream and detached-run state, and close a stream before a final message carrying files, which the stream cannot carry.
Adds the commands the Telegram rules already have: heartbeat and schedule, timezone, home, link, unlink and untaint. A member could otherwise be refused a command Telegram allows.
Removes the onebot settings key the config does not have, adds the allowed chats list, and checks every example channel section against its config descriptor.
A chat shares one progress ledger between its replies. Serialize that ledger, guard the activity sender against a second launch, and close the sender exactly once; finish both reply endings through cleanup so a cancellation cannot leak the reply registry or the chat's progress.
A socket call that times out is a failed delivery or unavailable approval, not a cancelled turn or withdrawn request. The inbox also keeps the ids it took, so an implementation that reports one message twice asks the agent for one turn.
After startup, a missing gateway, a token failure or a connection that never becomes ready no longer ends the loop. Close the socket on a failed ready, keep one heartbeat, and drop a session the close code says can no longer be resumed.
A model can write an image target that names a local path. The implementation would read that path and post it to the chat, outside the floor. Only an http or https target becomes an image segment; anything else stays as the text the model wrote.
Keep only the most recent 1024 quote references per chat, and cap an OneBot approval body at 1500 characters so a large diff cannot make the approval undeliverable.
A group whose bot_state call failed was remembered as asked and never looked up again, so a full-mode mention could stop naming the bot until restart. Forget the group after a failed lookup.
The platform fixes are what the channel branch runs on a host whose paths are not POSIX, and the Windows fixes for command.exec are based on them. This branch takes them from fix/platform rather than from master, so both branches still reach master separately: the PR for the channel branch shows them only until the platform branch is merged there.
Resolutions: the example config takes master's git server example and keeps the onebot and qqbot sections; the version catalog keeps java-websocket beside jline and maven-publish; ToolExecutor passes the channel's trigger and the platform's call sandboxes to the tool context.
The channel branch passes the turn's trigger in the call as well, so the call does not fit a line any more and spotlessCheck asks for the wrapped form.
…into
The master merge gives a channel AgentCatalog, ChatLinks and ChatSettings instead of one AgentControl, makes Pairings a final class to delegate to, and reports a chat's language through ChatSettings.snapshot(chat).effectiveLanguage. The gate, the command rules, the pairing desk and the approval text of both channels follow that, and the approval tests build a request without a chain and without always rules, as the new constructor means them.
The open platform refuses a menu or command panel with 40030013 when it holds more items than it takes, and says in that answer's limit how many it takes, which is fewer than its documentation promises. A refused configuration is published once more with that many items, and the failure a second refusal leaves names the limit and the panels the scope already holds.
The log said a menu of every command was published although only the first ten items were: it names the items now, and how many commands there were.
cmd, powershell, pwsh, cscript, wscript, mshta, rundll32, regsvr32 and wsl are launchers like sh and bash: what a call gives them is code, which the floor cannot see, so they are opaque and no standing rule covers them. Their names are compared without case on a Windows host, where PowerShell.exe and cmd spell themselves that way.
An opaque program's argument that names no path of the host is script text and no path to refuse: a command line that is passed to a shell is not refused as blocked_floor any more, and it is written as a file only when that is wanted. Every other program still refuses a path-shaped word that the host cannot name.
An answer of 40030013 need not carry the limit it mentions, so a refused menu or panel is retried with five items then, and a refusal that leaves one names the answer itself and how many panels the scope holds. Past the panels the platform keeps for one bot it says that the console has to give way, since no number of items helps there.
The platform counts a character that is not ASCII as two units, and 40030013 is what it answers when a description is over its thirty units rather than when a panel holds too many items: a host whose language is Chinese therefore had every command panel refused, since thirty Chinese characters are sixty units. The item counts of a menu and a panel, and the item names, are cut to the units the platform documents, and a test checks a Chinese and an ASCII description.
Buying the access token happened outside the try of QqBotApi.call, so an unreachable token endpoint threw out of every call that needed one. QqBotGateway named its address through such a call outside any catch, so that exception ended the reconnect loop and no further connection was tried until the runtime restarted.
The token purchase is now part of the call's try and yields a failed answer. Naming the gateway is caught and retried, the startup tries it up to START_ATTEMPTS before giving up, and the gateway's scope logs an unexpected failure instead of ending without a line. Reading one frame of the platform is guarded as well, so a frame that fails to read does not end the inbox.
Tests: a refused token answers Failed instead of throwing; a gateway whose token is refused twice still connects. :libraries:channels:qqbot:spotlessCheck and :test pass: 72 tests, 0 failures.
origin/master moved from c2fbf47 to df7c55c, 15 commits. Conflicts and how they were settled:
- app/build.gradle.kts: kept the onebot and qqbot module dependencies, and added the provider-common one the app now imports.
- libraries/tools/exec/Planner.kt, Programs.kt and ExecWindowsTest.kt: took the origin/master versions. 45d95a8 reworks the Windows program handling on top of the platform module and covers the cmd batch arguments, so the branch's 7e1a926 version is superseded.
- app/src/dist/config/alexandrite.example.json: kept the channels section and the app.updates settings, and the formatting of origin/master.
Verification on Windows 11, JDK 21: :app:installDist; spotlessCheck on app, agent, tools, onebot, qqbot, plugin-sdk, provider-common, testkit, internal and runtime; internal 176 tests / 16 failed / 10 skipped, tools 214 / 2 / 76, onebot 265 / 0, qqbot 72 / 0, app targeted 139 / 38 (the same 38 that origin/master alone fails on this host), agent targeted 93 / 0 / 1, testkit 243 / 0 / 1, provider-common 140 / 0, plugin-sdk 222 / 2, runtime 312 / 1 / 14. The 16 internal, 2 tools, 38 app, 2 plugin-sdk and 1 runtime failures are the recorded Windows platform baselines, not regressions.
The build publishes every module except the app and the examples, so the two channel modules need a description or their POM fails. Their plugin SDK dependency becomes an API dependency and their plugin entry classes become internal, as the Telegram module has them.
…ts admins
The channel modules take the SDK of the merged master: ChatSettings under sdk.turn, ApprovalRequest built by its builder with alwaysRules, FloorCheck read with an else branch, TurnEvent.Content.Preview, and ReplySink.abandon taking vouched.
OneBot takes an operator only from the instance's admins, not from a group owner's or administrator's role. The config checks that admins are numbers and that allowedChats are private or group keys.
An approval of a member's turn goes only to an administrator chat that paired, and the instance warns at start about administrator chats that did not pair and about an empty admins list.
The provider's own image reading is dropped: the merged master normalizes an image before a wire API sends it.
…s that never ran
OneBot and QQ list every rule that allowing a call for good grants, and QQ's always button names no rule so the body can name each one. OneBot answers /approve and /deny in any case, and keeps approvalMinutes between one and 1440 minutes. Two QQ integration tests returned Termination, which JUnit does not discover; they return Unit now and run.
The OneBot and QQ approval texts gained an always-rules line and a short always label; the app's Chinese packs carry them so every language pack keeps the keys of en.properties.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OneBot v11 的客户端 Channel,位于 libraries/channels/onebot,按协议域、传输、消息映射、渠道与实例级 API、agent 集成推进。
协议域不涉及传输,也不引库。含 21 种消息段(含实现方上报的 file 段),认不出的段保留原始 data;三种消息形态;CQ 编解码;四类事件,认不出的保留原始 JSON;38 个公开动作与隐藏动作,
_async与_rate_limited由后缀派生;结果分层。四种通信各自独立,每实例选一种。http 的对测覆盖成功、拒绝、连不上、鉴权失败、畸形应答,另有 token 不泄漏的断言;http_post 覆盖验签、路径、账号绑定与快速操作回写;ws 按 echo 关联读回应答并读事件;ws_reverse 由对端拨号接入,并经该连接完成一次调用。另有一个运行于真实 runtime 的集成测试,覆盖实现上报消息至回复经 send_private_msg 发出。
消息映射把上报的消息、引用、媒体与未寻址的群消息分开处理:群消息需 @ 本实例或引用本实例的消息才交给 agent,命令按
/前缀识别并查管理员门禁;每个聊天需先配对,未配对的聊天只放行配对码。批准请求以回复该条消息的方式应答,/approve与/deny走命令路径。非消息上报经 ChannelNotice 与 ChannelPoints.NOTICE 发布,订阅方贡献 ObserverHook 即可收到,未配对的聊天照常发布。工具面是一张声明表加两个工具(onebot.read只读、onebot.act改动),共 22 个动作,加动作即加一行;凭据类、raw、快速操作、重启与清缓存不在表内,无法由模型提出。文件参数经 SDK 的 HardFloor 判定路径,与 fs 工具同一套判决;超过单次请求可携带的文件走实现方的分块流上传。回合进行中的动作经渠道事件上报到聊天:第一步在开始时以现在时发出,已结束的步骤按 8 秒限流合并为过去时的一条消息,措辞与 telegram 渠道同一套键值,可由实例配置项progress关闭。回合进行中模型自己说的话经 preview 上报,同样受该间隔限流,每条只含未说过的部分;只在回合已经开始使用工具之后才发,因为在那之前的话就是紧随其后的回答。两个开关分别控制:progress管动作,streaming管模型的话。大文件上传声明了 5 分钟超时,文件读取与写入在 IO 调度器上进行。对渠道之外模块的改动:plugin-sdk 增加 ToolContext.trigger(带默认值,实现者不必改)与 ChannelNotice、ChannelPoints.NOTICE;agent 的框架标明消息 id 与引用 id,并把 trigger 交给工具;provider-common 在渠道只报了图片种类时按字节判断格式。依赖方面引入 org.java-websocket:Java-WebSocket:1.6.0,仅出现在插件模块与版本目录,唯一传递依赖为 slf4j-api。
以下边界本 PR 不处理:
验证命令为
./gradlew :libraries:channels:onebot:spotlessCheck :libraries:channels:onebot:test :app:installDist,插件模块 214 个测试通过,spotless 与 allWarningsAsErrors 通过。最近一次 CI 运行报出两处失败,已在本分支修复:testkit 的样本未列出 ToolContext 新增的 trigger 声明;Telegram 的批准引用测试未跟上框架中消息 id 与引用 id 的变化。Windows 上的失败与本 PR 无关,分两类,均由 fix/platform 分支(PR #3)处理:一类是断言写死 POSIX 的路径、换行与错误文案;另一类是上游 e7747d8 把 Path.of("/dev") 写入默认保护位置清单,Windows 上该路径为无盘符的
\dev,PathCanonicalizer 无法处理,AgentFloor 构造失败导致 runtime 无法启动,同一缺陷另使 writeAtomically 在无 POSIX 权限的宿主上无法建文件。