[Feature & Refactor] LeapFlow Gateway & App Connector Robustness Enhancements#16
Conversation
…kpoint, session persistence, callbacks, normalizers, unified inbound Close all design gaps for the IM bidirectional message processing architecture: - Add 4 Feishu message reading actions (im.list_messages, im.get_messages, im.search_messages, im.list_thread_messages) to feishu.yaml action pack - Implement EventDeduplicator (LRU-based) in consumer loop for idempotency - Add DuckDB-backed GatewayCheckpointStore for event source resume on restart - Wire ConversationStore into GatewayRouter for persistent session history - Parse card.action.trigger into InboundCallback with full field extraction - Add callback handler pipeline (GatewayServer → GatewayRouter.handle_callback) - Create CompositeEventSource for multi-event-key subscription merging - Update LarkCliEventSource to default-subscribe both im.message.receive_v1 and card.action.trigger via CompositeEventSource - Implement GatewayEventBridge to publish IM signals to EventBus for the learning pipeline (PatternMiner, Copilot) - Add TelegramEventNormalizer and DingTalkEventNormalizer with auto-registration - Create TelegramPollingEventSource and DingTalkWebhookEventSource as BackendEventSource implementations - Unify inbound architecture: remove adapter.on_message direct callback, route all platforms through normalizer → trigger policy consumer loop Co-authored-by: Cursor <cursoragent@cursor.com>
P0 — @ALL mention detection: - Normalizers (Feishu/Telegram/DingTalk) now detect @all/@所有人 and treat it as bot_mentioned for trigger policy routing. P0 — Processing indicators: - GatewayRouter signals start/done/error phases via IndicatorFn. - Context wires it to platform reactions (OnIt emoji). - Fire-and-forget with 5s timeout — never blocks message processing. P0 — Streaming reply: - GatewayRouter supports progressive message editing via StreamSendFn. - Sends initial message, then updates in-place as LLM chunks arrive. - Throttled updates (1.5s interval, 40-char minimum delta). - Falls back to non-streaming when tools are active. P1 — Media inbound (image/file/audio): - Feishu normalizer extracts MediaAttachment with file_key for deferred download via im.download_resource action. - Telegram normalizer extracts photo/document/audio/video/voice/sticker. - Non-text messages produce synthetic [type] text + media tuple. P1 — Extended event keys: - Default Feishu subscription now includes reaction, read receipt, and bot lifecycle events for richer signal observation. P1 — New Feishu actions: - im.reply_message, im.update_message, im.add_reaction, im.remove_reaction, im.update_card, im.download_resource. - FeishuAdapter.send() uses im.reply_message when reply_to_id is set. P2 — User authorization: - TriggerPolicy gains allowed_users field (frozenset whitelist). - Context wires allowed_users/blocked_users/keywords/rate limits from platform options (config-driven, no hardcoding). P2 — Outbound text chunking: - GatewayServer.send_reply() auto-chunks text exceeding adapter.max_message_length at paragraph/sentence boundaries. P2 — Persistent dedup: - DuckDBDeduplicationStore saves/loads event_ids across restarts. - EventDeduplicator gains load_from_store/save_to_store methods. - Consumer loop loads on start, saves on cancellation. - Auto-prunes entries older than 7 days. P2 — Thread parent context: - Feishu normalizer extracts parent_id → reply_to_id. - GatewayRouter fetches parent text via ContextFetchFn and prepends "[Replying to: ...]" context for the LLM. P2 — Markdown/rich format detection: - send_reply() detects markdown formatting (code blocks, headers, lists, bold, links) and sets format_hint metadata. - Adapters can use format_hint to select richer message types. Tests: 467 passed (12 new tests for @ALL, media, reply_to_id, allowed_users, text chunking, and markdown detection). Co-authored-by: Cursor <cursoragent@cursor.com>
…dempotency Root-cause fix for two classes of platform_action failures: 1. Payload structural errors (fields at wrong level, missing required fields): - Add normalize_payload() that lifts misplaced business fields into payload - Enhance ValidationResult with failure_code, missing_fields, recovery_hint - Enhance Capability Index to show per-action payload signatures with required markers 2. Duplicate side-effect execution (LLM repeating send/write across turns): - Add task-scoped side-effect dedup in platform_action_handler (before approval) - Add completed:true + execution_note in successful send/write/execute results - Add "Side-effect action rule" to system prompt constraining LLM behavior - Add platform_action evidence builder with COMPLETED/ALREADY_EXECUTED markers - Add loop exit bias nudging LLM to stop after side-effect completion Design: defense-in-depth with Prevention (prompt/result signals) + Protection (deterministic dedup guard). Read actions are never subject to dedup. Co-authored-by: Cursor <cursoragent@cursor.com>
Add a human-curated alias table as a remedial resolution layer between formatting normalization and "unknown" fallback. This transparently maps common LLM naming drift (read_file → file_read, execute_command → shell_run, etc.) to canonical tool names without requiring an LLM retry round-trip. Design: - Alias table declared in name_resolver.py as TOOL_NAME_ALIASES (Dict[str,str]) - Validated at registry construction: only entries targeting actual specs are kept - Resolution priority: exact > normalized > bridge prefix > alias > unknown - New ResolutionStatus "aliased" for auditability in logs and TUI - auto_executable=True — aliases are proven 1:1 semantic equivalences - Table is intentionally small; primary reliance is on LLM schema adherence Extensibility: - Adding a new alias = one line in TOOL_NAME_ALIASES - Adding a new LLM or tool = no change to resolution logic - Gateway router also uses the alias table for its scoped registry Co-authored-by: Cursor <cursoragent@cursor.com>
…ti-hallucination Defense-in-depth against LLM hallucinating resource IDs after authorization failures. Addresses the critical flow: list_chats fails → model fabricates chat_id → approval passes with insufficient info → send times out. P0 — Platform-Level Degradation: - CapabilityHealthLedger now tracks platform degradation: when a hard authorization failure (admin_required) is recorded, ALL side-effect actions on that platform are blocked by feasibility check, regardless of whether their specific capability has been tested. - Adds llm_instruction field to blocked responses, explicitly instructing the model to stop and report the failure rather than guessing. - Transient failures (timeout, rate-limit) do NOT trigger degradation. - clear(platform) removes degradation when permissions are fixed. P1 — Resource Provenance Tracking: - New ResourceProvenancePool (session-scoped) registers resource IDs (chat_id, message_id, etc.) observed from successful API responses. - Before side-effect actions, resource fields in the payload are checked against the pool: VERIFIED (seen from API), UNVERIFIED (pool populated but ID not found → likely hallucinated), UNKNOWN (pool empty). - UNVERIFIED triggers provenance warnings shown in approval detail and elevates risk_hint to 0.9 for the approval gate. - Pool populated automatically from execute_platform_action results by scanning for fields matching declared resource_fields across specs. P0 — Anti-Hallucination Context Signals: - System prompt: "Resource identifier provenance rule" — explicitly forbids fabricating IDs when read actions have failed. - ToolEvidenceBuilder: propagates llm_instruction and platform_degraded fields to LLM context for auth failure results. - Feasibility responses include stop-signal for LLM consumption. All layers are platform-neutral and auto-extend to new platforms/actions. Co-authored-by: Cursor <cursoragent@cursor.com>
…l-closed defaults Core PCD redesign (context_disclosure.py): - Replace natural-language tool-name guessing with a manifest-driven planner: disclosure decisions now derive only from structural facts (capability manifests, slash commands, context posture, recent failures, prior-turn tool-category continuity). - Three stable levels: CORE (always-on Tier 0 index + Tier 0.5 read-only whitelist), EXPANDED (+ Tier 1 categories opened by structural gates), FULL (safety/compat fallback for high-stakes turns). - CapabilityManifest.is_core is a static, auditable property (read_only risk + non-high schema cost), never a per-turn guess. - Fail-closed unclassified fallback: tools that declare no explicit x_leapflow metadata and match no recognized safe keyword now default to "unclassified"/medium risk (non-core) instead of silently trusting them as "general"/read_only. text_search is explicitly whitelisted to avoid a regression from this tightened default. - CORE-level reasoning stays OFF unless a Tier 1 category is open, to preserve the low-latency pure-chat path. Security-relevant tool classification fixes: - hub_tool.py / gateway_tool.py: add explicit x_leapflow metadata to all hub/platform/gateway tools. Their descriptions previously lacked distinguishing keywords, causing gateway_connect/platform_connect to be keyword-misclassified as "general"/read_only and silently promoted into the always-on core whitelist. Tier 1 continuity fix (engine.py): - _recent_tool_categories() previously tried to recover categories from a synthetic "[Called: ...]" text summary in working memory, which has no structured tool_calls field — Tier 1 continuity never actually fired. Replaced with a dedicated engine-instance state (_last_turn_tool_categories), reset per turn and populated from the turn's actual executed native tool_calls. - _format_tool_catalog now annotates every non-core tool with its exact capability_expand category so the model never has to guess the string. registry_bootstrap.py: - capability_expand's description is now patched at import time with the live, computed set of non-core categories instead of a hardcoded example list that could drift from the real registry. UX regression fixes: - tui_app/stream.py: update the disclosure-tag suppression set from the retired "selected_tools" level to the current "core" default, so normal chat turns no longer show a noisy "disclosure=core" tag. Tests: - Rewrote test_context_disclosure.py against the new manifest/planner API, including a regression test pinning hub/gateway tools as explicitly non-core. - Added an end-to-end regression test for Tier 1 continuity (test_progressive_disclosure_expands_write_category_after_prior_turn_tool_use). - Updated stale assertions in test_agent_execution.py and test_tui_command_queue.py that referenced retired disclosure semantics. 514 tests pass; all modified modules compile and import standalone.
- Treat authorization blockers (access_denied, missing_scope, platform_degraded, blocks_approval, admin_required non-retryable failures) as hard-stop tool results across native/text and stream/non-stream engine paths. - Stop remaining sequential native tool calls when a permission blocker is hit; classify platform/gateway/hub stateful tools as never-parallel. - Add shared permission-failure predicates under security/permission_failures.py so engine and TUI use the same authority for permission recovery detection. - Add TuiCommandStatus.BLOCKED and wire local + daemon interactive response labels to render permission-blocked turns as #N blocked instead of failed. - Fix permission recovery card rendering so Rich never receives Panel(None). - Add regression coverage for text/native hard-stop, blocked labels, command terminal state, and permission recovery card renderability. Validation: py_compile modified files; 518 tests pass.
- Remove synchronous await_learning() from teach stop RPC handler to prevent 30s RPC timeout; distillation now runs as background task - Sync app.prompt_mode with session state (learning/paused/idle) so TUI shows "● rec" recording indicator during active teach sessions - Add learning-mode placeholder text with available teach commands - Emit chat interaction events from engine during LEARNING mode for trajectory capture (user messages, tool calls, tool results, responses) - Add CHAT_* ActionTypes to trajectory domain for recording chat demos - Ensure RecordingProfile always activates InputTapObserver during teach - Start ObservationDaemon on-demand when recording begins if not running - Route daemon TUI input through both engine_chat and annotate in learning mode for chat-demonstration recording Co-authored-by: Cursor <cursoragent@cursor.com>
Implement a push notification system for background task transparency: - Add NotificationBus: lightweight async broadcast with per-subscriber queues, back-pressure handling, and graceful shutdown - Add events.subscribe streaming RPC: long-lived NDJSON connection for daemon-to-TUI push notifications (generalizable to any background task) - Wire session progress/completion callbacks to NotificationBus so distillation emits teach.progress and teach.complete events - TUI background subscription task: renders progress in status bar (⚗ phase pct%) and shows completion inline notification with results - Add /teach status command for on-demand distillation state query - Fix _update_status() overriding prompt_mode during teach session - Add teach.stopped notification for idle-watchdog auto-stop so TUI resets recording indicator when daemon times out the session - Gate distillation on has_pending_distillation property to avoid misleading "started" messages when learnability check skips - Add public SessionController properties: has_pending_distillation, is_distilling, recording_step_count, last_result - Reset stale distill state on notification stream reconnection Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Code Review
This pull request introduces significant enhancements to LeapFlow's App Connector and gateway infrastructure, focusing on robust error handling, platform-neutral capability health tracking, resource provenance verification, and progressive context disclosure. Key additions include a new CapabilityHealthLedger to track authorization failures, a ResourceProvenancePool to prevent resource ID hallucination, and a capability_expand tool for Tier 1 model-initiated discovery. Feedback from the review highlights several critical bugs and improvement opportunities, including: preventing AttributeError by adding defensive checks for ctx.session in teaching commands; explicitly terminating leaked subprocesses on timeout in LarkCliEventSource; preventing task leaks in CompositeEventSource by cancelling background tasks on cancellation; ensuring checkpoint and deduplication states are saved in a finally block; handling empty chunks in send_reply to avoid returning None; fixing a string formatting bug in the hub command handler; ensuring backward compatibility for custom file gates by adding fallback try-except blocks; and optimizing performance by caching capability manifests in engine.py and batching database inserts in checkpoint_store.py.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
PR: LeapFlow Gateway & App Connector Robustness Enhancements
This pull request strengthens LeapFlow's App Connector and gateway infrastructure by introducing platform-neutral capability health tracking, strict resource provenance, and progressive context discovery, alongside key stability and performance fixes.
Key Enhancements
ResourceProvenancePoolto prevent resource ID hallucination during execution.capability_expandtool to enable Tier 1 model-initiated, on-demand discovery.Bug Fixes & Stability Improvements
Defensive Session Handling: Added safety checks for
ctx.sessionin teaching commands to preventAttributeError.Leak Prevention:
Configured
CompositeEventSourceto cancel background tasks upon cancellation.Ensured leaked subprocesses in
LarkCliEventSourceare explicitly terminated on timeout.Guaranteed State Saving: Wrapped checkpoint and deduplication state saves in a
finallyblock to protect against middle-of-run failures.Robust Data Handling:
Updated
send_replyto handle empty chunks gracefully and avoid returningNone.Resolved string formatting bugs in the
hubcommand handler.Performance & Compatibility Optimizations
try-exceptblocks to ensure older, custom file gates continue to function seamlessly.engine.pyto reduce redundant processing overhead.checkpoint_store.pyfor high-throughput insert optimization.