Skip to content

feat(runtime-host): own bot conversation continuity - #2550

Open
me2seeks wants to merge 27 commits into
apache:mainfrom
me2seeks:feat/2325-bot-conversation-continuity
Open

feat(runtime-host): own bot conversation continuity#2550
me2seeks wants to merge 27 commits into
apache:mainfrom
me2seeks:feat/2325-bot-conversation-continuity

Conversation

@me2seeks

@me2seeks me2seeks commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • move opaque external-conversation binding ownership into Runtime Host operational SQLite, including Session creation, crash recovery, retirement cleanup, and backup/restore
  • reduce Desktop to the channel boundary: normalize conversation, source-event, and reply-target identities, then submit the stable source message through the canonical Host message path
  • recover exact redeliveries while the conversation binding remains live, including Desktop and Runtime Host reconnects, through the Host-selected historical Turn and transcript without running the model twice
  • fix Slack thread identity, DingTalk Stream delivery identity, and QQ direct-message reply routing while making the three identities explicit across bot adapters

Raw external conversation identifiers are converted to fixed-length SHA-256 lookup keys before persistence. This bounds/indexes the key and avoids storing the raw value, but it is not a confidentiality control for low-entropy platform identifiers. Release receipts and live bindings are bounded so an external channel cannot grow operational state without limit.

Outbound IM delivery remains at-least-once because platform send APIs do not share a transaction with Runtime Host state. A crash after a successful send can repeat the reply, but, while the conversation binding remains live, canonical message admission prevents a second model Turn.

Part of #2325

中文说明
  • 将不透明的外部会话绑定交给 Runtime Host 的 operational SQLite 管理,包括 Session 创建、崩溃恢复、退休清理和备份恢复
  • Desktop 只保留通道边界职责:分别规范化 conversation、source event 和 reply target,再通过 Host 的标准消息入口提交稳定的来源消息
  • 在 conversation binding 仍生效时(包括 Desktop 与 Runtime Host 重连),精确重投通过 Host 选定的历史 Turn 和 transcript 恢复,不会再次执行模型
  • 在所有 bot adapter 中显式拆分三类 identity,并修复 Slack thread、DingTalk Stream 消息 identity 与 QQ 私聊回复路由

外部 conversation identifier 会先哈希再持久化。实时绑定和 release receipt 都有容量上限,外部通道无法无限放大 operational state。

IM 出站投递仍是 at-least-once,因为平台发送 API 与 Runtime Host 状态之间没有共同事务。平台成功收到回复后若进程崩溃,回复仍可能重复发送,但在 conversation binding 仍生效时,标准消息 admission 会阻止第二次模型 Turn。

这是 #2325 的一部分。

Verification

  • npm --workspace @maka/core test (815 passed)
  • npm --workspace @maka/storage test (777 passed, 14 skipped)
  • npm --workspace @maka/runtime-host test (791 passed)
  • npm --workspace @maka/desktop test (1,130 passed)
  • Runtime bot suites, focused Runtime Host integration suites, Biome, and git diff --check

Review focus

  • the absent versus removed Session recovery boundary after a binding claim
  • bounded release receipts and archive/remove cleanup
  • historical Turn recovery and channel-specific identity mapping

Checklist

  • Tests cover successor-Host recovery, retirement, source redelivery, historical Turns, and channel routing
  • The affected package suites, formatting, and type builds pass locally

Does this PR entail a change in behavior?

  • Yes — Bot conversations survive Desktop and Runtime Host reconnects
  • No

Visual evidence

Sanitized deterministic review fixture (not a live workspace capture; no real account data). It shows the affected routing semantics: exact Slack thread/reply targeting, reconnect replay through Host proof without a duplicate Turn, and reset acknowledgement.

Sanitized bot conversation fixture showing thread targeting, reconnect replay, and reset

AI use

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

Tool(s) and scope: Maka authored the existing bot continuity implementation, tests, verification, and earlier review remediation. OpenAI Codex authored the replay-before-transient-admission remediation, the latest adapter identity hardening and regression tests, PR documentation updates, and the sanitized visual fixture.

Final squash trailers:

Generated-by: Maka
Generated-by: Codex

@me2seeks

me2seeks commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

CI hit the recap session-switch test in the CLI lane. This PR doesn’t touch that path, and the exact test passed 5/5 locally on the current head. I can’t rerun upstream workflows from the fork—could someone rerun the failed job when convenient?

@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks for taking on this continuity slice. Tracing the crash and reconnect windows here made the remaining ownership boundary much clearer to me.

I think this is a good point to pause and reshape the implementation around Runtime Host, rather than keep adding fixes to the Desktop path. Runtime Host now owns durable Session lifecycle and exact Turn admission, so I believe it should also own resolving, creating, and retiring an opaque external-conversation binding in operational SQLite. The bot layer would then only normalize three independent identities: conversation, source event, and reply target.

That shape would remove the separate JSON authority, keep backup and restore consistent, avoid the Desktop-side preparing / active reconciliation and manual retirement cleanup, and give historical Turn retries one canonical recovery path. It would also let us fix the Slack and DingTalk identity semantics at their channel boundaries instead of compensating downstream.

Since the PR has already grown substantially, I worry that fixing the individual findings one by one would leave us with a harder design to maintain. Would you be open to refactoring this around a unified Runtime Host operation and simplifying the Desktop bridge accordingly? I would be happy to review the reshaped version.

@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch from f502ad4 to f7b73cc Compare August 9, 2026 09:32
@me2seeks me2seeks changed the title feat(desktop): persist bot conversation continuity feat(runtime-host): own bot conversation continuity Aug 9, 2026
@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch 5 times, most recently from b32238d to 52b9f21 Compare August 12, 2026 14:43

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

Codex automated review

I reviewed exact head 52b9f2124ab79f4324a6f391690ec5eb059e5ef6 across remote operation authorization, external-conversation binding, Desktop admission/continuity, queue ownership, transcript recovery, source deduplication, and the individual bot bridges. I found one P1 permission-boundary bypass and two P2 continuity races; see the inline findings. The current test and test_workspaces checks are also failing.

This 46-file PR combines three independently reviewable owners: (1) Runtime Host external-conversation protocol/authority, (2) Desktop bot admission and continuity, and (3) channel-specific bridge identity/ACK behavior. I recommend splitting along those boundaries after fixing the Host-path gate, so each slice can obtain focused security and crash/recovery verification. I did not find a clearly low-value test block to delete.

Disclosure: This is an automated review performed by Codex using delegated adversarial review passes and a final evidence check. It has not been independently verified by Astro-Han or another human reviewer, does not constitute human approval, and does not represent the final judgment of a human reviewer.

Comment thread packages/runtime-host/src/protocol/operations.ts
Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts
Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts
@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch from 52b9f21 to eb43bb3 Compare August 13, 2026 04:28
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Problem solved

The PR moves bot conversation continuity into Runtime Host operational SQLite.

Runtime Host now owns:

  • External-conversation bindings.
  • Session resolution and creation.
  • Crash recovery.
  • Conversation release and retirement cleanup.
  • Backup and restore of continuity state.
  • Canonical Turn admission and historical Turn recovery.

Desktop now normalizes conversation, source-event, and reply-target identities. It submits stable source messages through the canonical Host path. Exact redeliveries recover existing Turns and transcripts without rerunning the model.

The PR also fixes Slack thread identity, DingTalk Stream delivery identity, and QQ direct-message reply routing. External conversation identifiers are hashed before persistence. Binding and release-receipt storage is bounded.

Source of truth

The PR extends the existing Runtime Host and SQLite operational path. It does not create a parallel JSON authority.

Desktop no longer maintains local conversation bindings, source-event deduplication, session limits, explore-mode preparation, or manual retirement reconciliation. Runtime Host owns binding lifecycle and recovery.

Solution size and complexity

The changes form one coherent solution. Ownership, protocol, storage, recovery, and channel identity normalization must change together.

The new coordinator and protocol surface is necessary to serialize reconciliation, prevent duplicate session creation, recover interrupted claims, reject caller-selected session IDs, release bindings idempotently, purge retired bindings, and recover historical Turns after redelivery.

The PR removes obsolete session creation, preparation, invalidation, and unavailable-session paths. Tests replace legacy lifecycle scenarios with coverage for stable routing, replay admission, crash recovery, release, and channel identity handling.

No safe deletion is evident beyond the obsolete code and fixtures already removed. Further deletion could weaken coverage for recovery, concurrency, bounded receipts, or channel-specific routing.

Validation

Coverage includes:

  • SQLite authority persistence, leases, hashing, limits, release receipts, and session purging.
  • Runtime Host reconciliation, release, crash recovery, migration, and retirement.
  • Protocol decoding, authorization, operation grants, and ID validation.
  • Replay-only admission, busy-session rejection, and canonical Turn recovery.
  • Source-event deduplication across process restart.
  • Slack, DingTalk, QQ, Discord, Telegram, Feishu, WeCom, and WeChat identity mapping.
  • Permission refusal, reset handling, reply routing, typing, and transcript recovery.

Reported verification included core, storage, Runtime Host, Desktop, bot, and integration suites, plus formatting and type checks. The reported CLI recap session-switch CI failure was unrelated and passed locally five times. Final required-check status remains unverified from direct evidence.

Complexity delta

  • Authorities: Removes the separate Desktop or JSON continuity authority. Adds one Runtime Host SQLite authority.
  • States: Removes Desktop preparing and active reconciliation states. Adds durable binding, release-receipt, claim-recovery, and retirement-recovery states.
  • Branches: Adds handling for archived sessions, busy Turns, stale bindings, capacity conflicts, persistence failures, and commit uncertainty. Removes legacy session-creation and unavailable-session branches.
  • Configuration: Adds Host coordinator dependencies and test-only ID generation. Removes local invalidation dependencies.
  • Public surface: Adds external-conversation operations, coordinator APIs, storage interfaces, and normalized bot event fields. Removes legacy session creation, preparation, invalidation, and unavailable-session APIs.
  • Test maintenance: Adds focused storage, protocol, coordinator, recovery, and channel tests. Removes obsolete lifecycle fixtures.

Total maintenance complexity likely decreases because continuity has one durable owner and fewer cross-process reconciliation paths. The added Host and storage complexity is justified by crash recovery, backup and restore, and idempotent delivery.

Review-relevant risks

  • BotMessageEvent changes from chatId and sourceMessageId to conversationId, sourceEventId, and replyTarget. Downstream integrations require independent human review.
  • Slack, DingTalk, and QQ routing behavior changes. User-visible threading and reply delivery require independent human review.
  • SQLite schema version 28 adds binding and release-receipt tables. Migration, backup, restore, and rollback behavior require independent human review.
  • External conversation identifiers are hashed before persistence. Storage confidentiality and identifier recovery require independent human review.
  • New remote-owner Host operations add authorization and ID-validation paths. Security changes require independent human review.
  • Outbound delivery remains at-least-once. Duplicate-delivery behavior requires independent human review.

The person performing the merge must review the final diff. A maintainer makes the final determination.

Walkthrough

The change standardizes bot event identity and reply routing, moves conversation-to-session ownership into Runtime Host, adds durable SQLite conversation bindings, and updates desktop turn execution and lifecycle recovery to use reconciliation APIs.

Changes

External conversation routing

Layer / File(s) Summary
Standardized bot event contract
packages/core/src/bot-events.ts, packages/runtime/src/bots/*
Bot events now use conversationId, sourceEventId, and structured replyTarget fields. Bridge mappings validate stable source IDs.
Persistent conversation authority
packages/storage/src/external-conversation-authority.ts, packages/storage/src/sqlite-session-metadata-schema.ts
SQLite stores conversation bindings and release receipts with lease validation, idempotent release, purging, bounded retention, and schema migration support.
Runtime Host reconciliation flow
packages/runtime-host/src/protocol/*, packages/runtime-host/src/server/*
Runtime Host adds reconciliation operations, serialized coordination, session probing and creation, replay admission, busy-turn rejection, composition wiring, and retirement recovery.
Desktop bot session execution
apps/desktop/src/main/bot-incoming-main.ts, apps/desktop/src/main/runtime-host-bot-session-adapter.ts
Desktop bot processing resolves and releases conversations through Host APIs, generates deterministic IDs, submits messages, handles permission refusals, and projects turn results.
Validation and fixtures
apps/desktop/src/main/__tests__/*, packages/runtime-host/src/__tests__/*, packages/runtime/src/bots/__tests__/*
Tests cover replay admission, reset release, Host reconciliation, turn recovery, protocol validation, storage persistence, bridge mapping, and retirement recovery.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🟠 High · up to 8e75a

This PR changes bot conversation identity and replay handling, but the current head can drop Telegram group or supergroup messages, generate fresh identities for blank source events and duplicate model Turns, return the wrong text for multi-message Turns, leave bindings after archive failure, and persist reversible low-entropy identifiers. These are concrete correctness, delivery, cleanup, and confidentiality risks that should be addressed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant BotBridge
  participant BotIncomingMain
  participant RuntimeHostClient
  participant HostExternalConversationCoordinator
  participant SessionCatalog
  participant MessageCoordinator
  BotBridge->>BotIncomingMain: deliver conversationId and sourceEventId
  BotIncomingMain->>RuntimeHostClient: reconcile conversation
  RuntimeHostClient->>HostExternalConversationCoordinator: external-conversation.reconcile
  HostExternalConversationCoordinator->>SessionCatalog: probe or create session
  HostExternalConversationCoordinator-->>RuntimeHostClient: resolved session
  BotIncomingMain->>RuntimeHostClient: submit message
  RuntimeHostClient->>MessageCoordinator: turn.message.submit
  MessageCoordinator-->>BotIncomingMain: turn disposition and state
  BotIncomingMain->>BotBridge: send reply through replyTarget
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Ai Use Disclosure ⚠️ Warning The description checks “Generative tooling was used...” but selects neither required substantive-contribution declaration; 25 introduced commits have valid Maka or Codex trailers. Select “Generative tooling made a substantive contribution,” retain the named tool and scope, and ensure every Generated-by trailer survives squash or amend; see CONTRIBUTING.md §Human ownership and AI attribution.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary change: Runtime Host now owns bot conversation continuity.
Description check ✅ Passed The description covers the required summary, verification, AI use, behavior change, checklist, review focus, and supporting evidence.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/2325-bot-conversation-continuity
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
packages/storage/src/sqlite-session-metadata-schema.ts (1)

926-927: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Delete the redundant session index.

session_id TEXT NOT NULL UNIQUE on line 922 already creates an implicit unique index on session_id. That index serves the DELETE FROM external_conversation_bindings WHERE session_id = ? lookup in purgeSession. external_conversation_bindings_by_session duplicates it and only adds write cost.

♻️ Proposed simplification
     );
 
-    CREATE INDEX external_conversation_bindings_by_session
-      ON external_conversation_bindings(session_id);
-
     CREATE TABLE external_conversation_release_receipts (

As per path instructions: "Flag concrete cases where code can be deleted or simplified."

Source: Path instructions

packages/storage/src/__tests__/sqlite-session-metadata-store.test.ts (1)

230-230: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the literal expected schema version.

The assertion now compares the migrated store against the same constant the migration targets. It passes for every future version bump, so it no longer detects a version bump that ships without its migration. Use the literal version this test intends to reach.

💚 Proposed fix
-        assert.equal(migrated.schemaVersion(), SQLITE_SESSION_METADATA_SCHEMA_VERSION);
+        assert.equal(migrated.schemaVersion(), 26);

As per path instructions: "Flag tests that duplicate existing coverage, assert implementation details, or do not protect observable behavior."

Source: Path instructions

packages/core/src/bot-events.ts (1)

166-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove duplicated command matching.

Both functions implement the same group guard and text normalization. Put this logic in one private helper so reset and help cannot diverge.

Proposed simplification
+function isPlaintextBotCommand(
+  message: Pick<BotMessageEvent, 'text' | 'isGroup'>,
+  commands: ReadonlyArray<string>,
+): boolean {
+  if (message.isGroup) return false;
+  const normalized = message.text.normalize('NFC').trim().toLowerCase();
+  return normalized.length > 0 && commands.includes(normalized);
+}
+
 export function isPlaintextResetCommand(
   message: Pick<BotMessageEvent, 'text' | 'isGroup'>,
 ): boolean {
-  if (message.isGroup) return false;
-  const trimmed = message.text.normalize('NFC').trim().toLowerCase();
-  if (trimmed.length === 0) return false;
-  return BOT_PLAINTEXT_RESET_COMMANDS.includes(trimmed);
+  return isPlaintextBotCommand(message, BOT_PLAINTEXT_RESET_COMMANDS);
 }

As per path instructions, "Flag concrete cases where code can be deleted or simplified."

Also applies to: 195-198

Source: Path instructions

packages/runtime/src/bots/__tests__/qq-bridge.test.ts (1)

118-135: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add an isGroup assertion for the direct-message event.

qqDirectMessageToEvent forces isGroup: false. That flag gates the DM-only plaintext reset command. The new case asserts conversation identity and send routing but not this flag, so a regression to isGroup: true would pass.

♻️ Proposed addition
     assert.equal(direct?.conversationId, 'dm:channel:dm-channel-1');
+    assert.equal(direct?.isGroup, false);
     assert.deepEqual(direct?.replyTarget, {
apps/desktop/src/main/runtime-host-bot-session-adapter.ts (1)

185-258: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

latestMessageId makes the completed text depend on delta arrival order.

The collector keeps per-message text in assistantText, but on completion at Line 243 it returns only the entry for latestMessageId. latestMessageId is the message of the last observed delta, not necessarily the final assistant message of the Turn. If the Turn emits deltas for two assistant messages and the last delta belongs to the earlier one, the reply text is the wrong message. The same variable also drives the publishedSnapshot stream, so a two-message Turn can publish alternating snapshots.

If a bot Turn can only ever produce one assistant message, then assistantText as a Map is unnecessary state and a single string is enough. Either constrain the shape or select the terminal message explicitly.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b51fb2e-6862-4377-8da0-19e6613dc280

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9ce0d and 498dbbc.

📒 Files selected for processing (53)
  • apps/desktop/src/main/__tests__/bot-incoming-project-cwd.test.ts
  • apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts
  • apps/desktop/src/main/__tests__/bot-incoming-typing.test.ts
  • apps/desktop/src/main/__tests__/bot-session-adapter-fixture.ts
  • apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-client.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-desktop-candidate.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-session-test-fixture.ts
  • apps/desktop/src/main/bot-incoming-main.ts
  • apps/desktop/src/main/bot-session-adapter.ts
  • apps/desktop/src/main/runtime-host-bot-session-adapter.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • apps/desktop/src/main/runtime-host-desktop-candidate.ts
  • packages/core/src/__tests__/bot-events.test.ts
  • packages/core/src/bot-events.ts
  • packages/runtime-host/src/__tests__/execution-composition.test.ts
  • packages/runtime-host/src/__tests__/external-conversation-coordinator.test.ts
  • packages/runtime-host/src/__tests__/external-conversation-protocol.test.ts
  • packages/runtime-host/src/__tests__/message-coordinator.test.ts
  • packages/runtime-host/src/__tests__/protocol.test.ts
  • packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts
  • packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts
  • packages/runtime-host/src/protocol/external-conversation.ts
  • packages/runtime-host/src/protocol/message.ts
  • packages/runtime-host/src/protocol/operations.ts
  • packages/runtime-host/src/server/execution-composition.ts
  • packages/runtime-host/src/server/external-conversation-coordinator.ts
  • packages/runtime-host/src/server/message-coordinator.ts
  • packages/runtime-host/src/server/operation-dispatcher.ts
  • packages/runtime-host/src/server/session-catalog-coordinator.ts
  • packages/runtime-host/src/server/session-retirement-coordinator.ts
  • packages/runtime/src/bots/__tests__/base-adapter.test.ts
  • packages/runtime/src/bots/__tests__/dingtalk-bridge.test.ts
  • packages/runtime/src/bots/__tests__/discord-bridge.test.ts
  • packages/runtime/src/bots/__tests__/im-channel-mapping.test.ts
  • packages/runtime/src/bots/__tests__/qq-bridge.test.ts
  • packages/runtime/src/bots/__tests__/slack-bridge.test.ts
  • packages/runtime/src/bots/__tests__/wechat-bridge.test.ts
  • packages/runtime/src/bots/dingtalk-bridge.ts
  • packages/runtime/src/bots/discord-bridge.ts
  • packages/runtime/src/bots/feishu-bridge.ts
  • packages/runtime/src/bots/qq-bridge.ts
  • packages/runtime/src/bots/slack-bridge.ts
  • packages/runtime/src/bots/telegram-bridge.ts
  • packages/runtime/src/bots/types.ts
  • packages/runtime/src/bots/wechat-bridge.ts
  • packages/runtime/src/bots/wecom-bridge.ts
  • packages/storage/package.json
  • packages/storage/src/__tests__/external-conversation-authority.test.ts
  • packages/storage/src/__tests__/session-store.test.ts
  • packages/storage/src/__tests__/sqlite-session-metadata-store.test.ts
  • packages/storage/src/external-conversation-authority.ts
  • packages/storage/src/sqlite-session-metadata-schema.ts

Included review availability: Your plan includes up to 3 reviews per rolling hour; 2 remain after this review.

Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts
Comment thread packages/runtime-host/src/server/session-retirement-coordinator.ts
Comment thread packages/runtime/src/bots/wechat-bridge.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.

Thanks for moving external conversation continuity into the Runtime Host. I reviewed this head with three independent @reviewer slices plus a read-only ollama-cloud/deepseek-v4-flash:high pass. The core direction is right: Host storage owns the durable binding, stable source IDs own redelivery, and archive cleanup uses the binding row as a retry marker.

The remaining failures come from not distinguishing three durable states: a claimed Session ID whose Session creation has not committed yet, a live binding, and a binding whose Session is archived/removed and awaits purge. Treating all missing Sessions as stale breaks crash-window idempotency; keeping deterministic create failures forever leaks capacity; and a Desktop pre-check bypasses the Host's existing durable message proof on exact redelivery.

The first-principles fix is an explicit binding/claim lifecycle owned by the storage/Host coordinator, plus one message-admission authority in Host. Preserve unknown-commit claims, release deterministic create failures, purge only archived/removed generations, and let submitMessage return the original turn_started proof before applying busy semantics. Recovery should isolate failures per Session.

This head also predates current main's isArchived-only lifecycle and schema 27. Rebase first, remove the status === 'archived' dependency, and add the binding tables in a monotonic schema 28 migration. Current CI is green but does not test v27 upgrade, crash-window recovery, or exact running-turn redelivery.

No local test suite was run during this review; conclusions are based on source, test, current-main, and CI inspection. Codex coordinated the passes and performed the final adjudication; external-model output was treated as unverified until checked against the code.

中文摘要

感谢把外部会话连续性收敛到 Runtime Host。Host storage 拥有 durable binding、稳定 source ID 负责 redelivery、binding row 作为 archive cleanup retry marker,方向正确。

当前缺口是没有区分三种持久状态:Session 尚未提交的 claim、live binding、以及 Session 已 archived/removed 且等待 purge 的 binding。把所有缺失 Session 都当 stale 会破坏 crash-window 幂等;永久保留确定性 create failure 又会泄漏容量;Desktop 预检查还绕过了 Host 已有的 durable message proof。

最小方案是由 storage/Host coordinator 明确管理 claim lifecycle,并只保留 Host 一处 message admission:保留 unknown-commit claim、释放确定性失败、只 purge archived/removed generation;submitMessage 先返回原 turn_started proof,再应用 busy 语义;recovery 按 Session 隔离失败。

该 head 还早于 current main 的 isArchived-only lifecycle 和 schema 27。请先 rebase,删除 status === 'archived' 依赖,并用单调 schema 28 migration 新增表。当前 CI 全绿,但未覆盖 v27 upgrade、crash-window recovery 和 running-turn 精确重投递。

本次未运行本地测试套件;外部模型输出在核对代码前均视为未验证输入。

Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts Outdated
Comment thread packages/runtime-host/src/server/session-retirement-coordinator.ts Outdated
Comment thread packages/runtime-host/src/server/session-retirement-coordinator.ts Outdated
Comment thread packages/storage/src/sqlite-session-metadata-schema.ts
@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch from 547f2c4 to 261cc8e Compare August 18, 2026 15:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime-host/src/server/session-retirement-coordinator.ts (1)

374-389: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Retire external conversations after a Session removal commit.

The removal path commits the Session deletion but never calls #retireExternalConversations. The binding can then reference a tombstoned Session until a later Host recovery. It also continues to consume binding capacity.

Call the existing callback with allSessionIds after the lifecycle commits and before publication. Add a removal test that verifies the bindings are deleted.

Disposition: fix-now.

Proposed fix
           removeHandles.scheduledTasks.commit();
           archiveHandles?.goal.commit();
           archiveHandles?.scheduledTasks.commit();
+          await this.#retireExternalConversations(allSessionIds);
           await this.#graphWake.retireSessions(allSessionIds);

As per path instructions, reuse the closest existing seam and make the smallest local correction.

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c3657ee1-7e8d-4795-aeb6-b2821ffdf4a4

📥 Commits

Reviewing files that changed from the base of the PR and between 498dbbc and 261cc8e.

📒 Files selected for processing (22)
  • apps/desktop/src/main/__tests__/bot-incoming-typing.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-desktop-candidate.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-session-test-fixture.ts
  • apps/desktop/src/main/runtime-host-bot-session-adapter.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • apps/desktop/src/main/runtime-host-desktop-candidate.ts
  • packages/runtime-host/src/__tests__/execution-composition.test.ts
  • packages/runtime-host/src/__tests__/external-conversation-coordinator.test.ts
  • packages/runtime-host/src/__tests__/protocol.test.ts
  • packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts
  • packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts
  • packages/runtime-host/src/server/execution-composition.ts
  • packages/runtime-host/src/server/external-conversation-coordinator.ts
  • packages/runtime-host/src/server/session-catalog-coordinator.ts
  • packages/runtime-host/src/server/session-retirement-coordinator.ts
  • packages/runtime/src/bots/__tests__/wechat-bridge.test.ts
  • packages/runtime/src/bots/wechat-bridge.ts
  • packages/storage/src/__tests__/session-store.test.ts
  • packages/storage/src/__tests__/sqlite-session-metadata-store.test.ts
  • packages/storage/src/external-conversation-authority.ts
  • packages/storage/src/sqlite-session-metadata-schema.ts
🚧 Files skipped from review as they are similar to previous changes (16)
  • packages/storage/src/tests/session-store.test.ts
  • packages/runtime-host/src/tests/protocol.test.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • packages/runtime-host/src/tests/session-catalog-coordinator.test.ts
  • packages/runtime/src/bots/tests/wechat-bridge.test.ts
  • packages/runtime-host/src/server/session-catalog-coordinator.ts
  • packages/runtime-host/src/tests/external-conversation-coordinator.test.ts
  • packages/runtime-host/src/server/external-conversation-coordinator.ts
  • apps/desktop/src/main/runtime-host-desktop-candidate.ts
  • apps/desktop/src/main/tests/bot-incoming-typing.test.ts
  • packages/runtime-host/src/server/execution-composition.ts
  • apps/desktop/src/main/tests/runtime-host-session-test-fixture.ts
  • apps/desktop/src/main/tests/runtime-host-desktop-candidate.test.ts
  • apps/desktop/src/main/tests/runtime-host-bot-session-adapter.test.ts
  • apps/desktop/src/main/runtime-host-bot-session-adapter.ts
  • packages/storage/src/external-conversation-authority.ts

Included review availability: Your plan includes up to 3 reviews per rolling hour; 0 remain after this review.

Comment thread packages/runtime-host/src/__tests__/execution-composition.test.ts Outdated

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

Moving external conversations onto Runtime Host-owned Session and Turn authority is the correct architectural direction, and deterministic Host operation IDs are the right idempotency seam. Four edge cases currently break that end-to-end contract. In particular, Desktop should not add an earlier volatile dedup authority, and a subscriber must either receive a reset/prefix or initialize from the durable transcript before accepting non-zero offsets.

AI-assisted review disclosure: Codex verified these findings against the current head and traced bot ingress, Host admission, subscription framing, transcript projection, terminal outcome selection, and binding persistence. Two independent reviewer-agent passes and an OpenCode Go DeepSeek V4 Flash (high) adversarial pass were used as inputs. No local tests were run.

中文复核

把外部会话收敛到 Runtime Host 管理的 Session/Turn 权威是正确方向,稳定 operation ID 也是合适的幂等边界。但当前有四个会破坏端到端契约的边缘路径:Desktop 在持久 admission 之前又建立了一层易失去重权威;重连订阅不能消费非零 offset;终态文本可能取到旧消息;创建失败会遗留占用容量的 binding。应优先删除并行去重状态,让订阅和持久绑定生命周期由 Host 契约闭合。

本次为 AI 辅助审查:Codex 在最新 head 上追踪 bot ingress、Host admission、subscription、transcript、终态投影与 binding 持久化;另使用两次独立 reviewer 及一次 OpenCode Go DeepSeek V4 Flash(high)对抗审查。未运行本地测试。

Comment thread apps/desktop/src/main/bot-incoming-main.ts Outdated
Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts
Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts Outdated
@Astro-Han

Copy link
Copy Markdown
Contributor

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Removal leaves archived bindings ✓ Resolved 🐞 Bug ☼ Reliability
Description
The PR purges external-conversation bindings for direct archive operations, but session.remove can
archive additional plan.archive sessions and only schedules cleanup for the actually removed IDs.
Those archive-only sessions retain durable bindings and consume the 500-binding capacity until a
later host recovery or conversation access cleans them up.
Code

packages/runtime-host/src/server/session-retirement-coordinator.ts[311]

+          await this.#retireExternalConversations(family.sessionIds);
Relevance

●●● Strong

Archived records retaining bounded durable bindings is a concrete lifecycle cleanup leak, matching
the PR’s stated cleanup focus.

PR-#3151
PR-#3079
PR-#3168

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Removal planning puts non-archived child sessions outside the removal family into
archiveSessionIds; the remove transaction archives those records, but the coordinator queues
sidecar cleanup only for IDs returned as deleted. Direct lifecycle archive calls the new external
binding purge, and startup recovery confirms archived bindings otherwise remain pending until
recovery.

packages/runtime-host/src/server/session-retirement-coordinator.ts[300-320]
packages/runtime-host/src/server/session-retirement-coordinator.ts[365-392]
packages/runtime-host/src/server/session-retirement-coordinator.ts[533-567]
packages/runtime-host/src/server/session-retirement-coordinator.ts[672-712]
packages/runtime-host/src/server/session-retirement-coordinator.ts[218-241]
packages/storage/src/sqlite-session-metadata-store.ts[3356-3373]
packages/storage/src/sqlite-session-metadata-store.ts[3403-3431]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Sessions archived as part of `session.remove` do not receive the external-conversation purge that direct archive operations now perform.

## Issue Context
Consolidate archive cleanup authority by reusing `retireExternalConversations` for `plan.archive.sessionIds` after the removal transaction commits. The existing committed/uncertain-outcome handling is sufficient, so no new state, branch type, configuration, public surface, or test-maintenance authority is required beyond covering this removal-plan case.

## Fix Focus Areas
- packages/runtime-host/src/server/session-retirement-coordinator.ts[365-392]
- packages/runtime-host/src/server/session-retirement-coordinator.ts[300-320]
- packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts[471-583]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Reset loses redelivery provenance ✗ Dismissed 🐞 Bug ≡ Correctness
Description
A DM reset deletes the only conversation-to-Session binding before later retries of earlier source
events can be routed to their historical Session. After the Desktop duplicate TTL expires or it
reconnects, a delayed redelivery of a pre-reset event resolves a new Session and submits the same
source-derived message ID there, so it can run the model a second time.
Code

apps/desktop/src/main/bot-incoming-main.ts[R232-235]

+      const had = await deps.sessions.releaseConversation({
+        conversationId: conversationKey,
+        operationId: botSourceOperationId(message, newId),
+      });
Relevance

●●● Strong

Durable provenance loss causing cross-Session duplicate execution is a clear correctness bug aligned
with continuity goals.

PR-#1755
PR-#2523
PR-#3048

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed reset path removes the binding, and the only Desktop duplicate protection is
intentionally non-durable. Host message admission explicitly keys pending submissions and durable
receipts by Session ID plus message ID, so the deterministic source-derived ID cannot deduplicate
once the retry is resolved to a replacement Session.

apps/desktop/src/main/bot-incoming-main.ts[232-235]
apps/desktop/src/main/bot-incoming-main.ts[391-397]
apps/desktop/src/main/bot-incoming-main.ts[40-45]
packages/storage/src/external-conversation-authority.ts[204-240]
packages/runtime-host/src/server/message-coordinator.ts[485-525]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A reset releases the external-conversation binding, so a delayed retry of a message admitted before the reset can no longer reach its historical Session and is admitted as a new Turn in the next Session.

## Issue Context
The in-memory duplicate map is explicitly TTL-bounded and is cleared on Desktop close, while Host message receipts/proofs are keyed by `(sessionId, messageId)`. Deleting the binding therefore removes the only route from the stable external source ID to the Session holding its durable proof.

Deletion is insufficient because deletion is the reset behavior that must remain. Reusing the existing per-Session message receipt alone is insufficient because resolution has already selected a different Session. Add only bounded operational provenance (or an equivalent bounded historical lookup) for source IDs admitted before a release, retained for the platform retry horizon, so a retry projects its historical Turn without restoring the old conversation as the destination for new messages. This adds bounded state and expiry/test-maintenance burden, which is necessary to preserve both reset semantics and exact redelivery.

## Fix Focus Areas
- apps/desktop/src/main/bot-incoming-main.ts[232-235]
- packages/storage/src/external-conversation-authority.ts[204-272]
- packages/runtime-host/src/server/external-conversation-coordinator.ts[70-139]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Ignored delta stalls subscription ✓ Resolved 🐞 Bug ≡ Correctness
Description
collectRuntimeHostBotTurn changed from for await to a manually advanced iterator, but its
existing continue skips the only iterator.next() call; any valid thinking delta or delta for
another Turn causes an infinite loop on the same frame. The bot request then never observes the
terminal projection, never sends the final reply, and can busy-spin a CPU core.
Code

apps/desktop/src/main/runtime-host-bot-session-adapter.ts[R210-212]

+  let next = await firstFrame;
+  while (!next.done) {
+    const frame = next.value;
Relevance

●●● Strong

An early-continue iterator bug causing an infinite loop is deterministic and directly violates
normal stream processing.

PR-#3048
PR-#3079
PR-#2674

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The collector advances next only at the bottom of the loop, while line 217 continues early for a
different Turn or any non-text delta. The wire contract explicitly permits both text and
thinking session deltas, so this branch is reachable in normal operation.

apps/desktop/src/main/runtime-host-bot-session-adapter.ts[210-252]
packages/runtime-host/src/protocol/session-continuity.ts[113-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The manual subscription loop reprocesses an ignored delta forever because `continue` bypasses iterator advancement.

## Issue Context
`SessionAssistantDelta.kind` legitimately includes `thinking`, and the subscription can also contain deltas whose Turn does not match the submitted Turn. Reuse the closest iteration seam by ensuring every non-terminal branch advances exactly once; no new state or public surface is needed.

## Fix Focus Areas
- apps/desktop/src/main/runtime-host-bot-session-adapter.ts[210-251]
- packages/runtime-host/src/protocol/session-continuity.ts[113-127]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a high-density cross-cutting behavioral change spanning persistence, crash recovery, message admission, retirement/backup lifecycle, and multiple independent bot identity adapters, creating many plausible easy-to-miss defects.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/desktop/src/main/runtime-host-bot-session-adapter.ts
Comment thread packages/runtime-host/src/server/session-retirement-coordinator.ts
Comment thread apps/desktop/src/main/bot-incoming-main.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.

The Runtime Host-centered shape is now substantially cleaner: durable binding ownership, message admission, crash recovery, and retirement cleanup all converge on the existing lifecycle authority, while the channel layer only projects external identities.

I’m leaving COMMENT because two bounded-edge cases still violate the new idempotency contract: malformed channel payloads can enter without a stable source-event identity, and an evicted reset receipt lets a delayed old reset delete a newer binding. I left focused P2 comments at the responsible boundaries. The smallest coherent fix is to fail closed at adapters that cannot supply a stable source ID, and make release replay incapable of deleting a binding created after the original operation (for example by retaining receipts for an explicit retry horizon or binding the release to the expected generation/session).

I did not treat duplicate ephemeral help/non-text acknowledgements as a blocker: the PR explicitly documents outbound IM delivery as at-least-once, and those paths do not create model Turns.

AI-assisted review disclosure: two Codex reviewers reviewed exact head e2ad534; Codex then independently traced and adjudicated the reported idempotency paths against the current code and PR contract.

中文说明

当前实现已经把 durable binding、消息 admission、崩溃恢复和退休清理统一到 Runtime Host,整体方向正确。仍有两个 P2:缺少稳定 source event ID 的异常平台 payload 会绕过可靠幂等;旧 reset receipt 被淘汰后,迟到的重复 reset 可能删除后来新建的 binding。建议 adapter 在没有稳定 ID 时 fail closed,并让 release 与原 binding generation/session 绑定,或至少按明确的平台重试窗口保留 receipt。

help/非文本提示的重复发送没有列为阻塞,因为 PR 已明确出站 IM 是 at-least-once,而且这些提示不会创建模型 Turn。

Comment thread packages/runtime/src/bots/telegram-bridge.ts Outdated
Comment thread packages/storage/src/external-conversation-authority.ts Outdated
@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch from e2ad534 to 230f720 Compare August 19, 2026 10:48

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

Thanks for moving bot conversation identity, admission, replay, and reset receipts into the durable Host authority. The current head is green and the core binding/cleanup route is coherent, but one exact-retry P2 remains inline: the transient burst gate can reject a redelivered event before Host admission gets a chance to recover its durable result.

This PR also changes user-visible Slack threading, QQ direct-message routing, reset feedback, reconnect behavior, and recovery replies. Please add representative bot conversation screenshots for the affected surfaces, including at least threading/reply targeting and reset or reconnect recovery.

The PR body discloses Codex/Maka assistance, but the rendered section contains literal \n escapes and substantive commits 87104669 and 68b5898d lack Generated-by: Maka trailers. Please fix the body formatting and add those trailers, or explicitly confirm those commits were human-only.

Reviewed with Codex as an AI-assisted code review. I verified the exact-head diff, Host admission/replay order, binding cleanup, platform routing, CI, visual evidence, and commit provenance; no external model output was used.

中文说明

把 bot conversation、admission、replay 与 reset receipt 收到 Host 的方向正确,但当前 transient burst gate 在 Host admission 之前执行;重连后批量重投超过 8 条时,第 9 条 exact retry 进不了 durable proof/replay,可能永久丢回复。此 PR 也改变 Slack thread、QQ DM、reset/reconnect 等用户体验,请补代表性 bot conversation 截图。另请修复 PR body 的字面量 \n,并补两个缺失的 Generated-by trailer,或明确为纯人工。

Comment thread apps/desktop/src/main/bot-incoming-main.ts Outdated
@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch from 230f720 to 8e75ab7 Compare August 19, 2026 12:16
@me2seeks

Copy link
Copy Markdown
Contributor Author

Review remediation is now on 8e75ab75e: Runtime Host proves exact replays before Desktop applies transient admission limits, with a service-recreation regression beyond the burst limit. Runtime Host full and Desktop 938/938 pass. The PR body now includes a sanitized deterministic screenshot covering Slack thread targeting, reconnect replay/no duplicate Turn, and reset acknowledgement, plus corrected Maka/Codex provenance. Please re-review the current head.

streamId carries the canonical source-message operation id, so the
draft projection and the final send share one identity even before the
Runtime Host selects a Turn id.

Generated-by: Maka
…entity

A redelivery of an id-less message minted a fresh sourceEventId each
time, defeating Host admission idempotency and running duplicate Turns.
Match the DingTalk bridge and drop the message instead.

Generated-by: Maka
placement 'next_turn' + busyBehavior 'reject' either starts the Turn or
fails, and an exact redelivery resolves to turn_started through the
Host's durable proof. A queued disposition therefore means the submit
contract changed; fail closed with a descriptive error instead of
misreporting a busy race.

Generated-by: Maka
An archive commits the lifecycle before purging external conversation
bindings, so a purge failure left stale bindings with no durable retry
record — the already-archived path returned success without retrying
and recovery only scheduled removal cleanup. The leftover binding rows
are themselves the durable record: recovery now lists bound Sessions
and retries the purge for any that are archived or gone, and the
already-archived path retries the purge before reporting success.

Generated-by: Maka

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (7)
packages/runtime/src/bots/qq-bridge.ts (1)

232-239: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Delete the redundant replyTarget re-assignment.

The object spread of channel already carries replyTarget. The explicit property restates it. Keep the comment, drop the line.

♻️ Proposed simplification
   return {
     ...channel,
     conversationId: `dm:${channel.conversationId}`,
     // QQ direct messages still use the channel REST route. Only continuity
     // needs a DM-specific identity; transport must retain the real address.
-    replyTarget: channel.replyTarget,
     isGroup: false,
   };
packages/storage/src/__tests__/external-conversation-authority.test.ts (1)

135-190: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The test name promises a bound, but the assertions stop at the limit.

The test seeds EXTERNAL_CONVERSATION_RELEASE_RECEIPT_TOTAL_LIMIT - 1 rows, performs one release, and asserts the total is exactly the limit. It never exercises the state where the total is at the limit. That is the branch where release throws instead of deleting a binding. Add one more release after this point and assert the rejection, so the global bound is actually protected by this test.

Disposition: optional.

apps/desktop/src/main/runtime-host-client.ts (1)

1277-1280: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

listAgentGraphEpochs is outside this PR's stated scope and omits its return type.

The PR describes conversation continuity, bot identity, and external conversation bindings. Agent graph epochs are unrelated. If this method belongs to another change, move it there so this diff stays the smallest coherent solution. If it must ship here, add the explicit return type that every neighbouring method on this class declares.

Disposition: optional.

apps/desktop/src/main/bot-incoming-main.ts (1)

246-309: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Extract the admitted-turn path out of processBotIncomingMessage.

processBotIncomingMessage now handles help, reset, session resolution, replay probing, rate limiting, reply streaming, the typing loop, and reply delivery in one function of roughly 200 lines. The admission_required branch alone spans Lines 246-309 and owns the typing AbortController lifecycle. Extract that branch into a named helper that takes turnInput, the reply stream, and the message, and returns the turn result. The rest of the function then reads as a linear flow.

Disposition: optional. No behavior change.

packages/runtime-host/src/__tests__/protocol.test.ts (1)

811-814: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an invalid busyBehavior assertion.

The test covers invalid admissionMode, but not the independent busyBehavior guard. Add the symmetric assertion next to it.

packages/runtime-host/src/protocol/operations.ts (1)

205-206: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Move agent.graph.epochs.query to a separate change. The external-conversation coordinator does not use this operation.

apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts (1)

72-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Correct the test name; this test does not recreate the service.

The test creates one service and never recreates it. The name claims service recreation, which is covered by the test at lines 44-70. The name can lead a maintainer to believe recreation is covered here.

Proposed fix
-test('service recreation replays more than the transient burst before admitting new work', async () => {
+test('replays every prior source delivery before admitting new work', async () => {

Disposition: optional.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c17d7296-5670-4efa-9a8c-9f8811dabb4a

📥 Commits

Reviewing files that changed from the base of the PR and between 261cc8e and 8e75ab7.

📒 Files selected for processing (32)
  • apps/desktop/src/main/__tests__/bot-incoming-session-lifecycle.test.ts
  • apps/desktop/src/main/__tests__/bot-incoming-typing.test.ts
  • apps/desktop/src/main/__tests__/bot-session-adapter-fixture.ts
  • apps/desktop/src/main/__tests__/runtime-host-bot-session-adapter.test.ts
  • apps/desktop/src/main/bot-incoming-main.ts
  • apps/desktop/src/main/bot-session-adapter.ts
  • apps/desktop/src/main/runtime-host-bot-session-adapter.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • packages/runtime-host/src/__tests__/execution-composition.test.ts
  • packages/runtime-host/src/__tests__/external-conversation-coordinator.test.ts
  • packages/runtime-host/src/__tests__/message-coordinator.test.ts
  • packages/runtime-host/src/__tests__/protocol.test.ts
  • packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts
  • packages/runtime-host/src/protocol/index.ts
  • packages/runtime-host/src/protocol/message.ts
  • packages/runtime-host/src/protocol/operations.ts
  • packages/runtime-host/src/server/external-conversation-coordinator.ts
  • packages/runtime-host/src/server/message-coordinator.ts
  • packages/runtime-host/src/server/operation-dispatcher.ts
  • packages/runtime-host/src/server/session-retirement-coordinator.ts
  • packages/runtime/src/bots/__tests__/discord-bridge.test.ts
  • packages/runtime/src/bots/__tests__/im-channel-mapping.test.ts
  • packages/runtime/src/bots/__tests__/qq-bridge.test.ts
  • packages/runtime/src/bots/__tests__/telegram-reply-to.test.ts
  • packages/runtime/src/bots/discord-bridge.ts
  • packages/runtime/src/bots/feishu-bridge.ts
  • packages/runtime/src/bots/qq-bridge.ts
  • packages/runtime/src/bots/telegram-bridge.ts
  • packages/runtime/src/bots/types.ts
  • packages/runtime/src/bots/wecom-bridge.ts
  • packages/storage/src/__tests__/external-conversation-authority.test.ts
  • packages/storage/src/external-conversation-authority.ts

Included review availability: Your plan provides up to 3 included reviews per hour; 0 remain after this review.

Comment thread apps/desktop/src/main/bot-incoming-main.ts Outdated
Comment thread packages/runtime/src/bots/qq-bridge.ts Outdated
Comment thread packages/runtime/src/bots/telegram-bridge.ts Outdated
Comment thread packages/storage/src/external-conversation-authority.ts
@me2seeks
me2seeks force-pushed the feat/2325-bot-conversation-continuity branch from 8e75ab7 to 8bd6267 Compare August 19, 2026 12:27
@me2seeks

Copy link
Copy Markdown
Contributor Author

Rebased onto current main at 8bd626728. The schema-version collision is resolved by preserving main migration 28 and assigning the conversation authority tables migration 29; the v27 migration fixtures now remove both later schema slices before exercising startup migration. Local verification after rebase: Desktop 964/964, Storage 817 passed (16 skipped), Runtime Host 1,027/1,028 with the sole migration-fixture failure then the corrected focused migration test 1/1. All branch commits retain explicit Maka or Codex trailers.

@me2seeks

Copy link
Copy Markdown
Contributor Author

Latest bot-review remediation is on d854710d2: source event IDs now fail closed across Slack/DingTalk/WeChat, QQ channel/user identities are validated, signed Telegram group IDs are preserved, and test narrowing can no longer pass by early return. The SHA-256 key is now explicitly documented as a fixed-length lookup key—not confidentiality. Local: Runtime adapter matrix 35/35, Host focused 25/25, Desktop continuity 16/16, affected builds and Biome/diff checks pass. All new threads are replied and resolved.

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

Reviewed exact head d854710d2bd7763d2a665b474940e319461f2531.

The code findings are resolved: exact historical retries now query the Host in replay_only mode before consuming the transient Desktop rate budget, cannot create new work without durable proof, and only genuinely new admission spends a token. The greater-than-burst restart regression covers the original failure mode. All checks are green, all threads are resolved, and the PR body plus all 27 commits have complete Maka/Codex provenance. I found no remaining P0–P2 code issue.

One acceptance item remains before approval: the linked image explicitly identifies itself as a sanitized deterministic fixture and “not a live workspace capture.” Because this PR changes user-visible Slack/QQ/Telegram threading, reply targeting, and reset/reconnect behavior, please add at least one author-provided, sanitized screenshot from an actual supported bot-channel UI showing the resulting behavior. The fixture is useful documentation, but it does not validate the real platform presentation.

AI-assisted review disclosure: Codex reviewed the exact-head diff, remediation, tests, current CI and threads, visual evidence, and provenance metadata. No external model was used. Astro-Han authorized this review campaign.

中文说明

代码问题已经全部闭环:历史重放会先查 Host 的 durable proof,不会先消耗 Desktop 限流 token;超过 burst 的重启回归也已覆盖。CI、线程和 AI provenance 都完整,没有剩余 P0–P2。

现在只差截图验收:现有图片明确写着它是合成的 deterministic fixture,不是真实 workspace capture。由于本 PR 会改变 Slack/QQ/Telegram 中用户实际看到的 thread、reply target 和 reset/reconnect 行为,请作者补至少一张脱敏的真实受支持 bot channel 截图。

@Astro-Han

Copy link
Copy Markdown
Contributor

Hi — this PR conflicts with current main and cannot be merged as-is.

I tested a rebase onto current main locally (in a throwaway worktree — your branch was not touched). It stops on these files:

  • packages/runtime-host/src/server/execution-composition.ts

These are real source conflicts, so they need your judgement rather than a mechanical rebase — please rebase onto current main and resolve them yourself, then push. Once the branch is conflict-free and CI is green on the new head, I will pick it up for review.

git fetch upstream && git rebase upstream/main
# resolve, then
git push --force-with-lease

Thanks for the contribution — happy to help if any conflict is unclear.


AI-assisted maintenance note, not a review. It does not count as the required human review under CONTRIBUTING.md §Review.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants