Implements WebSocket ping/pong keepalive mechanism to prevent random
disconnects with close code 1006 (abnormal closure).
Changes:
- Add 20-second ping interval when WebSocket connection is open
- Monitor pong responses for connection health
- Enhanced error and close event logging with detailed diagnostics
- Clean up ping interval when connection closes
This fix addresses the root cause of issue #4142 where network
intermediaries (NAT, firewalls, proxies) timeout idle WebSocket
connections. The 20-second ping interval is industry standard practice
for preventing idle connection timeouts.
Fixes#4142
Fixes#4107
Problem:
- Cron jobs with systemEvent payloads enqueue events as passive text
- Agent sees events but doesn't autonomously process them
- Expected behavior: agent should execute complex tasks (spawn subagents, etc.)
Root Cause:
- systemEvents are injected as 'System: [timestamp] text' messages
- Standard heartbeat prompt is passive: 'Read HEARTBEAT.md... reply HEARTBEAT_OK'
- No explicit instruction to process and act on systemEvents
- Exec completion events already had directive prompt, but generic events didn't
Solution:
- Added SYSTEM_EVENT_PROMPT constant with directive language
- Modified heartbeat runner to detect generic (non-exec) systemEvents
- When generic events are present, use directive prompt instead of passive one
- Directive prompt explicitly instructs agent to:
1. Check HEARTBEAT.md for event-specific handling
2. Execute required actions (spawn subagent, perform task, etc.)
3. Reply HEARTBEAT_OK only if no action needed
Changes:
- src/infra/heartbeat-runner.ts:
- Added SYSTEM_EVENT_PROMPT constant (lines 100-105)
- Modified prompt selection logic to detect generic systemEvents (lines 510-523)
- Updated Provider field to reflect 'system-event' context
Testing:
- Build passes (TypeScript compilation successful)
- Pattern follows existing exec-event precedent
- Backward compatible (only affects sessions with pending systemEvents)
Follow-up:
- Add integration test for cron → subagent spawn workflow
- Update documentation on systemEvent best practices
- Enhanced SubagentRunOutcome type with errorType and errorHint fields
- Added categorizeError() helper to classify common error patterns:
* File system errors (ENOENT, EACCES, etc.)
* API/model errors (rate limits, auth failures, invalid requests)
* Network errors (connection refused, DNS failures)
* Timeout errors
* Configuration errors (missing credentials, quota limits)
- Updated error emission in agent-runner-execution.ts to categorize errors
- Updated subagent-registry.ts to capture and propagate new error fields
- Added buildErrorStatusLabel() helper for user-friendly error messages
- Error announcements now include error type and remediation hints
Example improved messages:
- Before: 'failed: unknown error'
- After: 'failed (tool error): ENOENT — File or directory not found'
This makes subagent failures much easier to understand and debug while
maintaining backward compatibility.
The message processing pipeline had a synchronization bug where
limitHistoryTurns() truncated conversation history AFTER
repairToolUseResultPairing() had already fixed tool_use/tool_result
pairings. This could split assistant messages (with tool_use) from
their corresponding tool_result blocks, creating orphaned tool_result
blocks that the Anthropic API rejects.
This fix calls sanitizeToolUseResultPairing() AFTER limitHistoryTurns()
to repair any pairings broken by truncation, ensuring the transcript
remains valid before being sent to the LLM API.
Changes:
- Added import for sanitizeToolUseResultPairing from session-transcript-repair.js
- Call sanitizeToolUseResultPairing() on the limited message array
- Updated variable name from 'limited' to 'repaired' for clarity
Resolves#4315. The slug-generator embedded run was hardcoded to use
DEFAULT_MODEL (claude-opus-4-5) regardless of the user's configured
agents.defaults.model.primary. This caused unexpected Opus charges on
every /new command.
Now uses resolveDefaultModelForAgent() to honor the user's configured
default model, falling back to DEFAULT_MODEL only when no config exists.
Replaces the previous ASCII art in both the CLI banner and the wizard header with a new, wider design and updates the label to 'OPENCLAW' for consistency.
What:
- resolve shell from PATH in bash-tools tests (avoid /bin/bash dependency)
- mock DNS for web-fetch SSRF tests (no real network)
- stub a2ui bundle in canvas-host server test when missing
Why:
- keep gateway test suite deterministic on Nix/Garnix Linux
Tests:
- not run locally (known missing deps in unit test run)
What:
- stub resolvePinnedHostname in web-fetch tests to avoid DNS flake
- close lock file handles via FileHandle.close during cleanup to avoid EBADF
Why:
- make CI deterministic without network/DNS dependence
- prevent double-close errors from GC
Tests:
- pnpm vitest run --config vitest.unit.config.ts src/agents/tools/web-tools.fetch.test.ts src/agents/session-write-lock.test.ts (failed: missing @aws-sdk/client-bedrock)
NTFS does not allow < or > in filenames, causing the XML filename
escaping test to fail on Windows CI with ENOENT.
Replace file<test>.txt with file&test.txt — & is valid on all platforms
and still requires XML escaping (&), preserving the test's intent.
Fixes#3748
Previous fix only checked skippedEmpty > 0, but when model returns
content: [] no payloads are created at all. Now also checks
replies.length === 0 to catch this case.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When running multiple Telegram bot accounts bound to different agents,
the /new command (and other slash commands) would send confirmation
messages via the wrong bot because the context was missing AccountId.
The fix adds AccountId: route.accountId to the context payload in
registerTelegramNativeCommands, matching how bot-message-context.ts
handles regular messages.
Fixes#2537
- Add msg.video_note to media extraction chain in bot/delivery.ts
- Add placeholder detection for video notes in bot-message-context.ts
- Video notes (rounded square video messages) are now processed and downloaded like regular videos
Fixes issue where video note messages were silently dropped because they weren't in the media handling logic.
Native slash commands (e.g. /verbose, /status) should not emit tool
summaries. Gate onToolResult behind CommandSource !== 'native' in
addition to the existing ChatType !== 'group' check.
Add test for native command exclusion.
- provides onToolResult in DM sessions (ChatType=direct)
- does not provide onToolResult in group sessions (ChatType=group)
- sends tool results via dispatcher in DM sessions
Replaces the old cross-provider test that expected onToolResult to
always be undefined.
875b018ea removed onToolResult from dispatch-from-config.ts to prevent
tool summaries leaking into group channels. However, this also broke
verbose tool summaries in DM/private sessions where they are expected.
This restores onToolResult but gates it behind ChatType !== 'group',
so group channels remain unaffected while DM verbose works again.
mirror=false is passed to sendPayloadAsync to avoid duplicating tool
summaries in the session transcript (matching the block reply behavior).
Fixes#2665
Add a `paths` option to `memorySearch` config, allowing users to
explicitly specify additional directories or files to include in
memory search.
Follow-up to #2961 as suggested by @gumadeiras — instead of auto-following
symlinks (which has security implications), users can now explicitly
declare additional search paths.
- Add `memorySearch.paths` config option (array of strings)
- Paths can be absolute or relative (resolved from workspace)
- Directories are recursively scanned for `.md` files
- Single `.md` files can also be specified
- Paths from defaults and agent overrides are merged
- Added 4 test cases for listMemoryFiles
* fix: Prevent XML attribute injection by escaping special characters in file name and MIME type attributes.
* fix: text attachment MIME misclassification with security hardening (#3628)
- Fix CSV/TSV inference from content heuristics
- Add UTF-16 detection and BOM handling
- Add XML attribute escaping for file output (security)
- Add MIME override logging for auditability
- Add comprehensive test coverage for edge cases
Thanks @frankekn
The pairing CLI calls listPairingChannels() at registration time,
which requires the plugin registry to be populated. Without this,
plugin-provided channels like Matrix fail with "does not support
pairing" even though they have pairing adapters defined.
This mirrors the existing pattern used by the plugins CLI entry.
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Add mappings for audio/x-m4a, audio/mp4, and video/quicktime to ensure
media files sent as documents are saved with proper extensions, enabling
automatic transcription/analysis tools to work correctly.
- audio/x-m4a → .m4a
- audio/mp4 → .m4a
- video/quicktime → .mov
Also adds comprehensive test coverage for extensionForMime().
Adds `messages` config option to session-memory hook (default: 15).
Fixes filter order bug - now filters user/assistant messages first,
then slices to get exactly N messages. Previously sliced first which
could result in fewer messages when non-message entries were present.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a new dmScope option that includes accountId in session keys,
enabling isolated sessions per channel account for multi-bot setups.
- Add 'per-account-channel-peer' to DmScope type
- Update session key generation to include accountId
- Pass accountId through routing chain
- Add tests for new routing behavior (13/13 passing)
Closes#3094
Co-authored-by: Sebastian Almeida <89653954+SebastianAlmeida@users.noreply.github.com>