Skip to content

fix(desktop): create bot sessions without invalid explore-at-create - #4040

Open
ennian001 wants to merge 3 commits into
apache:mainfrom
ennian001:fix/bot-feishu-explore-session
Open

fix(desktop): create bot sessions without invalid explore-at-create#4040
ennian001 wants to merge 3 commits into
apache:mainfrom
ennian001:fix/bot-feishu-explore-session

Conversation

@ennian001

@ennian001 ennian001 commented Aug 27, 2026

Copy link
Copy Markdown

Summary

English: IM bot conversations (Feishu/Lark and other platforms) failed on the first inbound message. The bot adapter created sessions with permissionMode: 'explore' at create time, but Runtime Host rejects explore unless a declared SessionStartMode (e.g. deep_research) is also provided. Session creation failed; users only saw the sanitized Feishu reply: “Maka 暂时无法处理这条消息:机器人对话处理失败”.

This PR creates bot sessions with the Host default permission mode, then pins explore via updateSessionConfiguration—the same path prepareSession already uses for existing bindings.

中文: IM 机器人(飞书/Lark 等)在收到第一条消息时会失败。Bot 适配器在 创建 Session 时直接传了 permissionMode: 'explore',但 Runtime Host 规定:创建时若要用 explore,必须同时声明 SessionStartMode(如 deep_research)。Bot 对话不属于该模式,因此创建被拒;飞书侧只看到脱敏后的 「Maka 暂时无法处理这条消息:机器人对话处理失败」

本 PR 改为:先用 Host 默认权限创建 Session,再通过 updateSessionConfiguration 切到 explore(与已有会话的 prepareSession 路径一致)。

Reproduction / 复现步骤

Before / 修复前

  1. Desktop dev build running (npm run dev).
  2. Settings → Remote access → connect Feishu (credentials valid, channel enabled).
  3. Send any DM to the bot in Feishu (e.g. hi).
  4. Bot replies with a transient error notice instead of an agent answer.

Symptom / 现象

Feishu bot returns dialogue processing failed

Also in repo: docs/images/pr/bot-feishu-explore-session-create/feishu-bot-dialogue-failed.png

Root cause / 根因

runtime-host-bot-session-adapter.ts passed permissionMode: 'explore' to createSession. Host rule in session-catalog-coordinator.ts:

Session creation requires a declared mode for explore permission

After / 修复后

Same steps → bot session is created, explore mode is applied post-create, turn proceeds and Feishu receives the agent reply.

Verification

npm ci
npm run build
node --test apps/desktop/dist/main/__tests__/runtime-host-bot-session-adapter.test.js \
              apps/desktop/dist/main/__tests__/bot-incoming-session-lifecycle.test.js

Result: 13/13 tests passed locally (10 at the original head; 3 added by the follow-up commit covering the explore-pin reconciliation and abandonment paths).

Did not run full npm test (large monorepo suite); focused on bot adapter + lifecycle tests that cover this path.

AI use

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

Tool(s) and scope: Cursor — bug diagnosis from local repro, patch, test update, PR text, and reproduction screenshot packaging.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally (focused suites + npm run build)

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

Made with Cursor

Bot incoming adapters passed permissionMode=explore at session create time,
but Runtime Host rejects explore without a declared SessionStartMode. Pin
explore after create via updateSessionConfiguration instead.

Generated-by: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added the effort/S Under 100 readable lines label Aug 27, 2026
@Astro-Han

Copy link
Copy Markdown
Contributor

This is a synthesis of the independent blind review by @Sol-404ARE at exact head ae923ca394bab4d63aabfa577d42196764567b32 (base 4e16782540eca8af28a9bd5a023ecd9a1b8e7e06, 3 files +30/-3). I verified the diff and the exact-head CI myself; the file:line findings below are from Sol's sealed review.

What I checked myself:

  • Read gh pr diff 4040 (fix: create bot sessions without invalid explore-at-create, 3 files) and confirmed the adapter at runtime-host-bot-session-adapter.ts:78-104 reconciles uncertain session.create, while the new configuration mutation path and the screenshot at docs/images/pr/bot-feishu-explore-session-create/feishu-bot-dialogue-failed.png match the reported scope.
  • Checked exact-head CI: label run 33097067958 SUCCESS, test run 33097068044 action_required (not green), Windows recovery likewise pending, OPEN/MERGEABLE/BLOCKED/REVIEW_REQUIRED.

Findings from Sol's review (file:line anchored, durability defect shared across axes but counted per-axis):

Standards — NO-GO — 2×P2 + 1×P3 (worst P2)

  • P2 — Unreconciled two-phase create/partial commit. apps/desktop/src/main/runtime-host-bot-session-adapter.ts:78-104 reconciles the uncertain session.create, but the newly added configuration mutation simply rethrows failures. If the Host commits explore and its response is lost, createSession() rejects before apps/desktop/src/main/bot-incoming-main.ts:216 binds the stable ID; the next inbound message creates another Session. A definite post-create update failure likewise leaves an unbound default-ask Session. Reconcile commit_outcome_unknown by calling getSession(session.id) and accepting only verified explore; define cleanup/reuse for definite failure; cover both paths with tests.

  • P2 — Unresolved required template field. The PR body still contains literal Fixes #. The template requires it to be filled, deleted when no issue closes, or changed to Refs #N for context.

  • P3 — Asset suffix/content mismatch. docs/images/pr/bot-feishu-explore-session-create/feishu-bot-dialogue-failed.png is actually JPEG/JFIF data (1024×847). Rename to .jpg and update refs, or re-encode as PNG.

Spec — NO-GO — 1×P2 (worst P2)

  • P2 — Reconcile the new post-create permission write before abandoning the stable Session. Concrete repro: create returns an ask projection; updateSessionConfiguration commits explore but throws RuntimeHostOperationError('session.configuration.update', 'commit_outcome_unknown', 'response lost'). The adapter rejects, no binding is installed, and the next message generates a new UUID. Desktop retries revision conflicts (runtime-host-client.ts:1626-1636) but not outcome-unknown, while the Host explicitly models post-commit uncertainty. Query the stable ID and accept verified explore, otherwise apply a bounded update/retry policy. Add a test parallel to runtime-host-bot-session-adapter.test.ts:151-172.

The Standards durability P2 and Spec P2 are the same underlying defect viewed through the two axes — each count is preserved per Sol's seal.

What was proven green: the happy-path fix is correct (create omits invalid explore-at-create and a successful update correctly pins explore); normal revision conflicts are retried; deep-research mode is intentionally not appropriate here. git diff --check and targeted Biome on both TS files PASS; ASF header audit PASS; worktree clean; screenshot proves the pre-fix error only. The PR body reports 10/10 focused tests.

What I did not judge: a full create → updateSessionConfiguration(commit_outcome_unknown) → retry E2E with a real Host was not executed — verification was by code inspection and the unit tests noted above.

Gate: Standards 2×P2 + Spec 1×P2 remain; label green but test/windows_recovery are action_required (awaiting approval, not SUCCESS), so head ae923ca is not yet merge-ready. Seal: notes/pr-4040-provisional.md.


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.

… Sessions

The post-create permission update now verifies commit_outcome_unknown
against the stable Session id instead of rethrowing blind: a lost
response that did pin explore binds the existing Session, while a
definite failure (or an unverified outcome) archives the unbound
default-ask Session so the next inbound message cannot fork a sibling
conversation. Also re-encodes the PR screenshot as actual PNG data to
match its extension.
@ennian001

Copy link
Copy Markdown
Author

Thanks for the detailed review. All three findings are addressed in 23c6e66:

  • P2 (Standards + Spec, explore-pin reconciliation): createSession no longer rethrows the post-create permission update blindly. On commit_outcome_unknown it now re-reads the stable id via getSession and accepts only a verified non-archived explore projection (binding the existing Session instead of forking a sibling on the next message). A definite update failure — or an outcome that cannot be verified as committed — archives the unbound default-ask Session via setSessionLifecycle(sessionId, 'archived') (best-effort, so cleanup cannot mask the original error) before rethrowing. Covered by three new tests in runtime-host-bot-session-adapter.test.ts: verified-uncertain-pin binds without archiving, definite failure archives and throws, unverified uncertain pin archives and throws.
  • P2 (template field): the literal Fixes # placeholder is removed from the description (this PR closes no tracked issue; happy to switch to Refs #N if there is one).
  • P3 (asset mismatch): feishu-bot-dialogue-failed.png is re-encoded as actual PNG data (was JPEG/JFIF), so the extension now matches the content and all references stay valid.

Local verification at 23c6e66: tsc -p tsconfig.main.json clean, targeted Biome clean, node --test on runtime-host-bot-session-adapter.test.js + bot-incoming-session-lifecycle.test.js → 13/13 pass. The CI run for the new head is action_required and still needs a maintainer approval to start, same as the previous head.

@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 at exact head 23c6e6635e0292b1ad0e0e1c8e215535c0cc8c6f. Approving with comments (P2/P3 only).

The follow-up fixes the prior first-message leak. Create omits permissionMode: 'explore' (Host prepareCreate only rejects explicit explore-without-mode). The pin uses the same session.configuration.update as prepareSession. On pin commit_outcome_unknown, getSession on the stable id binds only a non-archived explore projection; otherwise the unbound default-ask Session is archived and the error is rethrown. Inbound botConversationSessions.set runs only after createSession resolves, and same-conversation traffic is queued, so a throw does not bind and cannot race a sibling create.

[P2] Create-unknown reconcile does not mirror this pin path: getSession(sessionId) in the create catch has no .catch, and a failed catalog read does not abandonUnusableBotSession. After this PR, a successful create is Host-default (typically ask). If that create committed and the follow-up get throws (persistence_failed / catalog_unstable), the ask row leaks and the next inbound IM mints another Session.

[P3] Tests still skip the production create-unknown → ask → pin sequence (the existing reconcile case stubs getSession as already-explore, so the pin branch is never entered). Also uncovered: pin-unknown getSession throw, update committed but still ask, inbound create throw leaving the conversation map empty.

Kicking CI on this SHA separately (fork action_required gate).

// stable id before deciding the Session is unusable — otherwise the
// next inbound message forks another conversation.
const reconciled = isCommitOutcomeUnknown(error)
? await deps.client.getSession(session.id).catch(() => null)

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.

[P2] This is the right pin-unknown pattern. The create-unknown getSession(sessionId) a few lines above does not mirror it (no .catch, no abandon). After this PR that create is Host-default ask, so a catalog-read throw leaks an unbound ask row and the next inbound IM mints a sibling. Please use getSession(...).catch(() => null) and abandon if it is not verified explore, then rethrow the original error.

…ion pin

The candidate connection harness never implemented
session.configuration.update, so after the bot adapter began pinning
explore post-create, the Host-backed Bot turn test died on the
unexpected-operation guard and hung awaiting turnStarted until the CI
runner timeout (882s). Commit the explore pin like the real Host would;
the suite is back to 19/19 in ~300ms.
@ennian001

Copy link
Copy Markdown
Author

Follow-up for the CI failure on 23c6e66: runtime-host-desktop-candidate.test.js hung for ~883s and was interrupted at the runner timeout.

Root cause: that suite's connection harness never implemented session.configuration.update. Before this PR the bot adapter passed permissionMode: 'explore' inside the session.create input and never verified the returned projection, so no configuration update was ever issued. Both this PR's original head and 23c6e66 pin explore post-create, so the bot-turn test hit the harness's unexpected-operation guard, the turn never started, and await host.turnStarted hung. (This path had not executed in CI before because the fork workflows were awaiting approval.)

Fix in cedc98f: the harness now commits the explore pin (session.configuration.updatecommitted with permissionMode: 'explore'), mirroring the real Host. Local: tsc -p tsconfig.main.json clean, runtime-host-desktop-candidate 19/19 in ~300ms (no hang), bot adapter + lifecycle suites 13/13, targeted Biome clean.

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

Labels

effort/S Under 100 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants