Skip to content

fix(core): silent heartbeat should not send empty response message#370

Open
chenhg5 wants to merge 14 commits intomainfrom
fix/issue-355-silent-heartbeat-empty-response
Open

fix(core): silent heartbeat should not send empty response message#370
chenhg5 wants to merge 14 commits intomainfrom
fix/issue-355-silent-heartbeat-empty-response

Conversation

@chenhg5
Copy link
Copy Markdown
Owner

@chenhg5 chenhg5 commented Mar 29, 2026

Summary

  • Silent heartbeat mode (heartbeat with silent=true) should suppress sending the "(空响应)" / "(empty response)" fallback message when the agent produces no output
  • Added SilentEmpty field to Message struct to propagate the flag from heartbeat execution
  • Added silentEmpty field to interactiveState struct to track the flag during message processing
  • Modified empty response handling in processInteractiveEvents to check state.silentEmpty before applying MsgEmptyResponse fallback

Root Cause

When heartbeat runs in silent mode (ExecuteHeartbeat(silent=true)), the agent may produce no output (e.g., just keeping awareness without actual work). Previously, the engine would still send "(空响应)" to the chat, causing unnecessary noise.

Test plan

  • go build ./... passes
  • go vet ./... passes
  • go test ./... passes
  • Manual test: heartbeat with silent=true should not send any message when agent produces empty response

🤖 Generated with Claude Code

Closes #355

claude added 14 commits March 24, 2026 23:58
Full-featured management UI for CC-Connect instances:
- Token-based auth, dark/light/system theme, i18n (EN/ZH/ZH-TW/JA/ES)
- Dashboard with system status, project overview
- Project management with providers, heartbeat, settings tabs
- Session list and chat interface with Markdown rendering
- Cron job management (create/delete)
- Bridge adapter viewer
- System config viewer, logs, restart/reload controls
- All endpoints aligned with docs/management-api.md

Made-with: Cursor
- Enrich session list/detail API with live status, last_message preview,
  agent_type, platform, user metadata, and timestamps
- Add SessionKeyMap() helper to core/session.go for ID-to-key mapping
- Redesign SessionList as responsive grid layout with project filtering,
  time-ago display, and last message preview
- Upgrade markdown rendering: rehype-highlight for syntax highlighting,
  @tailwindcss/typography for proper prose styling, copy-to-clipboard on
  code blocks, GitHub light/dark themes
- Show live/offline session status; disable message input for non-live sessions
- Update management-api.md to document new session fields
- Remove node_modules from git tracking and add to .gitignore

Made-with: Cursor
The /list command silently showed 0 messages when sqlite3 was not installed
or the DB query failed. Now logs a warning so operators can diagnose the
issue. Fixes #318.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add backend setup API endpoints for Feishu and Weixin QR onboarding
  flow (/api/v1/setup/{feishu,weixin}/{begin,poll,save}), proxying
  external registration APIs and persisting credentials via existing
  config.EnsureProjectWith* + SavePlatformCredentials functions.

- Add PlatformSetupQR React component with full QR scan lifecycle:
  idle → scanning → scanned → saving → completed, with retry on
  expiry/error.

- Restore "Add project" button on project list with a multi-step
  wizard (name → platform selection → QR or manual setup).

- Expand "Add platform" dialog in project detail to support all 10
  platform types (Feishu, WeChat, Telegram, Discord, Slack, DingTalk,
  WeChat Work, QQ, QQ Bot, LINE), with QR flow for Feishu/WeChat and
  manual config guidance for others.

- Add responsive font scaling in index.css (17px at 1536px, 18px at
  1920px, 20px at 2560px) for better readability on large screens.

- Add i18n keys for setup flow in all 5 locales (EN/ZH/ZH-TW/JA/ES).

Made-with: Cursor
- Add generic AddPlatformToProject API (POST /projects/{name}/add-platform)
  that persists any platform type + options to config.toml, creating the
  project automatically if it doesn't exist.

- Add PlatformManualForm component with field metadata for 8 non-QR
  platforms (Telegram, Discord, Slack, DingTalk, WeChat Work, QQ,
  QQ Bot, LINE), supporting password toggle, required validation,
  and collapsible advanced options.

- Integrate manual forms into both the project creation wizard and
  the project detail "Add platform" dialog, replacing the previous
  "edit config.toml" fallback.

- Add i18n translations for all 28 platform field labels and hints
  across all 5 locales (EN/ZH/ZH-TW/JA/ES).

Made-with: Cursor
…d fix bridge permission handling

- Redesign Sessions → Chat: each project is a chat entry (IM-style)
- Add CommandPalette with all slash commands grouped by category
- Add SessionDrawer (right slide-out panel via /list command)
- Auto-load latest session when entering a project chat
- Fix bridge handleCardAction to properly dispatch perm:, askq:, cmd: actions as messages
- Filter incoming bridge messages by session_key to prevent session mixing
- Add chat/cmd i18n translations for all 5 languages
- Update nav, routing, and header for Chat

Made-with: Cursor
- Fix WeChat QR polling stuck in "waiting for scan" caused by React 18
  StrictMode double-mount leaving cancelledRef as true; reset refs at
  flow start and inline polling loop to avoid stale closure issues
- Increase iLink API poll timeouts (37s context, 40s HTTP client) and
  Vite proxy timeout (45s) to match long-poll behavior
- Add debug logging to WeChat QR begin/poll handlers for troubleshooting
- Fix restart API returning 400 on empty body; make JSON body optional
- Add restart button to platform setup completion screen with i18n
- Add TOML config formatter: insert blank lines between sections, strip
  zero-value keys and empty sections for cleaner config output

Made-with: Cursor
…Socket

- Add DELETE /api/v1/projects/{name} API with config.RemoveProject()
  using callback pattern to keep core/ free of config/ imports
- Move delete button from project list to project detail Settings tab
  under a "Danger Zone" section with confirmation dialog
- Wait for service recovery after restart before navigating to avoid
  empty project list
- Change Chat list from single-column IM style to responsive grid
  layout (1/2/3 cols) matching the project list design
- Fix bridge WebSocket URL to use current page host:port through the
  proxy instead of directly connecting to bridge port
- Add i18n for delete/danger zone across all 5 languages

Made-with: Cursor
…consistency

- Remove bridge navigation and page (unused)
- Dashboard projects: grid layout (1/2/3 cols) instead of single column list
- System config: display raw TOML from config file instead of JSON summary
- Remove logs page (no backend endpoint)
- Cron page: grid layout, click-to-edit, friendly schedule dropdown,
  session key dropdown, mutually exclusive prompt/exec task type toggle
- Move language/theme/logout controls from sidebar to right header
- Unify accent color: use CSS variables with RGB format for proper
  Tailwind opacity support; light mode uses darker green (#16a34a),
  dark mode keeps neon green (#42ff9c)
- Fix Badge info variant from blue to accent green
- Align sidebar/header heights (both h-14)
- Add footer with copyright, version number, and GitHub link
- Fix project settings PATCH: persist quiet/language/admin_from/
  disabled_commands to config.toml via new SaveProjectSettings()
- Return admin_from and disabled_commands in project GET response
- Add GetDisabledCommands() to Engine for settings readback
- Backend: add PATCH support to cron job endpoint for editing
- Backend: /config endpoint returns raw TOML file content

Made-with: Cursor
…tings

- Project creation wizard now requires work_dir (working directory) and
  allows selecting agent_type (claudecode, codex, gemini, cursor, etc.)
- work_dir and agent_type are passed through QR setup (feishu/weixin)
  and manual platform form creation paths
- Project settings page reorganized into three sections:
  - Agent: work_dir, permission mode (default/acceptEdits/plan/yolo/dontAsk)
  - General: quiet, context indicator toggle, language, admin_from,
    disabled commands
  - Platform access control: per-platform allow_from editing
- Backend: GET /projects/{name} now returns work_dir, agent_mode,
  show_context_indicator, and platform_configs with allow_from
- Backend: PATCH /projects/{name} accepts and persists work_dir, mode,
  show_context_indicator, and platform_allow_from to config.toml
- config.AddPlatformToProject accepts workDir/agentType for new projects
- SaveProjectSettings refactored to use ProjectSettingsUpdate struct
- Added GetProjectConfigDetails to read extended config from TOML
- i18n: all 5 languages updated with new translation keys

Made-with: Cursor
- Add GET/PATCH /api/v1/settings endpoint for global config
- Add GlobalSettings component with editable sections: general (language,
  quiet, attachment_send, idle_timeout), display (thinking/tool max len),
  stream preview, rate limit, and log level
- Integrate global settings into System page with collapsible raw config
- Add "View All" link to Recent Sessions on Dashboard
- Add i18n translations for all global settings (en, zh, zh-TW, ja, es)

Made-with: Cursor
…h config

- Add /web install|upgrade|status|uninstall slash command (privileged)
- Auto-install cc-connect-web via npm into ~/.cc-connect/web/
- Auto-configure [bridge] and [management] in config.toml with random tokens
- Management server serves SPA static files from dist/ with fallback to index.html
- Add config.EnableWebAdmin() for atomic bridge+management setup
- Add core/web_manager.go with npm registry integration
- Prepare web/package.json for npm publish as cc-connect-web (files: dist/)
- Add i18n translations for all /web messages (en, zh, zh-TW, ja, es)

Made-with: Cursor
When heartbeat runs in silent mode (silent=true), the engine should
suppress sending the "(空响应)" / "(empty response)" fallback message
when the agent produces no output. This fix adds:

- SilentEmpty field to Message struct
- silentEmpty field to interactiveState struct
- Propagation from ExecuteHeartbeat(silent) → Message → state
- Check state.silentEmpty before applying MsgEmptyResponse fallback

Closes #355

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chenhg5
Copy link
Copy Markdown
Owner Author

chenhg5 commented Mar 29, 2026

⚠️ Scope mismatch detected

The PR title indicates a focused fix for silent heartbeat, but the diff includes unrelated changes:

  • New web admin files (web/, core/web_manager.go, core/setup.go)
  • Management API changes (core/management.go, docs/management-api.md)
  • Config changes unrelated to heartbeat

This appears to be branch contamination - possibly merging PR #356's branch into this PR.

Recommendation: Please rebase this PR onto main and ensure only the heartbeat-related changes (SilentEmpty field in Message/interactiveState, empty response suppression logic) are included.

@chenhg5
Copy link
Copy Markdown
Owner Author

chenhg5 commented Apr 1, 2026

⚠️ PR Scope Mismatch

The PR description mentions only the silent heartbeat fix, but the actual changes include:

Silent heartbeat fix (~50 lines in core/):

  • silentEmpty field in Message and interactiveState
  • Empty response suppression logic

Web admin panel (~13000 lines in web/):

  • Complete React/TypeScript frontend
  • Management API extensions
  • /web command callbacks
  • Multiple new files

Recommendation: Consider splitting this PR into:

  1. PR for silent heartbeat fix (core/engine.go, core/message.go)
  2. PR for web admin panel (web/, management.go, setup.go)

This makes reviews easier and allows independent merge decisions for each feature.

On the silent heartbeat fix itself: The implementation looks correct - SilentEmpty flag propagates from heartbeat execution to processInteractiveEvents and suppresses MsgEmptyResponse fallback.

Please clarify: Is this intentional bundling, or should the web admin panel be in a separate PR?

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.

[Bug] Silent heartbeat sends "(空响应)" to user instead of staying silent

2 participants