Skip to content

🐛 fix(hetero-agent): wire AskUserBridge response events to renderer#14732

Merged
arvinxx merged 1 commit into
canaryfrom
fix/askuser-bridge-response-wire-events
May 12, 2026
Merged

🐛 fix(hetero-agent): wire AskUserBridge response events to renderer#14732
arvinxx merged 1 commit into
canaryfrom
fix/askuser-bridge-response-wire-events

Conversation

@arvinxx

@arvinxx arvinxx commented May 12, 2026

Copy link
Copy Markdown
Member

Summary

  • Close the wire-protocol gap on AskUserBridge: every terminal path (timeout, user-driven resolve, cancel(), cancelAll()) now emits an agent_intervention_response event. Previously the bridge only resolved the local MCP handler on timeout, leaving the renderer's AskUserQuestion form stuck on pluginIntervention.status='pending' while CC silently continued and the owning op was GC'd 30s later — clicking Submit then threw Operation not found: op_xxx.
  • heterogeneousAgentExecutor now handles agent_intervention_response: timeout / session_ended mirror onto the tool message as intervention.status='rejected' + rejectedReason, so the UI form disables Submit before the op disappears. User-driven paths (!cancelled, user_cancelled) are filtered out — those are already optimistically updated by submitHeteroIntervention.
  • Defensive backstops so the throw can't recur even if a future regression breaks the wire path:
    • cleanupCompletedOperations: findfilter so every messageOperationMap entry pointing to a cleaned op is removed (assistant + tool message pairs previously stranded one entry as a dangling reference).
    • internal_getConversationContext: log + fall back to global state when the op is gone, instead of throwing.
    • submitHeteroIntervention: detect stale opId before passing it down the optimistic chain.

Scoped as a short-term backstop until LOBE-8746 retires the AskUser MCP bridge entirely.

Test plan

  • bunx vitest run 'packages/heterogeneous-agents/src/askUser' — 28/28 (4 new tests covering response emission on resolve / cancel / timeout / cancelAll)
  • bunx vitest run 'src/store/chat/slices/operation/__tests__/actions.test.ts' — 45/45 (updated invalid-opId case to assert fallback + warn instead of throw)
  • bunx vitest run 'src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts' — 28/28
  • bunx vitest run 'src/store/chat/slices/aiChat/actions/__tests__/heterogeneousAgentExecutor.test.ts' — 51/51
  • Manual repro: trigger CC AskUserQuestion, wait past 5min bridge timeout, confirm form auto-disables before the op cleanup window and Submit no longer throws.

🤖 Generated with Claude Code

@vercel

vercel Bot commented May 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lobehub Ready Ready Preview, Comment May 12, 2026 5:20pm

Request Review

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label May 12, 2026

@sourcery-ai sourcery-ai Bot 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.

Sorry @arvinxx, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@dosubot dosubot Bot added feature:agent Assistant/Agent configuration and behavior 🐛 Bug labels May 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0faef4f4a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 52 to +67
if (!operation) {
// The op was already cleaned up (e.g. completed CC turn whose
// runtime_end fired and was GC'd 30s later), but a late caller
// — typically a long-lived intervention surface — still carries
// the opId. Throwing here would tear down the optimistic write
// and any follow-up IPC the caller was about to perform, so we
// degrade to the global-state fallback and log loudly.
log(
'[internal_getConversationContext] ERROR: Operation not found: %s',
'[internal_getConversationContext] WARNING: Operation not found, falling back to global state: %s',
context.operationId,
);
throw new Error(`Operation not found: ${context.operationId}`);
console.warn(
'[internal_getConversationContext] operation not found, using global state:',
context.operationId,
);
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not route stale operations to the active conversation

When a late async update carries an operationId that has already been cleaned up and the user has since navigated elsewhere, this fallback makes internal_dispatchMessage and the subsequent messageService.update* query use the current active agent/topic instead of the operation's original conversation. That lets a stale event from topic A refresh/replace topic B's message list while topic A never receives the terminal update; stale operation contexts should be no-op'd or resolved from the target message rather than falling back to global state.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 35.18519% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.12%. Comparing base (84c89f9) to head (9ac27ad).
⚠️ Report is 5 commits behind head on canary.

Additional details and impacted files
@@            Coverage Diff             @@
##           canary   #14732      +/-   ##
==========================================
- Coverage   66.13%   66.12%   -0.02%     
==========================================
  Files        2913     2912       -1     
  Lines      255803   255790      -13     
  Branches    25026    30270    +5244     
==========================================
- Hits       169188   169150      -38     
- Misses      86462    86487      +25     
  Partials      153      153              
Flag Coverage Δ
app 60.54% <35.18%> (-0.02%) ⬇️
database 91.83% <ø> (ø)
packages/agent-runtime 80.48% <ø> (ø)
packages/builtin-tool-lobe-agent 83.41% <ø> (ø)
packages/context-engine 83.99% <ø> (ø)
packages/conversation-flow 92.43% <ø> (ø)
packages/file-loaders 87.60% <ø> (ø)
packages/memory-user-memory 74.74% <ø> (ø)
packages/model-bank 99.94% <ø> (ø)
packages/model-runtime 83.72% <ø> (ø)
packages/prompts 70.39% <ø> (ø)
packages/python-interpreter 92.90% <ø> (ø)
packages/ssrf-safe-fetch 0.00% <ø> (ø)
packages/types 5.44% <ø> (ø)
packages/utils 88.02% <ø> (ø)
packages/web-crawler 87.74% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Store 66.89% <35.18%> (-0.07%) ⬇️
Services 54.16% <ø> (ø)
Server 71.40% <ø> (ø)
Libs 56.16% <ø> (ø)
Utils 82.65% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Close the wire-protocol gap that left CC's AskUserQuestion form stuck on
"pending" after the bridge gave up. AskUserBridge now emits an
agent_intervention_response event on every terminal path (timeout,
user resolve, cancel, cancelAll), and heterogeneousAgentExecutor handles
it by stamping pluginIntervention.status = 'rejected' for timeout /
session_ended (user-driven paths are filtered out — already optimistic).

Layered defenses so a late Submit no longer throws "Operation not found":
- cleanupCompletedOperations: find→filter so every messageOperationMap
  entry pointing to the cleaned op is removed (assistant + tool message
  pairs previously stranded one entry as a dangling reference).
- internal_getConversationContext: log + fall back to global state when
  the op has been GC'd, instead of throwing.
- submitHeteroIntervention: detect a stale opId before passing it into
  the optimistic chain.

Scoped as a short-term backstop until LOBE-8746 retires the AskUser MCP
bridge entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arvinxx arvinxx force-pushed the fix/askuser-bridge-response-wire-events branch from b0faef4 to 9ac27ad Compare May 12, 2026 16:52
@arvinxx arvinxx merged commit 844f885 into canary May 12, 2026
34 of 35 checks passed
@arvinxx arvinxx deleted the fix/askuser-bridge-response-wire-events branch May 12, 2026 17:46
arvinxx added a commit that referenced this pull request May 12, 2026
…14732)

Close the wire-protocol gap that left CC's AskUserQuestion form stuck on
"pending" after the bridge gave up. AskUserBridge now emits an
agent_intervention_response event on every terminal path (timeout,
user resolve, cancel, cancelAll), and heterogeneousAgentExecutor handles
it by stamping pluginIntervention.status = 'rejected' for timeout /
session_ended (user-driven paths are filtered out — already optimistic).

Layered defenses so a late Submit no longer throws "Operation not found":
- cleanupCompletedOperations: find→filter so every messageOperationMap
  entry pointing to the cleaned op is removed (assistant + tool message
  pairs previously stranded one entry as a dangling reference).
- internal_getConversationContext: log + fall back to global state when
  the op has been GC'd, instead of throwing.
- submitHeteroIntervention: detect a stale opId before passing it into
  the optimistic chain.

Scoped as a short-term backstop until LOBE-8746 retires the AskUser MCP
bridge entirely.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 12, 2026
arvinxx added a commit that referenced this pull request May 13, 2026
# 🚀 LobeHub Release (20260513)

**Hotfix Scope:** Ship the canary backlog (111 PRs) onto main as a
fast-tracked patch — operator-focused, no weekly-style write-up.

> Brings the accumulated canary work into main: agent/task improvements,
hetero-agent fixes, desktop & onboarding polish, and several reliability
caps.

## ✨ What's Included

- **Agent & tasks** — Self-review proposal-to-action automation,
sub-agent dispatch consolidated to `lobe-agent`, AskUserQuestion wiring
for Claude Code, scheduler/hotkey/TodoList polish. (#14583, #14657,
#14715, #14639, #14732, #14707, #14713)
- **Home & onboarding** — Daily brief with linkable welcome + paired
input hint, inline skill auth in recommended task templates, cleanup of
captcha-on-signin and marketplace early-exit. (#14589, #14676, #14573,
#14598)
- **Bots & integrations** — Slack MPIM support, Discord DM fix,
slash-command + connect-error fixes, gateway client-tool plugin state.
(#14733, #14591, #14596)
- **Desktop & CLI** — Windows `.cmd` shim detection for `claude` /
`codex` CLIs, auth focus & pending-login reset fixes. (#14720, #14694,
#14695)
- **Reliability** — Cap web-crawler body size and image binary at safe
limits, attach error listeners to Neon/Node pools, reject inactive OIDC
access. (#14660, #14711, #14606, #14674)
- **Database** — `agent_operations` table + persist agent operations
from the runtime; switch user memory search to `paradedb.match(...)`.
(#14416, #14736, #14590)

## ⚙️ Upgrade

- **Self-hosted:** pull the latest image and restart. Drizzle migrations
(including the new `agent_operations` table) run automatically on boot.
lezi-fun pushed a commit to lezi-fun/lobehub that referenced this pull request May 13, 2026
…obehub#14732)

Close the wire-protocol gap that left CC's AskUserQuestion form stuck on
"pending" after the bridge gave up. AskUserBridge now emits an
agent_intervention_response event on every terminal path (timeout,
user resolve, cancel, cancelAll), and heterogeneousAgentExecutor handles
it by stamping pluginIntervention.status = 'rejected' for timeout /
session_ended (user-driven paths are filtered out — already optimistic).

Layered defenses so a late Submit no longer throws "Operation not found":
- cleanupCompletedOperations: find→filter so every messageOperationMap
  entry pointing to the cleaned op is removed (assistant + tool message
  pairs previously stranded one entry as a dangling reference).
- internal_getConversationContext: log + fall back to global state when
  the op has been GC'd, instead of throwing.
- submitHeteroIntervention: detect a stale opId before passing it into
  the optimistic chain.

Scoped as a short-term backstop until LOBE-8746 retires the AskUser MCP
bridge entirely.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 Bug feature:agent Assistant/Agent configuration and behavior size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant