Repository navigation
feat(agent): add Bot mode with SendUser and a Host-owned outbox - #6464
Conversation
Add the Bot as a mode of the Agent: one persistent Session per installation that reaches its user only through SendUser. - openBot returns the single Bot Session, creating it on first use. Its immutable tool allow-list holds only agent-native coordination tools; workspace, browser, web and MCP tools are never granted. - SendUser is hidden from every other Session, cannot be requested by ordinary creation and is not inherited by derived Sessions. - SendUser records messages in an outbox kept apart from Run state: the same messageId and content is a duplicate, the same id with different content is refused, and sending nothing is valid. botOutbox / watchBotOutbox read the newest 100 messages. - Queued wakes of the Bot Session, including scheduled and WorkHub-origin messages, continue after an unknown tool outcome (model told, tool not replayed); ordinary Sessions keep the stricter rule. - Interval schedules that wake the Bot must be at least 300 seconds apart. - Workbench: an "Open Bot" command and a panel above the composer showing the Bot's SendUser messages. The Maka composition enables the mode. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@xxhZs could you review this too? You built the personal assistant Bot on
简体中文@xxhZs 也麻烦你评审一下。
|
M4n5ter
left a comment
There was a problem hiding this comment.
English
I think this works as a first implementation. We can discuss the tool list and whether the Bot should only coordinate work later; we don't need to settle that in this PR. IM channels, attachments and further work on the conversation/execution views can also come later.
There are a few things I'd like to fix first:
-
I'd remove the message and scheduling limits.
I don't see a concrete need for 100 messages per call, 48,000 characters, a 200-character ID or a 300-second interval minimum. The scheduling rule is also inconsistent: an interval must be at least five minutes, but a cron schedule can run every minute. -
Bot identity needs to be consistent.
Regardless of which tools we eventually choose, the registered Session ID and the tool-list check currently disagree. A fork keepsSendUserand gets treated as a Bot during recovery, but cannot actually useSendUser. Opening a Bot from a Session with a restricted tool list can do the opposite: removeSendUserwhile still reporting success. We should have one place that decides whether a Session is the Bot. -
Two simultaneous first opens can leave an extra Session behind.
Both requests can create a Session before attempting the record CAS. The losing request returns the winner, but leaves its own Session behind. That path should leave just one Bot Session, including when requests race or are retried. -
The outbox shouldn't discard its deduplication records.
Once a call falls outside the 1,000-call window, the same ID and content can be recorded again, and different content with that ID is no longer rejected. I'd keep the existing Host storage, but store calls individually and read messages through cursor pagination. That would also avoid reading and rewriting the whole outbox on every send. Returning only the newest 100 messages doesn't let a reader retrieve anything it missed. -
Ordinary conversations don't need a Bot subscription.
At the moment they also subscribe to the global outbox and filter the result inside the component. We can attach the panel and subscription only to the Bot Session. -
The useful checks should stay.
Atomic writes, deduplication, invocation ownership and lifecycle checks all serve a purpose. In particular, state can change between preparation and execution, so those two checks can both be needed. We can adjust the tests when removing the limits and add coverage for the creation, fork and deduplication cases above.
简体中文
我觉得这个可以作为初版继续推进。工具名单,以及 Bot 是否只负责协调,我们之后再单独讨论,这次不用急着定下来。IM 渠道、附件,以及对话视图和执行视图的进一步区分,也可以后面逐步补。
有几处我希望先处理一下:
-
消息和调度的这些限制可以先去掉。
每次 100 条消息、48000 字符、200 字符 ID,以及 300 秒最小间隔,目前没看到具体依据。调度规则也不一致:interval 至少要五分钟,cron 却可以每分钟执行。 -
Bot 身份这里需要理顺。
无论最后选哪些工具,登记的 Session ID 和工具名单判断都应该得出一致结果。现在 fork 会保留SendUser,恢复时被当成 Bot,真正发送时却被拒绝;从工具受限的 Session 打开 Bot,又可能移除SendUser,但仍返回成功。我们需要统一由一处判断“这个 Session 是不是 Bot”。 -
并发首次打开时,会留下多余的 Session。
两个请求都可能先创建 Session,再竞争唯一记录的 CAS。输掉的请求返回赢家,但自己创建的 Session 留下了。这条路径需要在并发、重试时都能收敛到一个 Bot Session。 -
outbox 不应该把去重依据一起淘汰。
一次调用落到 1000 次的保留窗口之外后,相同 ID、相同内容可以再次记录,不同内容复用这个 ID 也不再被拒绝。我倾向于继续用现有 Host 存储,按调用分别保存、按游标分页读取,也避免每次发送都全量读写整个 outbox。只返回最新 100 条,读取方没办法补读错过的消息。 -
普通会话不需要订阅 Bot 消息。
现在普通会话也会订阅全局 outbox,再在组件内部过滤。可以只在 Bot Session 上挂载这个面板和订阅。 -
真正有用的检查还是保留。
原子写入、去重、Invocation 归属和生命周期检查都有实际作用。尤其准备阶段和执行阶段之间,状态可能变化,两次检查可以都有必要。删除限制时相应调整测试,再覆盖上面的创建、fork 和去重问题就好。
…le outbox - Remove the per-call message, character and messageId limits and the Bot interval minimum. - Decide Bot identity only from the registered record (`Bot::is`), loaded before activation. Tool grants, recovery and SendUser all use it; SendUser is granted by identity, not by the Bot's tool list, so forks and derived Sessions are ordinary and a Bot opened from a restricted Session can speak. - Claim the record with CAS before creating the Session so concurrent and retried first opens converge on one Session; release the claim on failure. - Store each call and message as its own record, committed with the head in one CAS batch. Calls are never evicted, and botOutbox pages by cursor. - Subscribe to the outbox only on the Bot Session. - Export the `bot` command from the conversations UI module; its absence made the desktop fail with a contribution/declaration mismatch. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@M4n5ter thanks for the thorough review. All six points are addressed in e85edaa:
New coverage: I also fixed a bug of my own that only shows at runtime: the conversations UI module did not export the new Separately, 简体中文@M4n5ter 感谢细致的评审。六点都在 e85edaa 中处理了:
新增测试: 另外修了一个我自己引入、只在运行时出现的 bug:会话插件的 UI 模块没有导出新增的 此外,当前 |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I reviewed all 25 changed files at e85edaafcc723dafe4950b3195d50a59b9fc6b6a, including the registered Bot identity, creation claim, tool selection and recovery paths, per-call outbox records, service contracts and workbench integration. I also read M4n5ter's review of 5846668f and the subsequent revision. I found no verified P0–P2 issue within this source review and the controlled UI checks. The slow-creation case in the P3 inline still needs native concurrency coverage.
The revision uses the registered Session ID for tool grants, recovery and SendUser, loads that identity before activation, and removes the earlier per-call and scheduling limits. Outbox appends put the head, call record and messages in one CAS batch; call records are retained for deduplication, and readers page by sequence. The ordinary-conversation path now performs one identity lookup and does not subscribe. These are source observations, not independent execution of the Rust durability or recovery tests.
Ten checks passed using the actual BotMessages async/state code with controlled source and signal adapters. They cover ordinary-session isolation, initial loading of the latest 50 messages, the latest-three and expanded views, multi-page catch-up, the 200-message in-memory window, repeated heads, disposal during lookup/subscription, a different Session, disabled mode, read failure and a closed stream. JSX rendering, native layout and actual Host storage were not exercised.
P3 — description: The opening summary still describes the old tool-list identity, newest-100-only outbox and interval minimum. Please update it to match this revision and the newer explanatory comment.
A static merge against rust-preview 8c55ad6149b33f7f6e5b13df913300dd30f2be1e is clean. This PR head has no CI result. I did not run Rust compilation, the full Agent suite, real model/tool execution, crash recovery or desktop/terminal acceptance. The author's reported native test results and isolated PTY rerun were not independently reproduced here.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
| Ok(_) => {} | ||
| Err(error) if error == "agent_session_unavailable" => { | ||
| // Another open has claimed but not yet created its Session. | ||
| if waits < CLAIM_WAITS { |
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
[P3] Cover a live creation that outlasts the five-second claim wait
After 50 waits of 100 ms, a caller that still sees agent_session_unavailable falls through and may replace the other operation's record with its own ID. The original creator has not necessarily stopped: create_inner can still be awaiting workspace preparation or storage before publishing its Session, and its success path does not recheck the registered claim. The per-Session creation guard uses each operation's different ID, so it does not by itself fence the two creators.
Please add a case that pauses the first creation after its successful claim for longer than this wait, starts a second open, and then resumes the first. Check that both callers converge and that only one Session remains. The current concurrency test covers quick successful opens, but does not pause that phase. A timeout should not by itself authorize replacing a live creator's claim; an ownership fence or confirmed settlement is needed before takeover.
This is a source-level concurrency concern; I have not run the native slow-creation scenario on this machine.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
The workbench looked up the Bot Session with `after: Number.MAX_SAFE_INTEGER`, which the desktop rejected as `agent_input_invalid`; the lookup's error then showed the Bot panel on an ordinary conversation. Add `limit` to botOutbox (default and maximum 100; 0 reads only the head) and use `limit: 0` for the lookup and the change signal. A failed lookup no longer renders a panel on ordinary conversations. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The desktop rejected "Open Bot" with "unsupported agent control method": openBot was missing from the client's control allow-list, which also required a sessionId for every control except create. Allow openBot without a target Session, and switch to the Bot when a recovered openBot completes. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I reviewed the nine-file increment from e85edaafcc723dafe4950b3195d50a59b9fc6b6a to 883d1bb685ee3ff84bee6bc727023e2b6c2e7e94. It adds a zero-message outbox read, uses it for identity lookup and subscriptions, suppresses lookup errors on ordinary conversations, and allows openBot through the native client control path and recovery UI. I found no new verified P0–P2 issue in this increment.
Twelve controlled checks passed against the actual message-panel async/state code and Composer source adapter. They include the earlier pagination/disposal cases, a failed identity lookup that stays hidden, and the exact limit: 0 query/subscription inputs. Running the failed-lookup case against e85edaaf fails because its error becomes visible. These checks do not execute the Rust client validator, native controls or Host storage.
The previous slow-creation P3 remains: the claim wait and takeover code have not changed, and I have not run that native concurrency scenario. The description also still describes the old identity and limits.
Integration status changed: an actual static merge with rust-preview d2529a0901ad1b089f2ad118b64bc5961c94f9e7 fails with content conflicts in plugins/agent/api/src/contract.rs and plugins/agent/native/src/owner/tests.rs. This supersedes my earlier clean-merge result. This head has no CI result; I did not run a Rust build, native Bot opening, receipt recovery or desktop acceptance.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
M4n5ter
left a comment
There was a problem hiding this comment.
English
I have two more suggestions after going through the latest changes:
-
Could we put a dedicated Maka Bot entry in the upper-left sidebar, perhaps right below New conversation, similar to the entry on main? Clicking it could reuse the existing
openBotflow. With that dedicated entry, the Bot Session should also be excluded from the regular session list, so it doesn't appear in two places. -
SendUsershould probably be always visible to the Bot. Maka enables tool discovery by default, but the first model request only lists its name in thetool_searchinventory. The instructions explaining that Bot must use it to communicate are hidden along with the tool description. I verified that searching for it and then calling it works, but I had to explicitly arrange that search in the test. Could we use the existingwith_always_visiblemechanism so the model sees the tool and its instructions from the start? This can be handled independently of the broader tool-list discussion.
简体中文
结合最新改动重新看了一遍,我这里还有两点建议:
-
Bot 的入口能不能参考 main,放在左上角侧边栏里?比如在 新建会话 下面放一个 Maka Bot,点击后复用已有的
openBot流程。有了专门的入口,普通会话列表里也应该排除 Bot Session,避免同一个会话出现在两个地方。 -
SendUser应该让 Bot 始终可见。Maka 默认开启工具发现,但首次模型请求里只在tool_search的工具名单中列出了它的名字。告诉模型“必须通过它向用户发消息”的说明,也随着工具描述一起被隐藏了。我实际验证过,先搜索再调用是能走通的,但这个搜索是我在测试里主动安排的。这里能不能复用已有的with_always_visible,让模型一开始就看到工具及其使用规则?这一点可以单独处理,不影响之后再讨论其他工具名单。
M4n5ter
left a comment
There was a problem hiding this comment.
English
One more thing to clarify: the Bot Session should keep running in a loop, including when there are no new user messages.
Its model requests should therefore use tool_choice: "required", and we should provide a sleep tool for it to call when there's nothing to do. Once the sleep finishes, the loop continues. SendUser should still only be used when there's something worth telling the user; the Bot can stay quiet while it waits.
@xxhZs, could you help review the remaining details?
简体中文
再补充一下 Bot 的运行方式:Bot Session 应该一直在循环中运行,没有新的用户消息时也要继续。
所以,Bot Session 的模型请求需要携带 tool_choice: "required",同时提供一个 sleep 工具,让它在没事可做的时候调用,等待结束后继续循环。SendUser 仍然只在有值得告知用户的事情时使用,等待期间可以保持安静。
其他细节也请 @xxhZs 帮忙 review 一下。
In the Bot Session, Chat now shows the user's messages and what the Bot sent with SendUser, rendered as Markdown in transcript order; the model's own replies and tool steps stay in Trajectory. This replaces the panel above the composer, which duplicated the reply and showed raw Markdown. Messages admitted to the Bot Session also carry standing Bot instructions: the user only sees SendUser, so ordinary text stays minimal. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # plugins/agent/api/src/contract.rs # plugins/agent/conversations/ui/Conversation.tsx # plugins/agent/native/src/owner/tests.rs
- Mark SendUser always visible, so with tool discovery the Bot sees the tool and its rules in the first request instead of only its name. - A first open now registers its live creation before claiming the Bot record. Another open waits for a live creator however long it takes, and takes over only a claim with no creator left (interrupted or from an earlier run). A timeout alone no longer authorizes the takeover; the creator rechecks the record before reporting success. - Judge the ordinary-Session recovery case by its own log: under parallel tests a stray client can reach a reused listener port. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Maka Bot entry below New conversation. It opens an existing Bot directly and creates it from the current conversation's workspace the first time. The Bot Session is no longer listed among ordinary conversations. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat matched SendUser messages to tool-call rows by role and argument text, but completed tool uses are presented as merged tool rows, so nothing matched and the Bot's reply did not appear. Record the sending Turn on each outbox message and show a Turn's SendUser messages after that Turn's entries. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat hides the Bot's own output, so a Turn in progress showed nothing until SendUser sent a message. Show a "Working…" row, with the latest tool name when there is one, at the end of the Bot's Chat while a run is active. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If a finished Turn sent nothing with SendUser, Chat shows the model's final answer so the user is never left without a reply. The standalone "working" row now appears only until the running Turn has its own header, which removes the duplicate line. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "working" row sat at the end of Chat, so a SendUser reply could appear above it and the completed header was then inserted above the reply. Place it at the start of the running Turn's entries, before the reply, so the header replaces it in place. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Asked what it could do, the Bot described every skill in the library, which it cannot run: it has no file, command, browser, network or MCP tools. State its real tools, send hands-on work to workhub_tasks, and say that the skill list is not a description of its own abilities. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/plugin-sdk/src/generated/methods.ts
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I compared 883d1bb6 with 64373ae9a881f2d20b59d509d000da7f7cf6bd41 using the author commit ranges: the four earlier commits are unchanged, and I reviewed the eight new author commits separately from the target-branch merges. One new P2 needs correction: Chat treats an unloaded or failed Bot transcript as proof that the Turn sent no SendUser reply, exposing ordinary model output. The inline comment contains the reproduction and suggested boundary.
The earlier slow-creation P3 is addressed in the source: a live creator is registered before claiming, a timeout returns bot_open_in_progress instead of taking its claim, and success rechecks ownership. The new native tests cover a seven-second live claim and an interrupted creator; I read them but did not independently execute them. The description now reflects the implementation. The sidebar entry, ordinary-list exclusion, always-visible SendUser, standing instructions, Turn-associated replies and working-row placement are present.
Four controlled checks of the actual transcript/display code passed: ordinary-session isolation, cursor paging and Turn grouping, working-row placement, and disposal during a page read. The two pending/failed-read safety checks fail at this head and pass with the actual display computation from before the fallback was added. Ten changed TS/TSX files from the merged tree parse with Biome 2.5.9; this is not a product type check or native rendering test.
The author explicitly defers the continuous loop, required tool choice and sleep tool from the maintainer's review. That scope request is still open; I have not treated it as completed.
A static merge against rust-preview 6fd58c38e7f47fea1927afe2614c6ac6f409c6ae is clean, superseding the old three-conflict result. This PR head has no CI result. I did not run Rust compilation, native Host storage, concurrency/crash recovery or desktop acceptance. The author's native test and manual desktop results remain their reported validation.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
| if ( | ||
| botChat && | ||
| row.role === 'assistant' && | ||
| !(row.finalOutput && turn && !turn.active && bot.forTurn(turn.turnId).length === 0) |
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
[P2] Distinguish an unloaded transcript from a Turn that sent nothing
createBotTranscript marks isBot true before subscribing and reading the outbox pages, while forTurn returns an empty array until those pages arrive. This condition therefore admits the ordinary final-answer row for a completed Turn even when that Turn actually sent a SendUser reply. A page-read failure is swallowed and leaves the same false-empty state indefinitely.
I reproduced both schedules using the unchanged production transcript and display computation with controlled Host/stream adapters. With a recorded SendUser message held behind the page read, Chat contains the internal final-answer row; releasing the page replaces it with the Bot reply. Rejecting that read leaves the internal row visible. Both safety assertions fail here and pass with the real pre-fallback display computation (40067dba), using the same current transcript and inputs. No native layout or real Host I/O was executed.
Keep ordinary model output hidden while transcript state is unknown or failed. Any no-message fallback needs a confirmed result for that Turn, rather than absence from the local map.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
The message-page case is fixed at 7bd0df4b64ad53b282ad3df571267e5485f68767: all six scenarios from my earlier production-code probe, including the held and rejected page reads, now pass. The new repository transcript suite also passes all six tests. The separate initial identity-lookup path still treats pending/failed identity as ordinary; I reported that separately in the current-head review. These checks use controlled Host/signal/stream adapters, without native rendering or actual Host I/O.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
# Conflicts: # plugins/agent/conversations/ui/SessionSurface.tsx
Thanks both. Everything below is pushed in 7bd0df4, and the branch merges cleanly with @Astro-Han, P2 (unloaded transcript treated as "sent nothing"): fixed. You were right: absence from the local map was used as proof that a Turn sent no @M4n5ter, sidebar entry and
@M4n5ter, continuous loop (
Order: One more thing to settle (from the thread above): a worker that has run for hours is hard to follow today. I'd add a read-only way to see a running task's progress (state, time running, last few steps) and a way to send a message to a task the Bot itself started, rather than exposing the whole Session API, which would also give an unattended Bot delete and archive. Does that split sound right? 简体中文谢谢两位。以下内容都在 7bd0df4 中,并且分支可以和 @Astro-Han,P2(把"还没加载"当成"没发过消息"):已修复。你说得对:之前把本地 map 里没有记录当成了“这一轮没调用 @M4n5ter,侧边栏入口和
@M4n5ter,持续循环(
顺序:先做 另一件需要定的事(来自上面线程里的讨论): 目前很难跟进一个已经跑了几个小时的 worker。我建议增加一个只读的方式来查看运行中任务的进度(状态、已运行时长、最近几步),以及一个向 Bot 自己发起的任务补话的入口,而不是把整个 Session API 暴露出去,否则无人值守的 Bot 也会拥有删除和归档能力。这样划分合适吗? |
|
The reference for this port should be the user-visible interaction of the assistant on For the behavior already introduced here, please preserve these interaction semantics:
On the This is not a request to add every missing assistant module to this PR. The main point is to avoid changing established chat and delivery semantics while porting the core. Useful parity checks are: send while still working; finish silently without publishing internal text; receive a user correction during a run; receive a worker result while handling a user message; reconnect without losing or duplicating sent messages. 中文希望对齐的是 main 个人助手的使用体验,不要求复制专用 Inbox,也不要求继续采用“用户会话+隐藏工作会话”。复用 Rust Agent 的队列、调度、执行机制,以及唯一 Bot 身份、协调工具和持久化 Outbox,这些方向都可以。 已经涉及的行为需要保持一致:
另外,main 的 Sleep 没有时长参数,是等待事件,不是让模型选择睡多久。没消息时不调用模型;用户消息、后台结果等事件到来时唤醒。如果休眠时撞上新消息,拒绝休眠并告诉模型原因。SendUser 不代表休眠,普通文本也不能结束循环。实现可以不同,但不要把它变成一个定时轮询 Bot,也不必为了复现这个机制新增休眠上下限或每日唤醒预算。 这里不要求把尚未移植的模块全部塞进本 PR,只希望当前核心的聊天、发送、沉默和调度交互与 main 一致。建议用“边工作边发消息、静默结束、运行中追加纠正、用户消息与任务结果交错、断线重连不丢不重”验证使用层面的对齐。 |
EnglishThanks, @xxhZs. The clarification about Sleep helps. Event-driven waiting makes sense, and ordinary model output should stay internal even when the Bot deliberately sends nothing. I agree that the final-answer fallback should go. Keeping Bot as a coordinator with an explicit tool list sounds reasonable to me. Within that role, could we expose the existing Session API through plugin-contributed tools and give Bot the relevant operations? Session management is itself a useful Bot capability. Users may ask it to find related conversations, rename or categorize them, archive old ones, or continue work in an existing Session. Restricting Bot to tasks it created would prevent those uses. Who created a Session doesn’t tell us whether the user wants Bot to manage it. For operations such as deletion, we can reuse the existing authorization and confirmation mechanism. A tool configured with Within our trust model, I don’t think defending against a deliberately malicious model should drive this API boundary. We treat installed plugins as user-authorized code. If the model can delegate to workers with sufficient filesystem and execution access, it could potentially modify Maka’s SQLite database directly anyway. I’d prefer to make the supported API usable and preserve its normal consistency and lifecycle checks. This also seems worth considering alongside WorkHub. Session tools could cover much of ordinary delegation and communication. We could consider refactoring WorkHub, or even removing it as a separate layer and folding the necessary coordination into Bot. Reliable result delivery, wake-up, recovery and dependencies would still need to be accounted for. I’m not asking for that whole refactor in this PR. Could we discuss this direction before adding a separate set of restricted Bot task APIs? 简体中文谢谢 @xxhZs,Sleep 的说明很有帮助。通过事件驱动等待是合理的,Bot 主动保持沉默时,普通模型输出也应该留在内部。我同意去掉 final answer 的兜底展示。 Bot 保持协调者定位,并使用明确的工具名单,这个方向我觉得可以。在这个定位下,能不能把已有 Session API 通过插件贡献为工具,再给 Bot 提供其中需要的操作? 会话管理本身也是很实际的 Bot 需求。用户可能希望它查找相关会话、修改标题或分类、归档旧会话,或者接着某个已有会话继续工作。如果只允许操作 Bot 自己创建的任务,这些需求也会被挡住。会话是谁创建的,并不能说明用户是否希望 Bot 管理它。 删除等操作可以复用已有的授权和审批机制。工具配置为 按我们目前的信任模型,我觉得不需要围绕“模型故意破坏 Maka”来设计这层 API 边界。我们把用户安装的插件视为已得到用户授权的代码。如果模型能够委派拥有足够文件和命令权限的 worker,甚至可能直接修改 Maka 的 SQLite 数据库。我更倾向于让正规 API 好用,并保留正常的一致性和生命周期检查。 这也值得结合 WorkHub 一起考虑。Session 工具可以覆盖不少普通的委派和沟通能力。我们可以考虑重构 WorkHub,甚至移除独立的这一层,把必要的协调能力融合进 Bot。结果可靠回传、唤醒、恢复和依赖关系等行为,仍然需要妥善处理。 我不是希望把整个重构都塞进这个 PR。只是,在增加另一套受限的 Bot 任务接口之前,能否先讨论一下这个方向? |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I reviewed the merge increment from 64373ae9a881f2d20b59d509d000da7f7cf6bd41 to 7bd0df4b64ad53b282ad3df571267e5485f68767, separating the incoming rust-preview changes from the Bot changes. I checked the SessionSurface resolution, sidebar signature, new transcript core and reactive adapter, Conversation display and confirmation effect, and the six new transcript tests.
The earlier unloaded/failed-message-page P2 is fixed. All six scenarios from my previous production-code probe now pass, including both safety assertions that failed at 64373ae9. The six new repository transcript tests also pass. These execute JavaScript/TypeScript state and display computation with controlled Host, signal and stream adapters; they do not exercise native rendering or actual Host storage.
One P2 remains at an earlier stage: pending or failed Bot identity lookup still exposes ordinary internal model output in Chat. The inline describes the two reproduced schedules. The new eight-scenario probe passes the six cases above and fails these two safety assertions. Supplying an already-confirmed Bot identity, with the same display code and Host schedules, makes both assertions pass; that is a diagnostic control, not an implemented fix.
I also read the latest author, xxhZs and M4n5ter comments. M4n5ter now explicitly agrees that the final-answer fallback should be removed even for a confirmed-empty outbox; this head still retains it in Conversation.tsx. The event-driven waiting and Session-tool/WorkHub direction remain under discussion. This revision does not establish agreement to defer those interaction requirements, and I have not independently tested ongoing-run input, delegation or reconnect parity.
Six changed TS/TSX/MJS files parse with the available Biome 2.5.9, and git diff --check passes. This is syntax checking, not the repository's complete type check. An actual static merge against current rust-preview 4929df52c00d26a8b119873f9b91356ac4137749 fails in plugins/agent/native/src/owner.rs and products/maka/workbench/app/WorkspaceBrowser.tsx; the earlier SessionSurface conflict has been resolved. This head has no CI checks. I did not run Rust compilation, native Bot concurrency/recovery, real model/tool execution or desktop/terminal acceptance.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
| changed(); | ||
| } | ||
| } catch { | ||
| // Not the Bot Session, or the Bot mode is off: the conversation stays ordinary. |
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
[P2] Keep unknown Bot identity separate from an ordinary Session
The first botOutbox({limit: 0}) lookup runs independently of history loading. Until it succeeds, isBot remains false; if it throws, this catch leaves it false permanently and never starts a subscription. Conversation uses bot.isBot() && reading() === 'chat' to decide whether to hide ordinary assistant rows. Consequently a real Bot Session whose history loads before this lookup displays its internal final-answer row as an ordinary reply. A failed identity lookup leaves that internal row visible indefinitely, even when the Host has a recorded SendUser reply for the Turn. The new confirmedEmpty check is bypassed in both cases.
I reproduced both cases with the unchanged current core, reactive adapter and display computation, holding or rejecting only the initial identity query while supplying the completed Turn's history. Both safety assertions fail; after releasing the held lookup, the internal row is replaced by the recorded Bot reply. Keeping the same inputs but supplying an already-confirmed Bot identity makes both assertions pass. Host/signal/stream adapters are controlled; no native screen or real Host I/O was executed.
Preserve the known Bot identity when opening its Session, or keep identity pending/failed distinct from confirmed ordinary before publishing assistant rows. An identity-query error must not silently grant the ordinary-display path. This is distinct from the message-page case fixed in this revision.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
Fixed at 6266ef0381aad99f5144a0b4d6cfe94465e4e6c3: my eight production transcript/display cases now all pass, including pending and failed initial identity lookups. Chat keeps ordinary assistant output hidden while identity is unknown, and the final-answer fallback was removed. The current repository transcript suite also passes all eleven tests. I reported a separate late-identity subscription recovery issue in the new review. Host/signal/stream/retry scheduling is controlled; native rendering and real Host I/O were not executed.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
Drop the final-answer fallback: only SendUser publishes assistant content, so a deliberately silent Turn no longer shows its internal conclusion as a message. A real failure still shows as a failure row, which the Bot's filter does not hide. This removes the per-Turn confirmation machinery added for the fallback, leaving ordered reading and per-Turn grouping. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # plugins/agent/native/src/owner.rs # products/maka/workbench/app/WorkspaceBrowser.tsx
…sion Chat decided whether to hide the model's own text from a boolean that was false until the identity lookup answered, so a pending or failed lookup published the Bot's internal output as an ordinary reply. Identity is now unknown, bot or ordinary: a failed lookup is retried and never decides ordinary, and Chat treats unknown as the Bot's rules. The application's already known Bot Session is passed in, so ordinary conversations do not wait for the lookup. Generated-by: Claude Code (Claude Opus 5.5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks all. Pushed in 6266ef0; the branch merges cleanly with the current Final-answer fallback: removed (@xxhZs, @M4n5ter). Only @Astro-Han, identity lookup (P2): fixed. You were right that a pending or failed
I'd still do this as a follow-up PR, after this one. Open point I have not verified: with no timer, what bounds a Turn that never calls Session tools and WorkHub (@M4n5ter). Your argument convinces me and I'm withdrawing my "narrow read-only plus message-your-own-tasks" proposal:
Three things I'd like to settle before building it, as a follow-up and not in this PR:
Also unsure: how plugin-contributed tools would reach the Agent's own session API without a wrapper plugin or a circular import. I'd like to understand that first. Not in this PR: the loop, the session tools, IM channels, memory. This PR stays at: Bot identity, 简体中文谢谢各位。已推送到 6266ef0,并且可以和当前的 最终回答兜底:已去掉(@xxhZs、@M4n5ter)。现在 Bot 的 Chat 里只有 @Astro-Han,身份查询(P2):已修复。 你说得对:
我仍然打算作为本 PR 之后的后续 PR 来做。我没有验证的一点:没有定时器后,一轮如果一直不调用 Session 工具与 WorkHub(@M4n5ter)。 你的论点说服了我,我撤回“只读加给自己的任务补话”那个窄方案:
动手之前我想先定下三件事,作为后续 PR,而不是放进这个 PR:
另外我还不确定:通过插件贡献的工具,如何在不引入包装插件或循环依赖的前提下,调用 Agent 自己的会话接口。我想先弄清楚这一点。 不在本 PR 中:持续循环、Session 工具、IM 渠道、记忆。本 PR 保持在:Bot 身份、 |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I reviewed the increment from 7bd0df4b64ad53b282ad3df571267e5485f68767 to 6266ef0381aad99f5144a0b4d6cfe94465e4e6c3: the two Bot revisions, the tool-selection/sidebar merge resolutions and their affected call sites, the three-state transcript core and application identity wiring, Conversation display, and the rewritten transcript tests. Incoming target-branch changes were separated from these Bot changes.
The earlier initial-identity P2 is fixed. All eight scenarios in my production-code display/transcript probe now pass, including held and rejected identity queries. The final-answer fallback and its per-Turn confirmation machinery are gone, so an intentionally silent Bot Turn keeps ordinary assistant text out of Chat. All eleven current repository transcript tests also pass.
One P2 remains in recovery after identity retries are exhausted: a later application answer changes the visible identity to Bot but never starts the outbox subscription. The inline contains the reproduced sequence. Its three-case production-core probe has two passing controls and one failed recovery assertion; recreating the core with the same now-healthy Host immediately reads the same recorded SendUser reply. Host, signal/stream and retry-wait scheduling are controlled. These checks do not execute native rendering or real Host/network I/O, and they do not measure real outage timing.
Six affected TS/TSX/MJS files parse with the available Biome 2.5.9, and git diff --check passes; this is not a complete type check. An actual static merge with current rust-preview 75bd51cfb654a9ad695849dc42e96f6bf65d8ca9 is clean, superseding the earlier two-conflict result. This exact head has no CI checks. Rust compilation, native identity/concurrency/recovery, real model/tool execution and desktop/terminal acceptance remain unverified here.
The opening PR summary still describes the removed final-answer fallback; please update that paragraph. The author's latest comment corrects the proposed event-driven Sleep and Session-tool direction, but explicitly leaves those implementations for later; I have not treated that unilateral scheduling choice as maintainer agreement or independently verified interaction parity.
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
|
|
||
| return { | ||
| started, | ||
| identity: (): BotIdentity => (identity === 'unknown' ? fromKnown() : identity), |
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
[P2] Start the outbox watch when a late application answer identifies the Bot
If all five initial identity queries fail, identify() returns false and the worker exits before host.subscribe. The application can answer later through known()—a case explicitly covered by the new final repository test—but these getters only change the returned identity. Nothing restarts the worker. The Conversation now treats the Session as Bot and hides ordinary assistant output, while forTurn() remains empty and recorded or later SendUser replies never appear until the transcript is recreated.
I reproduced this using the unchanged production core: fail the five head reads, exhaust its four retry waits, then make the Host healthy and supply the known Bot Session. Identity becomes bot, but the subscription count stays zero and the recorded Turn reply stays absent. A fresh core using that same healthy Host and known identity subscribes once and reads the reply; a late ordinary identity correctly needs no subscription. Retry waiting and Host/application responses are controlled, without real network or native rendering.
Make a late transition from unknown to Bot establish the watch/read lifecycle, including after retry exhaustion. The existing late-answer test should check reply delivery, not only identity().
Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.
EnglishI went through the remaining questions again. This PR can keep its current scope, with the continuous loop and WorkHub changes handled separately.
No duration, sleep floor/ceiling, daily wake budget or idle model polling is needed.
Personally, I am comfortable with the Bot directly deleting, archiving and restoring conversations, as well as messaging existing conversations. Session management is part of its job, so I do not think being a Bot alone justifies tighter restrictions. You can decide which operations should be preapproved and which should require confirmation based on the product experience. The existing
I implemented it in f301e9656. Declared imports also establish startup and lifecycle dependencies. Routing Agent → wrapper plugin → Agent therefore creates an initialization cycle. Tools now distinguish imported services from the owner's own exports. The Agent can project its declared Session service without importing itself; other plugins can still use an ordinary import. Calls continue through the existing Host authorization, mutation and receipt paths. The product configuration includes 14 Session tools, currently all
Session APIs already provide conversation creation, inspection, messaging and control, with mutation receipts handled by the Host. Dependency graphs, preset freezing, worker result delivery and waking the Bot still need concrete treatment during the consolidation. We should avoid building another restricted task interface alongside these APIs. Normal message delivery, approval waiting, restart recovery and lifecycle behavior should work correctly. Very narrow failure paths or deliberately unnatural usage do not all need new restrictions, abstractions or tests. 简体中文我重新梳理了一下剩下的问题。这个 PR 可以继续保持当前范围,持续循环和 WorkHub 的调整放到后续推进。
不需要时长、休眠上下限、每日唤醒预算或空闲模型轮询。
我个人对这部分权限持开放态度,能够接受 Bot 直接执行删除、归档和恢复,也不认为向已有会话发送消息需要默认确认。会话管理本身就是 Bot 的实际工作,不必因为它是 Bot 就额外收紧。 具体哪些操作预批准、哪些需要确认,你们结合产品体验决定即可。现有的
我已提交实现:f301e9656。 声明的 import 同时表达启动和生命周期依赖,所以通过 Agent → 包装插件 → Agent 的路径调用,会形成初始化环。现在工具目标显式区分导入服务和自身导出的服务:Agent 可以直接投影自己已声明的 Session 服务,无需导入自身;其他插件仍通过普通 import 使用该服务。 实际调用继续经过现有 Host 授权、mutation 和回执通路。产品配置已加入 14 项 Session 工具,目前统一为
会话创建、查询、补话和控制已经可以复用 Session API,mutation 回执继续由 Host 处理。依赖图、预设冻结、worker 结果回传和唤醒 Bot,则在后续收敛时具体承接,避免在这些 API 旁边再复制一套受限任务接口。 正常消息接收、审批等待、重启恢复和生命周期需要保证正确;触发路径极窄、需要刻意反常操作才能出现的问题,不必都转化成新的限制、抽象和测试。 |
Summary
Adds the Bot as a mode of the Agent, following the direction in #6076: one persistent Session per installation that keeps ordinary model output internal and reaches its user only through
SendUser. Capability tracking: #6413.Identity and tools
openBotreturns the installation's single Bot Session, creating it from a given workspace on first use. The registered record is the only source of Bot identity (Bot::is), loaded before activation; tool grants, recovery andSendUserall consult it, so forks and derived Sessions are ordinary Sessions.workhub_tasks, skills).SendUseris granted by identity, not by that list, and is always visible (with_always_visible), so tool discovery does not hide it or its rules. Messages admitted to the Bot Session carry standing instructions that say what it can and cannot do.Outbox
SendUsercall and per message, committed with the head in one CAS batch. Calls are never evicted, so a reusedmessageIdis always recognized: same content is a duplicate, different content is refused. Each message records the Turn that sent it. No limits on count, size or id length.botOutboxpages oldest first after a sequence cursor (limit: 0reads only the head);watchBotOutboxsignals head changes.Recovery
Workbench
openBotis a client shell control.SendUser(Markdown), after the Turn's header. The model's own replies and tool steps stay in Trajectory. If a finished Turn sent nothing withSendUser, its final answer is shown instead, so the user is never left without a reply. A "Working…" row covers the gap before the Turn has its own header.Not included, deliberately: the continuous loop (
tool_choice: requiredplus asleeptool) discussed in review, IM channels, attachments, memory, discovery, Matter.Verification
cargo test -p maka-agent-native --lib bot_tests: 12 pass, run in parallel twice after the latest merge. The recovery, identity, visibility and slow-creator cases each fail when the code they cover is reverted.cargo test -p maka-app-client --lib: 26 pass.maka-agent-nativebefore the last merge: 354 passed, 1 failed (terminal_live_pty_output_leaves_catalog_stable_but_resize_and_final_are_durabletimes out under--test-threads=4, passes alone). Not rerun in full after the last merge.cargo clippy --no-deps -p maka-agent-native -p maka-agent-api -p maka-app-client --tests -- -D warnings,cargo fmt --check,npm run schema:check,typecheckfor@maka-agent/plugin-sdkand@maka-agent/product, the product UI build and the ASF header check pass.maka desktop) by hand: opening the Bot from the sidebar, a reply throughSendUser, the working row and the completed header, and the Bot describing its real tools. Layout details and animations were only checked from screen recordings.LANG=en_US.UTF-8. Unrelated.Not run:
just checkas a whole. GitHub git fetches are too slow here, so thecua/mxcgit dependencies were built from source archives through local path overrides that are not part of this PR. The UI is not covered by any repository type check.Review focus
owner/queue.rs:allow_prior_unknownis widened only for the Bot Session.owner/bot.rs: identity, claim fencing and the outbox layout.Conversation.tsx: the Bot branch of the display projection.AI use
Tool(s) and scope: Claude Code (Claude Opus 5.5) wrote the implementation, tests and README text under my direction; the commits carry a
Generated-bytrailer.Checklist
🤖 Generated with Claude Code