Skip to content

feat(agent): add Bot mode with SendUser and a Host-owned outbox - #6464

Merged
M4n5ter merged 18 commits into
rust-previewfrom
feat/bot-mode
Oct 30, 2026
Merged

M4n5ter merged 18 commits into
rust-previewfrom
feat/bot-mode

Conversation

@liugddx

@liugddx liugddx commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

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

  • openBot returns 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 and SendUser all consult it, so forks and derived Sessions are ordinary Sessions.
  • The Bot Session is created with agent-native coordination tools only (scheduled tasks, goals, recall, checklist, workhub_tasks, skills). SendUser is 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.
  • Concurrent or retried first opens converge on one Session: the record is claimed with CAS before the Session is created, a live creator keeps its claim however long creation takes, and only a claim with no creator left may be taken over.

Outbox

  • One record per SendUser call and per message, committed with the head in one CAS batch. Calls are never evicted, so a reused messageId is 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.
  • botOutbox pages oldest first after a sequence cursor (limit: 0 reads only the head); watchBotOutbox signals head changes.

Recovery

  • Builds on a708280: queued wakes of the Bot Session, including scheduled and WorkHub-origin messages, continue after an unknown tool outcome (the model is told, the tool is not replayed). Ordinary Sessions keep the stricter rule.

Workbench

  • A fixed Maka Bot entry below New conversation (opens an existing Bot directly, creates it from the current conversation's workspace the first time); the Bot Session is not listed among ordinary conversations. openBot is a client shell control.
  • In the Bot Session, Chat shows the user's messages and, for each Turn, what the Bot sent with SendUser (Markdown), after the Turn's header. The model's own replies and tool steps stay in Trajectory. If a finished Turn sent nothing with SendUser, 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: required plus a sleep tool) 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.
  • Full maka-agent-native before the last merge: 354 passed, 1 failed (terminal_live_pty_output_leaves_catalog_stable_but_resize_and_final_are_durable times 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, typecheck for @maka-agent/plugin-sdk and @maka-agent/product, the product UI build and the ASF header check pass.
  • Run on macOS (maka desktop) by hand: opening the Bot from the sidebar, a reply through SendUser, the working row and the completed header, and the Bot describing its real tools. Layout details and animations were only checked from screen recordings.
  • With a Chinese system locale about 20 existing tests that assert English prompt text fail; they pass with LANG=en_US.UTF-8. Unrelated.

Not run: just check as a whole. GitHub git fetches are too slow here, so the cua / mxc git 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_unknown is widened only for the Bot Session.
  • owner/bot.rs: identity, claim fencing and the outbox layout.
  • Chat in Conversation.tsx: the Bot branch of the display projection.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

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-by trailer.

Checklist

  • Tests cover the change and fail without it

🤖 Generated with Claude Code

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>
@liugddx liugddx added pinned Exempt from automated lifecycle management and removed pinned Exempt from automated lifecycle management labels Oct 29, 2026
@liugddx
liugddx requested a review from xxhZs October 29, 2026 08:28
@liugddx

liugddx commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

@xxhZs could you review this too? You built the personal assistant Bot on main (#6004, #6124), and this PR brings its core to rust-preview as an Agent mode. I'd most like your view on whether the semantics match what you shipped:

  • SendUser: messageId idempotency (same content is a duplicate, different content is refused), up to 100 messages / 48,000 characters per call, and ordinary assistant text never reaching the user.
  • Outbox: kept apart from Run state, so a crash between "the model called SendUser" and "the message is shown" neither loses nor repeats it.
  • Single Session: one persistent Bot Session per installation. main uses a visible ledger plus a hidden worker Session; here internal output stays in the same Session, and only SendUser messages appear in the panel. Does that lose anything you relied on?
  • Not ported yet: Inbox batching, the Sleep / notNotified audit, Matter, discovery and memory, as tracked in [Tracking] Personal assistant / Always-on Bot capability inventory for the rust-preview switch #6413. Anything there you'd consider essential for a first version?
简体中文

@xxhZs 也麻烦你评审一下。main 上的个人助理 Bot(#6004、#6124)是你做的,这个 PR 把它的核心以 Agent 模式的形式带到 rust-preview。最想听你的意见:语义是否和你上线的版本一致。

  • SendUser:messageId 幂等(内容相同视为重复,不同则拒绝),每次最多 100 条、48000 字符,普通助手文本不会发给用户。
  • Outbox:与 Run 状态分开,所以“模型调用了 SendUser”和“消息已展示”之间发生崩溃时,既不丢也不重复。
  • 单一 Session:每台安装一个持久 Bot Session。main 用的是“可见账本 + 隐藏 worker Session”;这里内部输出留在同一个 Session 里,只有 SendUser 的消息显示在面板上。这样会丢掉你依赖的东西吗?
  • 尚未移植:Inbox 批量读取、Sleep / notNotified 审计、Matter、发现和记忆,跟踪在 [Tracking] Personal assistant / Always-on Bot capability inventory for the rust-preview switch #6413。其中有没有你认为第一版就必须要有的?

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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.

  2. 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 keeps SendUser and gets treated as a Bot during recovery, but cannot actually use SendUser. Opening a Bot from a Session with a restricted tool list can do the opposite: remove SendUser while still reporting success. We should have one place that decides whether a Session is the Bot.

  3. 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.

  4. 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.

  5. 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.

  6. 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 渠道、附件,以及对话视图和执行视图的进一步区分,也可以后面逐步补。

有几处我希望先处理一下:

  1. 消息和调度的这些限制可以先去掉。
    每次 100 条消息、48000 字符、200 字符 ID,以及 300 秒最小间隔,目前没看到具体依据。调度规则也不一致:interval 至少要五分钟,cron 却可以每分钟执行。

  2. Bot 身份这里需要理顺。
    无论最后选哪些工具,登记的 Session ID 和工具名单判断都应该得出一致结果。现在 fork 会保留 SendUser,恢复时被当成 Bot,真正发送时却被拒绝;从工具受限的 Session 打开 Bot,又可能移除 SendUser,但仍返回成功。我们需要统一由一处判断“这个 Session 是不是 Bot”。

  3. 并发首次打开时,会留下多余的 Session。
    两个请求都可能先创建 Session,再竞争唯一记录的 CAS。输掉的请求返回赢家,但自己创建的 Session 留下了。这条路径需要在并发、重试时都能收敛到一个 Bot Session。

  4. outbox 不应该把去重依据一起淘汰。
    一次调用落到 1000 次的保留窗口之外后,相同 ID、相同内容可以再次记录,不同内容复用这个 ID 也不再被拒绝。我倾向于继续用现有 Host 存储,按调用分别保存、按游标分页读取,也避免每次发送都全量读写整个 outbox。只返回最新 100 条,读取方没办法补读错过的消息。

  5. 普通会话不需要订阅 Bot 消息。
    现在普通会话也会订阅全局 outbox,再在组件内部过滤。可以只在 Bot Session 上挂载这个面板和订阅。

  6. 真正有用的检查还是保留。
    原子写入、去重、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>
@liugddx

liugddx commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

@M4n5ter thanks for the thorough review. All six points are addressed in e85edaa:

  1. Limits removed. No cap on messages per call, characters or messageId length (ids are hashed into storage keys), and no interval minimum; the cadence check and its test are gone. SendUser still requires a non-empty id, at least one message and non-empty text.
  2. One source of Bot identity. The registered record decides, through Bot::is(session), which is loaded before activation schedules queued work. Tool grants (selected_tools), recovery (allow_prior_unknown in queue.rs) and SendUser all use it. SendUser is no longer in the Bot's tool list; it is granted by identity, so a fork or derived Session never gets it, and a Bot opened from a Session with a restricted tool list still has it.
  3. One Session on concurrent or retried first opens. openBot claims the record with CAS before creating the Session, using the Session id derived from its own operation. A retry of the same operation resumes its own claim; a losing open waits for the winner's Session and returns it without creating one; a failed creation releases the claim.
  4. Outbox keeps every call. Same Host storage, now one record per call (keyed by a hash of messageId) and one per message, committed with the head in a single CAS batch. Nothing is evicted, so a reused id is always recognized, and a send writes only its own records. botOutbox pages oldest first after a sequence cursor (nextAfter), so readers can catch up.
  5. Only the Bot Session subscribes. Other conversations make a single botOutbox lookup and stop; the Bot Session subscribes to watchBotOutbox as a change signal and reads pages by cursor.
  6. Checks kept. Atomic writes, deduplication, invocation ownership and lifecycle checks are unchanged, including the check at preparation and again at execution.

New coverage: bot_identity_alone_grants_send_user (ordinary, derived and forked Sessions), bot_opened_from_a_restricted_session_still_speaks, concurrent_and_retried_first_opens_leave_one_bot_session (fails if the open creates before claiming), and outbox_keeps_every_call_and_pages_by_cursor (150 messages in one call, a refused id reuse, cursor pages). Full maka-agent-native: 354 passed, 1 failed: terminal_live_pty_output_leaves_catalog_stable_but_resize_and_final_are_durable times out under --test-threads=4 and passes alone, unrelated to this change.

I also fixed a bug of my own that only shows at runtime: the conversations UI module did not export the new bot command, so the desktop failed with "Plugin contribution does not match its declaration: bot". Neither the product build nor the typecheck catches that.

Separately, maka desktop does not build on macOS at current rust-preview (unrelated to this PR); the fix is in #6465.

简体中文

@M4n5ter 感谢细致的评审。六点都在 e85edaa 中处理了:

  1. 去掉限制。 不再限制每次调用的消息数、字符数和 messageId 长度(id 会哈希后作为存储键),也去掉了间隔下限,对应的检查和测试已删除。SendUser 仍要求 id 非空、至少一条消息、文本非空。
  2. Bot 身份只有一个来源。 由登记记录决定,经 Bot::is(session) 判断;它在激活调度排队工作之前加载。工具授予(selected_tools)、恢复(queue.rs 中的 allow_prior_unknown)和 SendUser 都用它。SendUser 不再写入 Bot 的工具列表,而是按身份授予,所以 fork 或派生的会话永远拿不到它;从工具受限的会话打开 Bot,也仍然有 SendUser。
  3. 并发或重试的首次打开只留一个会话。 openBot 先用 CAS 认领登记记录,再创建会话,认领用的是由本次操作派生的会话 id。同一操作重试会接着完成自己的认领;输掉的请求等待赢家的会话并返回它,自己不创建;创建失败会释放认领。
  4. Outbox 保留每一次调用。 仍用原来的 Host 存储,改为每次调用一条记录(键为 messageId 的哈希)、每条消息一条记录,与消息头在同一个 CAS 批次中提交。不做淘汰,所以复用的 id 总能被识别;每次发送只写自己的记录。botOutbox 按序号游标(nextAfter)从旧到新分页,读取方可以补读。
  5. 只有 Bot 会话订阅。 其他会话只做一次 botOutbox 查询就停止;Bot 会话把 watchBotOutbox 当作变更信号订阅,再按游标读取分页。
  6. 检查保留。 原子写入、去重、Invocation 归属和生命周期检查都不变,包括准备阶段和执行阶段各检查一次。

新增测试:bot_identity_alone_grants_send_user(普通、派生和 fork 的会话)、bot_opened_from_a_restricted_session_still_speaks、concurrent_and_retried_first_opens_leave_one_bot_session(若改回先创建后认领则失败),以及 outbox_keeps_every_call_and_pages_by_cursor(一次调用 150 条消息、拒绝复用 id、游标分页)。maka-agent-native 全量:354 通过,1 个失败:terminal_live_pty_output_leaves_catalog_stable_but_resize_and_final_are_durable 在 --test-threads=4 下超时,单独运行通过,与改动无关。

另外修了一个我自己引入、只在运行时出现的 bug:会话插件的 UI 模块没有导出新增的 bot 命令,桌面端报“插件扩展与声明不一致:bot”。产品构建和类型检查都发现不了这类问题。

此外,当前 rust-preview 在 macOS 上无法构建 maka desktop(与本 PR 无关),修复见 #6465。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread plugins/agent/native/src/owner/bot.rs Outdated
Ok(_) => {}
Err(error) if error == "agent_session_unavailable" => {
// Another open has claimed but not yet created its Session.
if waits < CLAIM_WAITS {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

liugddx and others added 2 commits October 29, 2026 18:53
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 Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

English

I have two more suggestions after going through the latest changes:

  1. 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 openBot flow. With that dedicated entry, the Bot Session should also be excluded from the regular session list, so it doesn't appear in two places.

  2. SendUser should probably be always visible to the Bot. Maka enables tool discovery by default, but the first model request only lists its name in the tool_search inventory. 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 existing with_always_visible mechanism so the model sees the tool and its instructions from the start? This can be handled independently of the broader tool-list discussion.

简体中文

结合最新改动重新看了一遍,我这里还有两点建议:

  1. Bot 的入口能不能参考 main,放在左上角侧边栏里?比如在 新建会话 下面放一个 Maka Bot,点击后复用已有的 openBot 流程。有了专门的入口,普通会话列表里也应该排除 Bot Session,避免同一个会话出现在两个地方。

  2. SendUser 应该让 Bot 始终可见。Maka 默认开启工具发现,但首次模型请求里只在 tool_search 的工具名单中列出了它的名字。告诉模型“必须通过它向用户发消息”的说明,也随着工具描述一起被隐藏了。我实际验证过,先搜索再调用是能走通的,但这个搜索是我在测试里主动安排的。这里能不能复用已有的 with_always_visible,让模型一开始就看到工具及其使用规则?这一点可以单独处理,不影响之后再讨论其他工具名单。

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 一下。

liugddx and others added 10 commits October 29, 2026 22:52
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 Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions github-actions Bot added the effort/XXL Over 2500 readable lines label Oct 30, 2026
# Conflicts:
#	plugins/agent/conversations/ui/SessionSurface.tsx
@liugddx

liugddx commented Oct 30, 2026 •

Copy link
Copy Markdown
Member Author

Update: the sleep and continuous-loop proposal in this comment is superseded. It had the model choose the sleep length, with a floor/ceiling and a daily wake budget, which departed from main without a reason. The corrected version (an event wait with no duration; no floor, ceiling or budget) is in #6464 (comment) (the final-answer fallback discussed here has also been removed).

更新: 本条评论里关于 sleep 与持续循环的提议已被取代。它让模型自选睡眠时长,并设上下限和每日唤醒预算,这没有理由地偏离了 main。更正后的版本(不带时长的事件等待,无上下限和预算)见 #6464 (comment) 。这里讨论的最终回答兜底也已去掉。

Thanks both. Everything below is pushed in 7bd0df4, and the branch merges cleanly with rust-preview.

@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 SendUser reply. Chat now shows a finished Turn's final answer only when that Turn is confirmed to have sent nothing. A confirmation is a read that reaches the outbox head observed after the Turn ended; a pending or failed read confirms nothing, so the model's own text stays hidden. The logic moved into botTranscriptCore.ts (no nsh import) so it can be unit tested, and bot-transcript.test.mjs covers a reply held behind a page read, a rejected read followed by recovery, a reply that lands after the Turn ended, an ordinary Session making one lookup and no subscription, and confirmations sharing reads. Reverting to "no messages means empty" fails the first two. The slow-creation P3 is also covered by the earlier commit (a_live_slow_creator_keeps_its_claim).

@M4n5ter, sidebar entry and SendUser visibility: both done.

  • A fixed Maka Bot entry sits 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 in the ordinary list.
  • SendUser is with_always_visible, so with tool discovery the first request carries its definition and rules. A test fails without it.

@M4n5ter, continuous loop (tool_choice: "required" + sleep): not in this PR; it changes how the Bot runs, so I'd do it as a follow-up once this one is in. Here is what I'd propose, after checking the engine:

  • sleep ends the Turn instead of keeping a task parked. The engine already has ToolSemantics::FinishTurn (yield_agent_graph uses it to end a supervisor Turn and resume on durable outcomes), so sleep can follow that shape: the model calls it, the Turn ends, and the next wake is registered with the scheduler. Nothing waits in memory, so a restart or crash loses nothing and a sleep of hours holds no Run. (I had earlier said the engine has no finish hook; that was incomplete.)
  • tool_choice: required where it works, without depending on it. The adapters currently hardcode Auto, and only the Anthropic protocol layer has Required, with a warning that some models ignore a forced choice. So: force a tool call on models that honour it, keep Auto on the rest, and treat a Turn that ends with plain text as a sleep of the default length. Correctness then does not hinge on the model obeying.
Question Proposal
Who sets the length? The model per call, within a Host floor and ceiling (say one minute to 24 hours)
Early wake? Yes: a new user message, a due schedule or a WorkHub result wakes the Bot; otherwise it is deaf while asleep. These entry points already exist
Cost bound? Three layers: a per-day wake budget; if several consecutive wakes did nothing, double the floor; no model call at all when nothing changed
required on every request? Yes, including the one after SendUser, giving a fixed rhythm of SendUser then sleep. The cost is one extra model call per Turn, since SendUser cannot run in parallel with other tools

Order: sleep + early wake + budget first (no adapter change); required second (needs the adapter change and a check per provider). Not verified yet: how a FinishTurn tool ending a Turn combines with the scheduler wake and with message intake in the Bot Session (I read the existing uses, I have not prototyped it), each provider's real behaviour under required, and a sensible daily budget. I'd settle those with a small spike before fixing details. Does this direction work for you?

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 中,并且分支可以和 rust-preview 干净合并。

@Astro-Han,P2(把"还没加载"当成"没发过消息"):已修复。你说得对:之前把本地 map 里没有记录当成了“这一轮没调用 SendUser”的证据。现在只有在确认这一轮没发消息时,Chat 才显示它的最终回答。确认的方式是:读取到这一轮结束之后观察到的 outbox 头;读取未完成或失败都不算确认,模型自己的文字继续隐藏。逻辑拆到了不依赖 nsh 的 botTranscriptCore.ts,因此可以做单元测试。bot-transcript.test.mjs 覆盖了:回复被卡在分页读取后面、读取被拒绝后恢复、回复在这一轮结束后才落库、普通会话只查一次且不订阅、以及并发确认共享读取。把逻辑改回“没有消息就算空”,前两条会失败。之前的慢速创建 P3 由上一个提交的 a_live_slow_creator_keeps_its_claim 覆盖。

@M4n5ter,侧边栏入口和 SendUser 可见性:都已完成。

  • “新建对话”下面有固定的 Maka Bot 入口:已存在就直接打开,第一次从当前对话的工作区创建;Bot 会话不再出现在普通列表里。
  • SendUser 使用了 with_always_visible,开启工具发现时,第一次请求就带有它的定义和规则。没有这项改动,对应测试会失败。

@M4n5ter,持续循环(tool_choice: "required" 加 sleep):这个 PR 里没有。它会改变 Bot 的运行方式,所以我想等这个 PR 合入后,作为后续 PR 来做。看过引擎之后,我的建议如下:

  • sleep 做成“结束本轮”的工具,而不是让任务一直挂着。 引擎里已有 ToolSemantics::FinishTurn(yield_agent_graph 就用它结束监督者这一轮,并在持久结果到达后恢复)。sleep 可以照这个形态:模型调用它,这一轮结束,下一次唤醒登记到调度器。没有任何东西在内存里等待,所以重启和崩溃都不丢,睡几个小时也不占用一个 Run。(我之前说引擎没有收尾钩子,那个说法不完整。)
  • tool_choice: required 能用就用,但不依赖它。 目前适配层写死了 Auto,只有 Anthropic 协议层有 Required,而且有“部分模型会忽略强制选择”的警告。所以:支持的模型强制必须调工具,其余保持 Auto,并把“模型直接写文字结束”当作默认时长的一次 sleep。这样正确性不取决于模型听不听话。
问题 建议
睡多久谁定 模型每次调用时指定,Host 设上下限(比如 1 分钟到 24 小时)
能否提前唤醒 能:新的用户消息、到点的定时任务或 WorkHub 结果都会唤醒 Bot,否则睡着时它是聋的。这些入口现在都已存在
成本封顶 三层:每日唤醒预算;连续几次醒来什么都没做就把下限翻倍;没有变化时完全不调用模型
每次请求都 required 吗 是,包括 SendUser 之后那一次,形成 SendUser 然后 sleep 的固定节奏。代价是每轮多一次模型调用,因为 SendUser 不能和其他工具并行

顺序:先做 sleep、提前唤醒和预算(不动适配层);required 放第二步(需要改适配层并逐家验证)。目前还没有验证的有:FinishTurn 工具结束一轮后,如何与调度器唤醒、以及 Bot 会话的消息接收衔接(我只读了现有用法,没有做原型);各家模型在 required 下的实际表现;每日预算的合理数值。我想在定细节之前,先做一个小的验证。这个方向你觉得可以吗?

另一件需要定的事(来自上面线程里的讨论): 目前很难跟进一个已经跑了几个小时的 worker。我建议增加一个只读的方式来查看运行中任务的进度(状态、已运行时长、最近几步),以及一个向 Bot 自己发起的任务补话的入口,而不是把整个 Session API 暴露出去,否则无人值守的 Bot 也会拥有删除和归档能力。这样划分合适吗?

@xxhZs

xxhZs commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

The reference for this port should be the user-visible interaction of the assistant on main. Reusing the Rust Agent's queue, scheduling and execution is fine; it does not require reproducing the dedicated Inbox implementation or the visible-session/hidden-worker split. A registered Bot identity, coordination-only tools, and a durable idempotent outbox fit that direction.

For the behavior already introduced here, please preserve these interaction semantics:

  1. Only SendUser publishes assistant content. The latest 7bd0df4b6 fix correctly distinguishes an unloaded outbox from a confirmed empty one, but the final-answer fallback still changes the contract. Even when the outbox is confirmed empty, ordinary model output should stay internal. Otherwise an intentional silent check can publish an internal conclusion such as “nothing to report.” A real execution failure should have an explicit status/error indication, rather than turning arbitrary final text into a user message.
  2. Sending and ending work are independent. A SendUser batch should appear promptly, in order, without waiting for the entire Turn to finish. The Bot can continue handling work and send further messages afterwards. The user-facing conversation should remain a chat, rather than requiring the user to follow execution steps; Trajectory can remain available for inspection.
  3. New input must remain usable during background work. Accept it durably while the Bot is running, and ensure it is subsequently handled without another user nudge. A running delegated task must not require the Bot to wait synchronously for it before handling the next user request. The task's results come back to the Bot for user-facing communication.
  4. Delegation must preserve the user's ongoing conversation. A correction or additional requirement for an existing task should reach that task rather than silently starting another one. Read-only task status plus messaging the Bot's own tasks is a sensible capability boundary; parity does not require exposing arbitrary session deletion/archive operations.

On the sleep questions in the latest comment: main's Bot sleep is an event wait, not a model-selected timer. It has no sleep duration. User input and background events wake it; an explicitly scheduled event can also arrive later. With nothing pending, it does not poll the model. If new input races with entering sleep, sleep is refused and the Bot receives the reason so it can handle that input. SendUser does not itself put it to sleep. Ordinary final text does not terminate main's Bot loop. The Rust implementation can enforce this through its own runtime mechanisms; tool_choice: required is an implementation choice, not the user-facing requirement. No new sleep floor/ceiling or daily wake budget is needed merely to reproduce that behavior.

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,这些方向都可以。

已经涉及的行为需要保持一致:

  • 只有 SendUser 才对外发消息。 最新提交修好了“未加载误认为没发送”,但确认没发送后仍展示 final answer,会把主动沉默变成对外发内部结论。建议去掉这个兜底;真正失败用明确的状态或错误反馈表达。
  • 发送与结束工作独立。 SendUser 消息及时、按顺序出现,不等整轮结束;发完还能继续处理。用户看到的是聊天,内部执行可在 Trajectory 查阅。
  • 后台工作不堵住交流。 运行中收到的新输入要可靠接收并继续处理,不要求用户再催一次;委派后也不应同步等待子任务结束才能接下一条用户消息。
  • 追加信息能接着原任务走。 只读查询任务状态、向 Bot 自己创建的任务补话,是合理边界,不需要因此开放任意会话的删除、归档权限。

另外,main 的 Sleep 没有时长参数,是等待事件,不是让模型选择睡多久。没消息时不调用模型;用户消息、后台结果等事件到来时唤醒。如果休眠时撞上新消息,拒绝休眠并告诉模型原因。SendUser 不代表休眠,普通文本也不能结束循环。实现可以不同,但不要把它变成一个定时轮询 Bot,也不必为了复现这个机制新增休眠上下限或每日唤醒预算。

这里不要求把尚未移植的模块全部塞进本 PR,只希望当前核心的聊天、发送、沉默和调度交互与 main 一致。建议用“边工作边发消息、静默结束、运行中追加纠正、用户消息与任务结果交错、断线重连不丢不重”验证使用层面的对齐。

@M4n5ter

M4n5ter commented Oct 30, 2026

Copy link
Copy Markdown
Member
English

Thanks, @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 confirmation: "required" can request approval before execution. Exposing an operation doesn’t require preapproving every call.

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 管理它。

删除等操作可以复用已有的授权和审批机制。工具配置为 confirmation: "required",就可以在执行前请求用户批准。开放操作不意味着每次调用都提前获得批准。

按我们目前的信任模型,我觉得不需要围绕“模型故意破坏 Maka”来设计这层 API 边界。我们把用户安装的插件视为已得到用户授权的代码。如果模型能够委派拥有足够文件和命令权限的 worker,甚至可能直接修改 Maka 的 SQLite 数据库。我更倾向于让正规 API 好用,并保留正常的一致性和生命周期检查。

这也值得结合 WorkHub 一起考虑。Session 工具可以覆盖不少普通的委派和沟通能力。我们可以考虑重构 WorkHub,甚至移除独立的这一层,把必要的协调能力融合进 Bot。结果可靠回传、唤醒、恢复和依赖关系等行为,仍然需要妥善处理。

我不是希望把整个重构都塞进这个 PR。只是,在增加另一套受限的 Bot 任务接口之前,能否先讨论一下这个方向?

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

liugddx and others added 3 commits October 30, 2026 16:42
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>
@liugddx

liugddx commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

Thanks all. Pushed in 6266ef0; the branch merges cleanly with the current rust-preview.

Final-answer fallback: removed (@xxhZs, @M4n5ter). Only SendUser publishes in the Bot's Chat now. A deliberately silent Turn no longer shows its conclusion as a message, and the per-Turn "confirmed empty" machinery I added for the fallback went with it. A real failure still shows as a failure row: the Bot's filter only hides ordinary assistant text, not failure rows.

@Astro-Han, identity lookup (P2): fixed. You were right that a pending or failed botOutbox({limit: 0}) left identity "false", which Chat read as an ordinary Session. Identity is now unknown, bot or ordinary. A failed lookup is retried and never decides "ordinary", and Chat treats unknown like the Bot (hide the model's text). The application already knows the Bot Session, so it is passed into each conversation and ordinary conversations do not wait for the lookup. New tests: pending lookup, failed-then-recovering lookup, always-failing lookup, someone else's Bot, an identity supplied by the application, and a late answer from the application. Putting back "the first failure means ordinary" fails three of them.

sleep and the continuous loop: correcting what I proposed. I suggested a model-chosen sleep length with a Host floor/ceiling and a daily wake budget. That departed from main without a reason, and @xxhZs's description is simpler and cheaper, so I'm dropping mine:

  • sleep takes no duration. It ends the Turn and waits for an event; with nothing pending the model is not called.
  • A user message, a WorkHub result or a due schedule wakes the Bot. If new input races with entering sleep, sleep is refused and the Bot is told why.
  • SendUser does not put the Bot to sleep, and plain final text does not end the loop.
  • tool_choice: required stays an implementation choice (the adapters hardcode Auto today and some models ignore a forced choice), not the user-facing rule. No sleep floor/ceiling or wake budget.

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 sleep (a step limit?).

Session tools and WorkHub (@M4n5ter). Your argument convinces me and I'm withdrawing my "narrow read-only plus message-your-own-tasks" proposal:

  • Managing sessions is a real Bot job, and who created a session does not say whether the user wants the Bot to manage it. Messaging an existing session is already possible through workhub_tasks (Existing target), so exposing it adds no new kind of power.
  • Destructive operations belong behind confirmation: "required" rather than being left out.

Three things I'd like to settle before building it, as a follow-up and not in this PR:

  1. Tiers: reads open; rename/categorize pre-approved; delete and archive require confirmation; sending to an existing session starts as "required" and is relaxed once it proves safe.
  2. Waiting for approval while unattended: a confirmation request should park until the user returns, not fail or auto-approve. The native_approval_park work suggests the mechanism exists, but I have not checked that it works for a Bot's scheduled wakes.
  3. WorkHub: before folding it into the Bot, we would need to carry over what plain Session tools do not give: the dependency graph, preset freezing, idempotent receipts and, above all, a worker result waking the Bot.

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, SendUser and the outbox, recovery, the sidebar entry and the Chat view.

简体中文

谢谢各位。已推送到 6266ef0,并且可以和当前的 rust-preview 干净合并。

最终回答兜底:已去掉(@xxhZs、@M4n5ter)。现在 Bot 的 Chat 里只有 SendUser 发的内容。刻意沉默的一轮不会再把结论当作消息显示,为兜底加的“确认为空”机制也一并删除。真正的失败仍然以失败行显示:Bot 的过滤只隐藏普通助手文字,不隐藏失败行。

@Astro-Han,身份查询(P2):已修复。 你说得对:botOutbox({limit: 0}) 未返回或失败时,身份被当成“否”,Chat 就按普通会话处理。现在身份有 unknown(未知)、bot、ordinary 三态。查询失败会重试,且不会因此判为“普通”;Chat 对 unknown 按 Bot 的规则处理(隐藏模型文字)。应用层本来就知道 Bot 会话是哪个,所以会把它传给每个对话,普通对话不必等查询。新增测试:查询挂起、失败后恢复、一直失败、别人的 Bot、应用层已知的身份、应用层稍后才给出答案。把“第一次失败就算普通”改回去,其中三个会失败。

sleep 与持续循环:更正我之前的提议。 我之前建议由模型选择睡眠时长,Host 设上下限,再加每日唤醒预算。这没有理由地偏离了 main,@xxhZs 的描述更简单也更省,所以我放弃自己的方案:

  • sleep 不带时长,结束本轮并等待事件;没有待处理事件时不调用模型。
  • 用户消息、WorkHub 结果或到点的定时任务会唤醒 Bot。如果新输入恰好与进入休眠竞争,则拒绝休眠并告诉 Bot 原因。
  • SendUser 不会让 Bot 休眠,普通的最终文字也不会结束循环。
  • tool_choice: required 仍是实现层面的选择(目前适配层写死 Auto,部分模型会忽略强制选择),不是对用户可见的规则。不需要休眠上下限,也不需要唤醒预算。

我仍然打算作为本 PR 之后的后续 PR 来做。我没有验证的一点:没有定时器后,一轮如果一直不调用 sleep,用什么来兜底(比如步数上限)?

Session 工具与 WorkHub(@M4n5ter)。 你的论点说服了我,我撤回“只读加给自己的任务补话”那个窄方案:

  • 会话管理本身就是 Bot 的实际工作,会话是谁创建的并不能说明用户是否希望 Bot 管理它。向已有会话发消息,今天通过 workhub_tasks 的 Existing 目标就能做到,所以开放它并没有增加新的能力种类。
  • 破坏性操作应当放在 confirmation: "required" 之后,而不是不开放。

动手之前我想先定下三件事,作为后续 PR,而不是放进这个 PR:

  1. 分级:只读开放;改名和分类可预批准;删除和归档需要确认;向已有会话发消息先从“需要确认”开始,证明安全后再放宽。
  2. 无人值守时等待审批:确认请求应当挂起直到用户回来,而不是失败或自动通过。native_approval_park 的工作说明机制存在,但我没有验证它对 Bot 的定时唤醒是否有效。
  3. WorkHub:在把它并入 Bot 之前,需要承接纯 Session 工具提供不了的东西:依赖图、预设冻结、幂等回执,尤其是 worker 结果唤醒 Bot。

另外我还不确定:通过插件贡献的工具,如何在不引入包装插件或循环依赖的前提下,调用 Agent 自己的会话接口。我想先弄清楚这一点。

不在本 PR 中:持续循环、Session 工具、IM 渠道、记忆。本 PR 保持在:Bot 身份、SendUser 与发件箱、恢复、侧边栏入口和 Chat 视图。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@M4n5ter

M4n5ter commented Oct 30, 2026

Copy link
Copy Markdown
Member

@liugddx

English

I went through the remaining questions again. This PR can keep its current scope, with the continuous loop and WorkHub changes handled separately.

  1. sleep ends the current Turn and waits for an event.

No duration, sleep floor/ceiling, daily wake budget or idle model polling is needed. SendUser and finishing work remain independent; ordinary model text stays internal and does not end the loop. We should handle new input racing with sleep correctly, without introducing Bot-specific limits just because the model might keep working without calling sleep.

  1. Reuse the existing confirmation configuration; the defaults are your call.

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 preapproved / required tool configuration supports those choices; we do not need another Bot-specific permission hierarchy.

  1. The own-service projection mechanism is already on rust-preview.

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 preapproved; those defaults can be adjusted once the policy is decided. The Bot can select these tools in its follow-up integration. This commit does not implement the Bot loop or change WorkHub.

  1. WorkHub consolidation can proceed separately.

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 的调整放到后续推进。

  1. sleep 就是结束当前 Turn,等待新事件。

不需要时长、休眠上下限、每日唤醒预算或空闲模型轮询。SendUser 和结束工作彼此独立;普通模型文字继续留在内部,也不会结束循环。新输入与进入休眠之间的竞争需要正确处理,但不必为了“模型可能一直不调用 sleep”提前增加一套 Bot 专属限制。

  1. 审批复用现有工具配置,具体默认策略由你们决定。

我个人对这部分权限持开放态度,能够接受 Bot 直接执行删除、归档和恢复,也不认为向已有会话发送消息需要默认确认。会话管理本身就是 Bot 的实际工作,不必因为它是 Bot 就额外收紧。

具体哪些操作预批准、哪些需要确认,你们结合产品体验决定即可。现有的 preapproved / required 工具配置可以承接这些选择,无需另外设计一套 Bot 权限分级。

  1. 自身服务投影机制已经接入 rust-preview。

我已提交实现:f301e9656。

声明的 import 同时表达启动和生命周期依赖,所以通过 Agent → 包装插件 → Agent 的路径调用,会形成初始化环。现在工具目标显式区分导入服务和自身导出的服务:Agent 可以直接投影自己已声明的 Session 服务,无需导入自身;其他插件仍通过普通 import 使用该服务。

实际调用继续经过现有 Host 授权、mutation 和回执通路。产品配置已加入 14 项 Session 工具,目前统一为 preapproved,后续可以按最终确定的策略调整。Bot 后续接入时可以直接选用这些工具。这次提交没有实现 Bot 持续循环,也没有改动 WorkHub。

  1. WorkHub 的收敛可以单独推进。

会话创建、查询、补话和控制已经可以复用 Session API,mutation 回执继续由 Host 处理。依赖图、预设冻结、worker 结果回传和唤醒 Bot,则在后续收敛时具体承接,避免在这些 API 旁边再复制一套受限任务接口。

正常消息接收、审批等待、重启恢复和生命周期需要保证正确;触发路径极窄、需要刻意反常操作才能出现的问题,不必都转化成新的限制、抽象和测试。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/XXL Over 2500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants