Commit Graph

7069 Commits

Author SHA1 Message Date
artale
b4a90bb743 fix(telegram): suppress message_thread_id for private chat sends (#17242)
Private chats (positive numeric chat IDs) never support forum topics.
Sending message_thread_id to a private chat causes Telegram to reject
the request with '400: Bad Request: message thread not found', silently
dropping the message.

Guard all three send functions (sendMessageTelegram, sendStickerTelegram,
sendPollTelegram) to omit thread-related parameters when the target is a
private chat.

Root cause: the auto-reply pipeline can set messageThreadId from a
previous forum-group context, then reuse it when sending a DM.

Tests: add private-chat suppression assertions; update existing thread-
retry tests to use group chat IDs so the retry path is still exercised.
2026-02-17 00:01:26 +01:00
simonemacario
2ed43fd7b4 fix(cron): resolve accountId from agent bindings in isolated sessions
When an isolated cron session has no lastAccountId (e.g. first-run or
fresh session), the message tool receives an undefined accountId which
defaults to "default". In multi-account setups where accounts are named
(e.g. "willy", "betty"), this causes resolveTelegramToken() to fail
because accounts["default"] doesn't exist.

This change adds a fallback in resolveDeliveryTarget(): when the
session-derived accountId is undefined, look up the agent's bound
account from the bindings config using buildChannelAccountBindings().
This mirrors the same binding resolution used for inbound routing,
closing the gap between inbound and outbound account resolution.

Session-derived accountId still takes precedence when present.

Fixes #17889
Related: #12628, #16259

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:01:22 +01:00
Operative-001
e9f2e6a829 fix(heartbeat): prune transcript for HEARTBEAT_OK turns
When a heartbeat run results in HEARTBEAT_OK (or empty/duplicate), the user+assistant
turns are now pruned from the session transcript. This prevents context window
pollution from zero-information exchanges.

Implementation:
- captureTranscriptState(): records transcript file path and size before heartbeat
- pruneHeartbeatTranscript(): truncates file back to pre-heartbeat size
- Called in ok-empty, ok-token, and duplicate cases (same places as restoreHeartbeatUpdatedAt)

This extends the existing pattern where delivery is suppressed and updatedAt is restored
for HEARTBEAT_OK responses - now the transcript is also cleaned up.

Fixes #17804
2026-02-17 00:01:15 +01:00
artale
7bb9a7dcfc fix(telegram): wire sendPollTelegram into channel action handler (#16977)
The Telegram channel adapter listed no 'poll' action, so agents could
not create polls via the unified action interface. The underlying
sendPollTelegram function was already implemented but unreachable.

Changes:
- telegram.ts: add 'poll' to listActions (enabled by default via gate),
  add handleAction branch that reads pollQuestion/pollOption params and
  delegates to handleTelegramAction with action 'sendPoll'.
- telegram-actions.ts: add 'sendPoll' handler that validates question,
  options (≥2), and forwards to sendPollTelegram with threading, silent,
  and anonymous options.
- actions.test.ts: add test verifying poll action routes correctly.

Fixes #16977
2026-02-17 00:01:07 +01:00
amabito
068b9c9749 feat: wrap compaction generateSummary in retryAsync
Integrate retry logic with abort-classifier for /compact endpoint:
- Wrap generateSummary calls in retryAsync with exponential backoff
- Auto-skip retry on user cancellation and gateway restart (AbortError)
- Config: 3 attempts, 500ms-5s delay, 20% jitter
- Add comprehensive Vitest tests (5/5 passed)

Related: #16809, #5744, #17143
2026-02-17 00:01:03 +01:00
boris
f82a3d3e2b fix: use resolveUserPath utility for tilde expansion 2026-02-17 00:00:57 +01:00
boris
f70b3a2e68 refactor: bundle export-html templates instead of reading from node_modules
- Copy templates from pi-coding-agent into src/auto-reply/reply/export-html/
- Add build script to copy templates to dist/
- Remove fragile node_modules path traversal
- Templates are now self-contained (~250KB total)
2026-02-17 00:00:57 +01:00
boris
1eb1a33f37 chore: remove --open option (not useful for remote sessions) 2026-02-17 00:00:57 +01:00
boris
ffe700bf94 fix: use proper pi-mono dark theme colors for export HTML 2026-02-17 00:00:57 +01:00
boris
add3afb743 feat: add /export-session command
Export current session to HTML file with full system prompt included.
Uses pi-coding-agent templates for consistent rendering.

Features:
- Exports session entries + full system prompt + tools
- Saves to workspace by default, or custom path
- Optional --open flag to open in browser
- Reuses pi-mono export-html templates

Usage:
  /export-session           # Export to workspace
  /export-session ~/export  # Export to custom path
  /export-session --open    # Export and open in browser
2026-02-17 00:00:57 +01:00
Xinhua Gu
3c3a39d165 fix(test): use path.resolve for cross-platform Windows compatibility 2026-02-17 00:00:54 +01:00
Xinhua Gu
90774c098a fix(sessions): allow cross-agent session file paths in multi-agent setups
When OPENCLAW_STATE_DIR changes between session creation and resolution
(e.g., after reinstall or config change), absolute session file paths
pointing to other agents' sessions directories were rejected even though
they structurally match the valid .../agents/<agentId>/sessions/... pattern.

The existing fallback logic in resolvePathWithinSessionsDir extracts the
agent ID from the path and tries to resolve it via the current env's
state directory. When those directories differ, the containment check
fails. Now, if the path structurally matches the agent sessions pattern
(validated by extractAgentIdFromAbsoluteSessionPath), we accept it
directly as a final fallback.

Fixes #15410, Fixes #15565, Fixes #15468
2026-02-17 00:00:54 +01:00
8BlT
e20b87f1ba fix: handle forum/topics in Telegram DM thread routing (#17980)
resolveTelegramThreadSpec now checks isForum in the non-group path.
DMs with forum/topics enabled return scope 'forum' so each topic
gets its own session, while plain DM threads keep scope 'dm'.
2026-02-17 00:00:51 +01:00
SK Akram
c25c276e00 refactor: remove unnecessary optional chaining from agent meta usage in reply and cron modules 2026-02-17 00:00:47 +01:00
SK Akram
d649069184 fix: add optional chaining to runResult.meta accesses to prevent crashes on aborted runs 2026-02-17 00:00:47 +01:00
Operative-001
690ec492df refactor: remove redundant field assignments in resolveCronSession
Addresses Greptile review comment: when !isNewSession, the spread already
copies all entry fields. The explicit entry?.field assignments were
redundant and could cause confusion. Simplified to only override the
core fields (sessionId, updatedAt, systemSent).
2026-02-17 00:00:40 +01:00
Operative-001
57c8f62396 fix(cron): reuse existing sessionId for webhook/cron sessions
When a webhook or cron job provides a stable sessionKey, the session
should maintain conversation history across invocations. Previously,
resolveCronSession always generated a new sessionId and hardcoded
isNewSession: true, preventing any conversation continuity.

Changes:
- Check if existing entry has a valid sessionId
- Evaluate freshness using configured reset policy
- Reuse sessionId and set isNewSession: false when fresh
- Add forceNew parameter to override reuse behavior
- Spread existing entry to preserve conversation context

This enables persistent, stateful conversations for webhook-driven
agent endpoints when allowRequestSessionKey is configured.

Fixes #18027
2026-02-17 00:00:40 +01:00
Clawdbot
952db1a3e2 fix(discord): route audioAsVoice payloads through voice message API
deliverDiscordReply now checks payload.audioAsVoice and routes through
sendVoiceMessageDiscord instead of sendMessageDiscord when true.

This matches the existing Telegram behavior where audioAsVoice triggers
the voice message path (wantsVoice: true).

Fixes #17990
2026-02-17 00:00:34 +01:00
Peter Steinberger
9f0fc74d10 refactor(model): share normalized provider map lookups 2026-02-16 23:00:32 +00:00
Clawdbot
1fca7c3928 fix(discord): strip user:/discord:/pk: prefixes in command allowFrom
Discord's formatAllowFrom now strips these prefixes before matching,
aligning with normalizeDiscordAllowList behavior used in DM admission.

Before: commands.allowFrom: ["user:123"] → no match (senderCandidates: ["123", "discord:123"])
After: commands.allowFrom: ["user:123"] → "123" → matches sender "123"

Fixes #17937
2026-02-17 00:00:30 +01:00
Operative-001
6931ca7035 fix(subagent): route nested announce to parent even when parent run ended
When a depth-2 subagent (Birdie) completes and its parent (Newton) is a
depth-1 subagent, the announce should go to Newton, not bypass to the
grandparent (Jaris).

Previously, isSubagentSessionRunActive(Newton) returned false because
Newton's agent turn completed after spawning Birdie. This triggered the
fallback to grandparent even though Newton's SESSION was still alive and
waiting for child results.

Now we only fallback to grandparent if the parent SESSION is actually
deleted (no sessionId in session store). If the parent session exists,
we inject into it even if the current run has ended — this starts a new
agent turn to process the child result.

Fixes #18037

Test Plan:
- Added regression test: routes to parent when run ended but session alive
- Added regression test: falls back to grandparent only when session deleted
2026-02-17 00:00:27 +01:00
aether-ai-agent
235794d9f6 fix(security): OC-09 credential theft via environment variable injection
Implement comprehensive environment variable sanitization before Docker
container creation to prevent credential theft via post-exploitation
environment access.

Security Impact:
- Blocks 39+ sensitive credential patterns (API keys, tokens, passwords)
- Prevents exfiltration of ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
- Fail-secure validation with audit logging

Changes:
- Add sanitize-env-vars.ts with blocklist/allowlist validation
- Integrate sanitization into docker.ts (lines 273-294)
- Add validateEnvVars() to security validation
- Comprehensive test suite (62 tests, 100% pass rate)

Test Results: 62/62 passing
Code Review: 9.5/10 approved
Severity: HIGH (CWE-200, CVSS 7.5)

Signed-off-by: Aether AI Agent <github@tryaether.ai>
2026-02-17 00:00:23 +01:00
康熙
65a1787f92 fix: normalize paths to forward slashes for Windows RegExp compatibility
Windows path.relative() produces backslashes (e.g., memory\2026-02-16.md)
which fail to match RegExp patterns using forward slashes.

Normalize relative paths to forward slashes before RegExp matching
using rel.split(path.sep).join('/').

Fixes 4 test failures on Windows CI.
2026-02-17 00:00:20 +01:00
康熙
811c4f5e91 feat: add post-compaction read audit (Layer 3) 2026-02-17 00:00:20 +01:00
康熙
3296a25cc6 fix: format compaction-safeguard.ts with oxfmt 2026-02-17 00:00:20 +01:00
康熙
c4f829411f feat: append workspace critical rules to compaction summary
- Add readWorkspaceContextForSummary() to extract Session Startup + Red Lines from AGENTS.md
- Inject workspace context into compaction summary (limited to 2000 chars)
- Export extractSections() from post-compaction-context.ts for reuse
- Ensures compaction summary includes core rules needed for recovery

Part 1 of post-compaction context injection feature.
2026-02-17 00:00:20 +01:00
康熙
d0b33f23eb fix: improve section extraction robustness (case-insensitive, H3, code blocks) 2026-02-17 00:00:20 +01:00
康熙
90476d465d fix: format post-compaction-context test file 2026-02-17 00:00:20 +01:00
康熙
35a3e1b788 feat: inject post-compaction workspace context as system event (#18023) 2026-02-17 00:00:20 +01:00
artale
b1d5c71609 fix(cli): use standalone script for service restart after update (#17225)
The updater was previously attempting to restart the service using the
installed codebase, which could be in an inconsistent state during the
update process. This caused the service to stall when the updater
deleted its own files before the restart could complete.

Changes:
- restart-helper.ts: new module that writes a platform-specific restart
  script to os.tmpdir() before the update begins (Linux systemd, macOS
  launchctl, Windows schtasks).
- update-command.ts: prepares the restart script before installing, then
  uses it for service restart instead of the standard runDaemonRestart.
- restart-helper.test.ts: 12 tests covering all platforms, custom
  profiles, error cases, and shell injection safety.

Review feedback addressed:
- Use spawn(detached: true) + unref() so restart script survives parent
  process termination (Greptile).
- Shell-escape profile values using single-quote wrapping to prevent
  injection via OPENCLAW_PROFILE (Greptile).
- Reject unsafe batch characters on Windows.
- Self-cleanup: scripts delete themselves after execution (Copilot).
- Add tests for write failures and custom profiles (Copilot).

Fixes #17225
2026-02-17 00:00:16 +01:00
artale
a62ff19a66 fix(agent): isolate last-turn total in token usage reporting (#17016)
recordAssistantUsage accumulated cacheRead across the entire multi-turn
run, and totalTokens was clamped to contextTokens. This caused
session_status to report 100% context usage regardless of actual load.

Changes:
- run.ts: capture lastTurnTotal from the most recent model call and
  inject it into the normalized usage before it reaches agentMeta.
- usage-reporting.test.ts: verify usage.total reflects current turn,
  not accumulated total.

Fixes #17016
2026-02-17 00:00:12 +01:00
OpenClaw Bot
d6acd71576 fix: session-memory hook finds previous session file after /new/reset
When /new or /reset is triggered, the session file gets rotated
before the hook runs. The hook was reading the new (empty) file
instead of the previous session content.

This fix:
1. Checks if the session file looks like a reset file (.reset.)
2. Falls back to finding the most recent non-reset .jsonl file
3. Logs debug info about which file was used

Fixes openclaw/openclaw#18088
2026-02-17 00:00:08 +01:00
OpenClaw Bot
068260bbea fix: add api-version query param for Azure verification 2026-02-17 00:00:08 +01:00
OpenClaw Bot
960cc11513 fix: add Azure AI Foundry URL support for custom providers
Detects Azure AI Foundry URLs (services.ai.azure.com and
openai.azure.com) and transforms them to include the proper
deployment path (/openai/deployments/<model-id>) required by
Azure's API. This fixes the 400 error when configuring OpenAI
models from Azure AI Foundry.

Fixes openclaw/openclaw#17992
2026-02-17 00:00:08 +01:00
Rain
4e5a9d83b7 fix(gateway): preserve unbracketed IPv6 host headers 2026-02-17 00:00:03 +01:00
Iron9521
8e55503d77 fix(browser): track original port mapping for EADDRINUSE fallback
Address review feedback: when port fallback occurs, maintain mapping from
original requested port to the relay server for proper cleanup and reuse.

- Add relayByOriginalPort map to track original port -> relay
- Update ensureChromeExtensionRelayServer to check both maps
- Update stopChromeExtensionRelayServer to clean up both mappings
- Stop function now uses the relay's actual bound port for auth cleanup
2026-02-16 23:59:59 +01:00
Iron
0e6daa2e6e fix(browser): handle EADDRINUSE with automatic port fallback
When the Chrome extension relay server fails to bind due to port
conflict (EADDRINUSE), automatically try alternative ports in the
dynamic range (49152-65535) instead of failing immediately.

This resolves issues where stale processes hold onto port 18792
after gateway restarts or crashes.

Fixes potential issues related to #8926, #13867, #17584
2026-02-16 23:59:59 +01:00
artale
a1a1f56841 fix(process): disable detached spawn on Windows to fix empty exec output (#18035)
The supervisor's child adapter always spawned with `detached: true`,
which creates a new process group. On Windows Scheduled Tasks (headless,
no console), this prevents stdout/stderr pipes from properly connecting,
causing all exec tool output to silently disappear.

The old exec path (pre-supervisor refactor) never used `detached: true`.
The regression was introduced in cd44a0d01 (refactor process spawning).

Changes:
- child.ts: set `detached: false` on Windows, keep `detached: true` on
  POSIX (where it's needed to survive parent exit). Skip the no-detach
  fallback on Windows since it's already the default.
- child.test.ts: platform-aware assertions for detached behavior.

Fixes #18035
Fixes #17806
2026-02-16 23:59:53 +01:00
Operative-001
d0a5ee0176 fix: include token drift warning in JSON response
Address review feedback - when --json mode is used, the drift warning
was completely suppressed. Now it's included in the warnings array
of the DaemonActionResponse so programmatic consumers can surface it.
2026-02-16 23:59:50 +01:00
Operative-001
d6e85aa6ba fix(daemon): warn on token drift during restart (#18018)
When the gateway token in config differs from the token embedded in the
service plist/unit file, restart will not apply the new token. This can
cause silent auth failures after OAuth token switches.

Changes:
- Add checkTokenDrift() to service-audit.ts
- Call it in runServiceRestart() before restarting
- Warn user with suggestion to run 'openclaw gateway install --force'

Closes #18018
2026-02-16 23:59:50 +01:00
Marcus Widing
8af4712c40 fix(cron): prevent spin loop when job completes within scheduled second (#17821)
When a cron job fires and completes within the same wall-clock second it
was scheduled for, the next-run computation could return undefined or the
same second, causing the scheduler to re-trigger the job hundreds of
times in a tight loop.

Two-layer fix:

1. computeJobNextRunAtMs: When computeNextRunAtMs returns undefined for a
   cron-kind schedule (edge case where floored nowSecondMs matches the
   schedule), retry with the ceiling (next second) as reference time.
   This ensures we always get the next valid occurrence.

2. applyJobResult: Add MIN_REFIRE_GAP_MS (2s) safety net for cron-kind
   jobs.  After a successful run, nextRunAtMs is guaranteed to be at
   least 2s in the future.  This breaks any remaining spin-loop edge
   cases without affecting normal daily/hourly schedules (where the
   natural next run is hours/days away).

Fixes #17821
2026-02-16 23:59:44 +01:00
Glucksberg
cd4f7524e3 feat(telegram): receive and surface user message reactions (#10075) 2026-02-16 23:59:36 +01:00
Rain
d3698f4eb6 fix(gateway): trim trusted proxy entries before matching 2026-02-16 23:59:32 +01:00
Vishal Doshi
e91a5b0216 fix: release stale session locks and add watchdog for hung API calls (#18060)
When a model API call hangs indefinitely (e.g. Anthropic quota exceeded
mid-call), the gateway acquires a session .jsonl.lock but the promise
never resolves, so the try/finally block never reaches release(). Since
the owning PID is the gateway itself, stale detection cannot help —
isPidAlive() always returns true.

This commit adds four layers of defense:

1. **In-process lock watchdog** (session-write-lock.ts)
   - Track acquiredAt timestamp on each held lock
   - 60-second interval timer checks all held locks
   - Auto-releases any lock held longer than maxHoldMs (default 5 min)
   - Catches the hung-API-call case that try/finally cannot

2. **Gateway startup cleanup** (server-startup.ts)
   - On boot, scan all agent session directories for *.jsonl.lock files
   - Remove locks with dead PIDs or older than staleMs (30 min)
   - Log each cleaned lock for diagnostics

3. **openclaw doctor stale lock detection** (doctor-session-locks.ts)
   - New health check scans for .jsonl.lock files
   - Reports PID status and age of each lock found
   - In --fix mode, removes stale locks automatically

4. **Transcript error entry on API failure** (attempt.ts)
   - When promptError is set, write an error marker to the session
     transcript before releasing the lock
   - Preserves conversation history even on model API failures

Closes #18060
2026-02-16 23:59:22 +01:00
Rodrigo Uroz
7d8d8c338b config: align memory hybrid UI metadata with schema labels/help 2026-02-16 23:59:19 +01:00
Rodrigo Uroz
65ad9a4262 Memory: fix MMR tie-break and temporal timestamp dedupe 2026-02-16 23:59:19 +01:00
Rodrigo Uroz
33cf27a52a fix: MMR default disabled, tie-break null guard, correct docs URL
- DEFAULT_MMR_CONFIG.enabled = false (opt-in, was incorrectly true)
- Tie-break: handle bestItem === null so first candidate always wins
- CHANGELOG URL: docs.clawd.bot → docs.openclaw.ai
- Tests updated to pass enabled: true explicitly where needed
2026-02-16 23:59:19 +01:00
Rodrigo Uroz
6b3e0710f4 feat(memory): Add opt-in temporal decay for hybrid search scoring
Exponential decay (half-life configurable, default 30 days) applied
before MMR re-ranking. Dated daily files (memory/YYYY-MM-DD.md) use
filename date; evergreen files (MEMORY.md, topic files) are not
decayed; other sources fall back to file mtime.

Config: memorySearch.query.hybrid.temporalDecay.{enabled, halfLifeDays}
Default: disabled (backwards compatible, opt-in).
2026-02-16 23:59:19 +01:00
Rodrigo Uroz
fa9420069a feat(memory): Add MMR re-ranking for search result diversity
Adds Maximal Marginal Relevance (MMR) re-ranking to hybrid search results.

- New mmr.ts with tokenization, Jaccard similarity, and MMR algorithm
- Integrated into mergeHybridResults() with optional mmr config
- 40 comprehensive tests covering edge cases and diversity behavior
- Configurable lambda parameter (default 0.7) to balance relevance vs diversity
- Updated CHANGELOG.md and memory docs

This helps avoid redundant results when multiple chunks contain similar content.
2026-02-16 23:59:19 +01:00
Rain
a0ab301dc3 Fix Discord auto-thread attempting to thread in Forum/Media channels\n\nCreating threads on messages within Forum/Media channels is often redundant\nor invalid (as messages are already posts). This prevents API errors and spam.\n\nFix: Check channel type before attempting auto-thread creation. 2026-02-16 23:59:16 +01:00