Skip to content

feat(telegram): expose forum topic names in agent context#65973

Merged
obviyus merged 3 commits intoopenclaw:mainfrom
ptahdunbar:feat/telegram-forum-topic-names
Apr 13, 2026
Merged

feat(telegram): expose forum topic names in agent context#65973
obviyus merged 3 commits intoopenclaw:mainfrom
ptahdunbar:feat/telegram-forum-topic-names

Conversation

@ptahdunbar
Copy link
Copy Markdown
Contributor

@ptahdunbar ptahdunbar commented Apr 13, 2026

Summary

  • Problem: Telegram Bot API doesn't expose a method to look up forum topic names by message_thread_id. Agents only see a numeric topic_id (e.g. 563) with no way to know the human-readable name.
  • Why it matters: Agents need topic names to provide contextual responses, auto-match topic-specific config/files, and display meaningful labels — all impossible with just a numeric ID.
  • What changed:
    • New bounded in-memory LRU cache (topic-name-cache.ts) populated from forum_topic_created, forum_topic_edited, forum_topic_closed, forum_topic_reopened service messages
    • Fallback seed from reply_to_message.forum_topic_created for topics created before the bot started
    • TopicName added to MsgContext (available as {{TopicName}} in templates)
    • topic_name added to the agent prompt metadata block (alongside existing topic_id)
    • topicName propagated to plugin hook event metadata
  • What did NOT change: Existing MessageThreadId, IsForum, topic_id fields. Non-forum messages. Other channels. No new dependencies.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

N/A — new feature, not a bug fix.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/telegram/src/topic-name-cache.test.ts
  • Scenario the test should lock in: Cache stores/retrieves topic names, handles renames, tracks close/reopen, respects LRU eviction at 2048 entries
  • Why this is the smallest reliable guardrail: The cache is a pure function module with no I/O; unit tests cover all branches including eviction
  • If no new test is added, why not: New tests ARE added (9 test cases)

User-visible / Behavior Changes

  • topic_name now appears in the agent's "Conversation info (untrusted metadata)" block for Telegram forum topic messages. Example: topic_name: "Design-learning" alongside topic_id: "42".
  • {{TopicName}} is available in prompt templates.
  • Plugin hooks (message:received, claim events, internal contexts) now include topicName in metadata.
  • Cache is in-memory only; after a gateway restart, falls back to the creation-time name from reply_to_message.forum_topic_created until a live rename event repopulates.

Diagram (if applicable)

Before:
[telegram msg in topic] -> agent sees: topic_id: "563"

After:
[telegram msg in topic] -> topic-name-cache lookup -> agent sees: topic_id: "563", topic_name: "Design-learning"

Cache population:
[forum_topic_created msg] -> updateTopicName(chatId, threadId, {name})
[forum_topic_edited msg]  -> updateTopicName(chatId, threadId, {name})  (captures renames)
[forum_topic_closed msg]  -> updateTopicName(chatId, threadId, {closed: true})
[regular msg with reply_to_message.forum_topic_created] -> seed cache if empty (creation-time name)

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS (dev) + Amazon Linux 2023 on EC2 (VPS test)
  • Runtime: Node.js 22
  • Integration/channel: Telegram (forum supergroup)
  • Relevant config: Standard OpenClaw Telegram config with forum supergroup enabled

Steps

  1. Deploy OpenClaw with this change to a VPS connected to a Telegram bot
  2. Create or use an existing forum topic in a supergroup where the bot is a member
  3. Send a message in the topic
  4. Observe the agent's response — it should reference the topic name
  5. Rename the topic, send another message — agent should reflect the new name

Expected

  • topic_name populated in agent prompt metadata with the current topic name
  • Renames captured live from forum_topic_edited service messages

Actual

  • Verified working end-to-end on live Telegram forum supergroup

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Test results:

pnpm test:extension telegram
 Test Files  91 passed (91)
      Tests  1224 passed (1224)

pnpm test:contracts
 exit_code: 0

Debug trace confirming rename capture:

[TOPIC-DEBUG] chat=-1003633263090 thread=563 ftEdited={"name":"GLM vs Gemma vs M2.7"}
[TOPIC-DEBUG] stored from ftEdited: "GLM vs Gemma vs M2.7"
[TOPIC-DEBUG] final resolved name for -1003633263090:563 = "GLM vs Gemma vs M2.7"

Human Verification (required)

  • Verified scenarios: forum_topic_created seeding from reply_to_message, forum_topic_edited live rename capture, topic_name appearing in agent prompt metadata, agent referencing topic name in natural language response
  • Edge cases checked: cold cache (restart then fallback to creation-time name), LRU eviction at 2048 entries (unit test), missing message_thread_id, non-forum chats
  • What I did not verify: forum_topic_closed / forum_topic_reopened live (unit tested only), other channels unaffected (code inspection only)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — new optional field, existing behavior unchanged
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: Cache is in-memory only; topic names lost on restart
    • Mitigation: Fallback seed from reply_to_message.forum_topic_created provides creation-time name immediately. Live renames repopulate the cache. Persistence could be added as a follow-up.
  • Risk: Eviction scan is O(n) over cache entries
    • Mitigation: Cache is capped at 2048 entries; linear scan over 2048 entries is sub-millisecond. Only runs when cache is full and a new entry is inserted.

AI-Assisted PR Disclosure

  • This PR was AI-assisted (Cursor + Claude)
  • Testing level: fully tested — unit tests + live Telegram verification on VPS
  • I understand what the code does
  • Bot review conversations will be resolved as addressed

Addresses all feedback from the prior closed PR #36916:

  1. FIFO eviction → true LRU via updatedAt timestamp (not Map insertion order)
  2. Unsafe Record<string, unknown> cast → uses grammy's native typed fields directly
  3. topic_name missing from prompt metadata (P1) → added to inbound-meta.ts + plugin hooks
  4. Added reply_to_message.forum_topic_created seed (not in feat(telegram): include forum topic name in inbound metadata #36916)
  5. Added forum_topic_closed/forum_topic_reopened lifecycle handling (not in feat(telegram): include forum topic name in inbound metadata #36916)

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: e33950bd23

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/telegram/src/topic-name-cache.ts Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 13, 2026

Greptile Summary

Adds in-memory LRU caching of Telegram forum topic names, populating the cache from forum_topic_created/forum_topic_edited service messages and a reply_to_message fallback seed, then surfacing the resolved name via MsgContext.TopicName, the agent prompt topic_name metadata field, and plugin hook event metadata. The implementation is well-structured with a clean bounded cache and correct merge semantics in updateTopicName.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/test suggestions that do not affect correctness.

The cache implementation is correct (bounded eviction, merge semantics, empty-name guard, timestamp ordering). Service-message population and the reply_to_message fallback are ordered correctly and well-guarded. The only gap is a missing eviction unit test that the PR description incorrectly marks as done — not a blocking correctness issue.

extensions/telegram/src/topic-name-cache.test.ts — eviction path is untested despite being claimed in the test plan.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/telegram/src/topic-name-cache.test.ts
Line: 13

Comment:
**Missing eviction test**

The PR description's test plan checks off "LRU eviction" as covered, but no such test exists in this file. The eviction path in `evictOldest` is the one non-trivial branch that isn't exercised. Consider adding something like:

```ts
it("evicts the oldest entry when MAX_ENTRIES is exceeded", async () => {
  const OVER = 2049;
  for (let i = 0; i < OVER; i++) {
    updateTopicName(-100000, i, { name: `Topic ${i}` });
  }
  expect(topicNameCacheSize()).toBe(2048);
});
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Merge branch 'openclaw:main' into feat/t..." | Re-trigger Greptile

Comment thread extensions/telegram/src/topic-name-cache.test.ts
@ptahdunbar ptahdunbar force-pushed the feat/telegram-forum-topic-names branch 3 times, most recently from fc7c90b to 7b01de7 Compare April 13, 2026 15:28
@obviyus obviyus self-assigned this Apr 13, 2026
ptahdunbar and others added 3 commits April 13, 2026 23:36
Telegram Bot API does not provide a method to look up forum topic names
by thread ID. This adds an in-memory LRU cache that learns topic names
from service messages (forum_topic_created, forum_topic_edited,
forum_topic_closed, forum_topic_reopened) and seeds from
reply_to_message.forum_topic_created as a fallback for pre-existing
topics.

The resolved topic name is surfaced as:
- TopicName in MsgContext (available to {{TopicName}} in templates)
- topic_name in the agent prompt metadata block
- topicName in plugin hook event metadata

Includes unit tests for the topic-name-cache module (11 tests including
eviction and read-recency).

Known limitation: cache is in-memory only; after a restart it falls back
to the creation-time name until a rename event is observed.
@obviyus obviyus force-pushed the feat/telegram-forum-topic-names branch from b183379 to 82117ff Compare April 13, 2026 18:07
Copy link
Copy Markdown
Contributor

@obviyus obviyus left a comment

Choose a reason for hiding this comment

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

Verified the Telegram forum-topic path now surfaces human topic names from service-message/reply metadata through inbound context, prompt metadata, and hook metadata.

Maintainer follow-up: distilled the topic-name flow into one local path, trimmed excess cache comments, and added direct tests for context payload, prompt metadata, and hook metadata.

Local gate: pnpm test extensions/telegram/src/bot-message-context.dm-threads.test.ts src/auto-reply/reply/inbound-meta.test.ts src/hooks/message-hook-mappers.test.ts

@obviyus obviyus merged commit 8c43768 into openclaw:main Apr 13, 2026
8 checks passed
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Apr 13, 2026

Landed on main.

Thanks @ptahdunbar.

Kyzcreig added a commit to Kyzcreig/openclaw that referenced this pull request Apr 14, 2026
* fix(matrix): sync runtime dependency lockfile

* fix(matrix): mirror staged runtime dependencies

* fix(release): allow matrix runtime pack size

* [codex] Fix LM Studio header-auth follow-ups (openclaw#65806)

* fix: harden lmstudio header auth handling

* fix: suppress lmstudio shell env auth

* fix(docker): install bundled plugin deps after prune

* fix: extract shared session status runtime (openclaw#65807)

Merged via squash.

Prepared head SHA: f027bd6
Co-authored-by: dutifulbob <261991368+dutifulbob@users.noreply.github.com>
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
Reviewed-by: @osolmaz

* fix: preserve qmd command paths

* fix: harden qmd service startup

* test(release): align pack size budget assertion

* feat: Streamline Feishu channel onboarding with QR code scan-to-create flow (openclaw#65680)

Add QR-based app registration, improve Feishu onboarding flows, support direct login entry, add group chat policy setup, reduce log noise, and update docs.

* fix(ci): verify bundled plugin runtime deps

* fix(feishu): avoid sdk facade cycles

* fix(feishu): keep channel auth on local api barrel

* fix(feishu): break auth login barrel cycle

* fix(feishu): guard app registration fetches

* test: fix macos parallels gateway fallback

* ci: add stable npm dist-tag sync

* docs: clarify npm dist-tag auth

* feat(docs): add Hostinger installation guide and link in VPS document… (openclaw#65904)

* Run context-engine turn maintenance as idle-aware background work (openclaw#65233)

Merged via squash.

Prepared head SHA: e9f6c67
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(logging) add failover log source and target (openclaw#65955)

* clarify failover log source and target

* fix embedded runner final assistant raw text helper

* feat(plugin-sdk): add claimable dedupe helper

* refactor(line): share replay dedupe guard

* refactor(feishu): reuse persistent dedupe lookups

* fix(qr): lazy load terminal runtime modules

* test(wizard): mock auth profile runtime seam

* fix(qr): lazy load terminal ascii renderer

* perf(wizard): keep explicit skip auth path cold

* refactor(feishu): share synthetic event dedupe claims

* perf(daemon): keep install auth env path cold

* fix(nextcloud-talk): release replay claims on handler failure

* fix(plugin-sdk): serialize claimable dedupe races

* fix(plugins): treat context-engine plugins as capabilities in status/inspect (openclaw#58766)

Merged via squash.

Prepared head SHA: 23269d2
Co-authored-by: zhuisDEV <95547369+zhuisDEV@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(nextcloud-talk): make replay retries explicit

* fix(zalo): make replay retries explicit

* fix: validate resolved context engine contracts (openclaw#63222)

Merged via squash.

Prepared head SHA: 5f3a15c
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(mattermost): make replay retries explicit

* fix(whatsapp): make inbound retries explicit

* perf(secrets): fast-path explicit channel target lookup

* perf(cli): skip redundant schema passes for structured dry runs

* fix(discord): make inbound retries explicit

* perf(agents): keep fallback auth store cold without sources

* fix(slack): make inbound retries explicit

* fix(matrix): make delivery replay retries explicit

* fix(line): make webhook replay retries explicit

* fix(feishu): make card action retries explicit

* fix(whatsapp): await write stream finish before returning encFilePath (openclaw#65896)

* fix(whatsapp): await write stream finish in encryptedStream to fix race-condition ENOENT crash

* fix(whatsapp): ship Baileys media hotfix on npm installs

* fix(whatsapp): keep Baileys hotfix postinstall best-effort

* fix(whatsapp): harden Baileys postinstall temp writes

* fix(whatsapp): preserve Baileys hotfix file mode

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>

* perf(config): reuse validated best-effort snapshots

* perf(infra): cache login shell env probes

* fix(gateway): harden service entrypoint resolution (openclaw#65984)

Merged via squash.

Prepared head SHA: 31cbc33
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* fix(feishu): make bot menu retries explicit

* fix(feishu): keep comment replay closed after generic failures

* perf(cron): lazy-load isolated cli runner runtime

* fix(plugin-sdk): avoid leaking queue rejection cleanup

* fix(tasks): avoid leaking scheduled sweep failures

* perf(cron): keep auth profile runtime cold

* fix(telegram): swallow update watermark persistence failures

* perf(cron): narrow session runtime imports

* fix(auto-reply): release inbound dedupe after dispatch errors

* fix(auto-reply): avoid leaking inbound debounce cleanup

* perf(cron): isolate runtime-heavy seams

* fix(telegram): avoid leaking thread binding persist cleanup

* perf(cron): lazy-load embedded runtime branch

* fix: stop repeated unknown-tool loops (openclaw#65922)

Merged via squash.

Prepared head SHA: f352a27
Reviewed-by: @osolmaz

* fix(runtime): avoid leaking detached cleanup promises

* perf(cron): trim unused runtime barrel exports

* fix(tlon): release replay claims after handler failures

* fix(nostr): retry inbound events after handler failures

* perf(cron): narrow live switch error import

* perf(agents): keep model fallback auth runtime cold

* fix(telegram): block watermark advancement past failed updates

* perf(cron): lazy-load context and catalog lookups

* perf(cron): use session store read path

* perf(cron): lazy-load delivery subagent registry

* fix(voice-call): retry rejected inbound hangups

* perf(cron): lazy-load skills snapshot runtime

* fix(telegram): defer replay commit until update succeeds

* fix(voice-call): keep unknown-call replays retryable

* perf(outbound): use read-only channel registry seam

* perf(outbound): use loaded-only channel plugin reads

* fix(telegram): retry failed group migration updates

* perf(outbound): isolate id-like target resolution

* fix(telegram): retry failed reaction updates

* perf(agents): narrow failover helper imports

* perf(plugins): isolate manifest registry cache state

* perf(auth-profiles): narrow source check path imports

* fix(telegram): retry failed model callbacks

* fix(heartbeat): preserve Telegram topic routing for isolated heartbeats (openclaw#66035)

Merged via squash.

Prepared head SHA: 83b986a
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* fix(telegram): retry failed commands pagination callbacks

* perf(channels): isolate loaded target parsing

* perf(outbound): narrow loaded target channel reads

* perf(utils): isolate message channel normalization

* fix(telegram): retry failed plugin binding callbacks

* perf(cron): lazy-load delivery logger runtime

* fix(telegram): retry failed pagination preflight

* fix(telegram): retry failed model browser callbacks

* perf(channels): read bundled channel metadata directly

* perf(cron): use read-only allow-from store seam

* fix(mattermost): dedupe repeated model picker selects

* fix(browser): unblock managed loopback CDP startup and control (openclaw#66043)

Merged via squash.

Prepared head SHA: c3d0a99
Reviewed-by: @mbelinky

* perf(sessions): use loaded thread-info seam

* fix(voice-call): keep retryable errors replayable

* perf(cron): narrow execution and skill runtime imports

* fix(telegram): retry failed model selections

* perf(cron): use narrow bound-account lookup

* fix(telegram): retry failed approval callbacks

* test(cron): mock skills snapshot runtime seam

* fix(plugins): serialize interactive callback dedupe

* fix(nostr): dedupe deterministic rejected events

* perf(cron): drop stale skill snapshot runtime exports

* perf(cron): use lightweight model selection resolver

* perf(agents): use lightweight model fallback selection helpers

* perf(cron): use narrow verbose-level runtime seam

* Gateway/sessions: preserve shared session route on system events (openclaw#66073)

Merged via squash.

Prepared head SHA: 314a935
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* perf(channels): split hot-path message channel normalization

* perf(sessions): isolate reset policy helpers

* perf(cron): lazy-load run executor runtime

* perf(cron): lazy-load external content runtime

* perf(agents): isolate thinking default helper

* fix(browser): detect local attachOnly loopback CDP sessions (openclaw#66080)

Merged via squash.

Prepared head SHA: 90c1c10
Reviewed-by: @mbelinky

* perf(agents): isolate agent scope config helpers

* fix(session): clear stale thread route on system events

* perf(cron): lazy-load delivery runtime helpers

* perf(cron): keep skill filter runtime lazy

* fix(bluebubbles): lazy-refresh Private API status on send (openclaw#43764) (openclaw#65447)

* fix(bluebubbles): lazy refresh Private API cache on send to prevent silent reply threading degradation (openclaw#43764)

When the 10-minute server info cache expires, sends requesting reply
threading or effects silently degrade to plain messages. Add a lazy
async refresh of the cache in the send path when Private API features
are needed but status is unknown, preserving graceful degradation if
the refresh fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(bluebubbles): apply lazy Private API refresh to attachment sends and add missing test coverage (openclaw#43764)

Attachment sends had the same cache-expiry bug as text sends: when the
10-minute Private API status cache TTL expired, reply threading metadata
was silently dropped. Apply the same lazy-refresh pattern from send.ts.

Also add the missing "refresh succeeds with private_api: false" test case
for both send.ts and attachments.ts — proves effects throw and reply
threading degrades without the "unknown" warning when the API is explicitly
disabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update no-raw-channel-fetch allowlist for test-harness line shift

Adding fetchBlueBubblesServerInfo to the probe mock module shifted
globalThis.fetch in test-harness.ts from line 128 to 130.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(telegram): add inbound retry regressions (openclaw#66075)

Merged via squash.

Prepared head SHA: 175cd25
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

* feat(telegram): expose forum topic names in agent context

Telegram Bot API does not provide a method to look up forum topic names
by thread ID. This adds an in-memory LRU cache that learns topic names
from service messages (forum_topic_created, forum_topic_edited,
forum_topic_closed, forum_topic_reopened) and seeds from
reply_to_message.forum_topic_created as a fallback for pre-existing
topics.

The resolved topic name is surfaced as:
- TopicName in MsgContext (available to {{TopicName}} in templates)
- topic_name in the agent prompt metadata block
- topicName in plugin hook event metadata

Includes unit tests for the topic-name-cache module (11 tests including
eviction and read-recency).

Known limitation: cache is in-memory only; after a restart it falls back
to the creation-time name until a rename event is observed.

* refactor(telegram): distill topic name flow

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* perf(config): skip shell env fallback for explicit empty vars

* fix(cron): stop unresolved next-run refire loops (openclaw#66083)

Merged via squash.

Prepared head SHA: b86ba58

* perf(daemon): lazy-load auth profile install helpers

* fix(config): redact sourceConfig and runtimeConfig alias fields in redactConfigSnapshot [AI] (openclaw#66030)

* fix: address issue

* docs: add changelog entry for PR merge

* perf(daemon): slim gateway install token imports

* fix(msteams): enforce sender allowlist checks on SSO signin invokes [AI] (openclaw#66033)

* fix: address issue

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* perf(daemon): import install config helpers directly

* fix(browser): enforce SSRF policy on snapshot, screenshot, and tab routes [AI] (openclaw#66040)

* fix: address issue

* fix: address review feedback

* fix: finalize issue changes

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* perf(config): reuse prepared snapshots for daemon token writes

* perf(config): defer legacy web search registry reads

* Lobster: import published core runtime (openclaw#64755)

* Lobster: import published core runtime

* Changelog: add Lobster core runtime note

* Lobster: type embedded core runtime

* Lobster: keep package-boundary tsconfig narrow

* perf(config): use direct writes for gateway token persistence

* fix(heartbeat): force owner downgrade for untrusted hook:wake system events [AI-assisted] (openclaw#66031)

* fix: address issue

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* fix(cron): preserve unresolved next-run backoff (openclaw#66113)

Merged via squash.

Prepared head SHA: a553daa
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* agents: stop strict mode from hijacking chat turns

* perf(secrets): lazy-load provider env var exports

* fix: classify openrouter json 404 model errors

Rewrites the stale branch on top of current `main` and preserves the original issue as regression coverage for the exact OpenRouter JSON 404 payload from openclaw#51571.

No production behavior changes are introduced here; current `main` already classifies this payload as `model_not_found`, and this merge locks that in across the shared matcher, failover classifier, and fallback loop.

Co-authored-by: 屈定 <mrdear@users.noreply.github.com>
Co-authored-by: Altay <altay@uinaf.dev>

* test(cron): fix openclaw#66019 maintenance regression coverage (openclaw#66122)

Merged via squash.

Prepared head SHA: 7f2a604

* perf(config): use generated SecretRef policy metadata

* perf(config): skip cold runtime refresh on one-shot writes

* fix(trace command): Improve trace raw diagnostics and trace command UX (openclaw#66089)

* improve trace raw diagnostics and command acks

* address trace review feedback

* avoid sync transcript reads in raw trace

* preserve raw cli output for trace

* gate trace emission at reply time

* reflect raw trace mode in status surfaces

* fix(outbound): replay queued session context (openclaw#66025)

* fix(outbound): preserve replay session context

* fix(outbound): remove user work log

* changelog: note outbound session-context replay fix (openclaw#66025)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* fix(queue): split collect batches by auth context (openclaw#66024)

* fix(queue): split collect batches by auth context

Co-authored-by: zsx <git@zsxsoft.com>

* fix(queue): keep overflow summary on splits

* fix(queue): preserve grouped collect retry semantics

* fix(queue): drop USER.md from pr

* fix(queue): keep overflow summary in first auth group

* fix(queue): clear overflow summary state after first auth group

* fix(queue): narrow auth split key

* fix(queue): flush collect summary-only drains

* changelog: note collect-mode auth-context batch split (openclaw#66024)

---------

Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>

* perf(config): narrow channel legacy rule loading

* perf(config): scope dry-run legacy validation

* perf(agents): lazy-load cli runner seams

* perf(commands): narrow session test imports

* fix(memory-core): run Dreaming once per cron schedule (openclaw#66139)

Merged via squash.

Prepared head SHA: 48229a2
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* perf(commands): narrow agent config imports

* perf(commands): lazy-load agent secret resolution

* [codex] fix(ui): guard dreaming wiki plugin calls (openclaw#66140)

Merged via squash.

Prepared head SHA: 030562b
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* perf(agents): keep attempt execution runtime cold

* perf(agents): lazy-load delivery runtime

* perf(agents): lazy-load session store updates

* perf(agents): narrow session helper imports

* fix: allow plugin commands on Slack when channel supports native commands (openclaw#64578)

Merged via squash.

Prepared head SHA: 2ec97bf
Co-authored-by: rafaelreis-r <57492577+rafaelreis-r@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* perf(config): keep runtime compat migrations lightweight

* fix(ui): preserve user-selected session on reconnect and tab switch (openclaw#59611) thanks @loong0306

Fixes openclaw#57072 — chat UI state desync after route navigation.

- applySessionDefaults() now detects user-selected sessions and preserves them on reconnect
- Chat tab session switching consolidated to use switchChatSession() helper
- Overview session-key handler uses shared resetChatStateForSessionSwitch to prevent stale state leaks
- Session select dropdowns now set ?selected to reflect actual state

Co-authored-by: loong0306 <loong0306@gmail.com>
Co-authored-by: Nova <nova@openknot.ai>

* fix: count unknown-tool retries only when streamed (openclaw#66145)

Merged via squash.

Prepared head SHA: b79209c
Co-authored-by: Bob <dutifulbob@gmail.com>
Reviewed-by: @osolmaz

* fix(active-memory): Move active memory recall into the hidden prompt prefix (openclaw#66144)

* move active memory into prompt prefix

* document active memory prompt prefix

* strip active memory prefixes from recall history

* harden active memory prompt prefix handling

* hide active memory prefix in leading history views

* strip hidden memory blocks after prompt merges

* preserve user turns in memory recall cleanup

* fix(ui): replace marked.js with markdown-it to fix ReDoS UI freeze (openclaw#46707) thanks @zhangfnf

Replace marked.js with markdown-it for the control UI chat markdown renderer
to eliminate a ReDoS vulnerability that could freeze the browser tab.

- Configure markdown-it with custom renderers matching marked.js output
- Add GFM www-autolink with trailing punctuation stripping per spec
- Escape raw HTML via html_block/html_inline overrides
- Flatten remote images to alt text, preserve base64 data URI images
- Add task list support via markdown-it-task-lists plugin
- Trim trailing CJK characters from auto-linked URLs (RFC 3986)
- Keep marked dependency for agents-panels-status-files.ts usage

Co-authored-by: zhangfan49 <zhangfan49@baidu.com>
Co-authored-by: Nova <nova@openknot.ai>

* fix(ci): unblock discord boundary typing

* fix(ci): repair extension boundary contracts

* fix(ci): mirror whatsapp runtime dependency

* fix(ci): align cron and session tests with runtime

* fix(ci): clear residual tsgo blockers

* fix(ci): repair baileys lockfile snapshot

* fix(memory): unify default root memory handling (openclaw#66141)

* fix(memory): unify default root memory handling

* test(memory): align legacy migration expectation

* docs(changelog): tag qmd root-memory fix

* docs(changelog): append qmd root-memory entry

* docs(changelog): dedupe qmd root-memory entry

* docs(changelog): attribute qmd root-memory fix

---------

Co-authored-by: mbelinky <mbelinky@users.noreply.github.com>

* fix: normalize OpenAI minimal reasoning

* chore: fix pulled lint assertion

* plugins: trim staged runtime cargo

* fix(ci): restore plugin-local whatsapp deps

* fix(ci): align cron tests with default model

* fix(ci): repair agent test mocks

* fix(ci): repair telegram topic cache typing

* fix(ci): repair telegram ui and watch regressions

* fix(stream): tighten voice stream ingress guards (openclaw#66027)

* fix(stream): tighten voice stream ingress guards

* fix(stream): address review follow-ups

* fix(stream): normalize trusted proxy ip matching

* changelog: note voice-call media-stream ingress guard tightening (openclaw#66027)

* fix(stream): require non-empty trusted proxy list before honoring forwarding headers

Without an explicit trusted proxy list, the prior gate treated every
remote as 'from a trusted proxy', so enabling trustForwardingHeaders
let any direct caller spoof X-Forwarded-For / X-Real-IP and rotate the
resolved IP per request to evade maxPendingConnectionsPerIp. Require
trustedProxyIPs to be non-empty AND match the remote before trusting
forwarding headers.

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* Feishu: tighten allowlist target canonicalization (openclaw#66021)

* fix(feishu): tighten allowlist id matching

* fix(feishu): address review follow-ups

* changelog: note Feishu allowlist canonicalization tightening (openclaw#66021)

* fix(feishu): collapse typed wildcard allowlist aliases to bare wildcard

Previously normalizeFeishuTarget folded chat:* / user:* / open_id:* /
dm:* / group:* / channel:* down to '*', so those entries acted as
allow-all. The new typed canonicalization was producing literal keys
(chat:*, user:*, ...) that never matched any sender, silently
flipping those configs from allow-all to deny-all. Restore the prior
behavior by collapsing a wildcard value to '*' inside
canonicalizeFeishuAllowlistKey.

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* fix(ci): mirror whatsapp runtime dependency

* docs(changelog): note perf fixes

* test: align failover source model expectation

* fix: sendPolicy deny should suppress delivery, not inbound processing (openclaw#53328) (openclaw#65461)

* fix: sendPolicy deny suppresses delivery, not inbound processing (openclaw#53328)

Previously, sendPolicy "deny" returned early before the agent dispatch,
preventing the agent from ever seeing the message. This broke the use
case of an agent listening on WhatsApp groups with sendPolicy: deny to
read messages without replying — the agent couldn't read them at all.

Move the deny gate from before the agent dispatch to after it. The agent
now processes inbound messages normally (context, memory, tool calls),
but all outbound delivery paths are suppressed: final replies, tool
results, block replies, working status, plan updates, typing indicators,
and TTS payloads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: propagate sendPolicy to ACP tail dispatch instead of hardcoded allow

The ACP tail dispatch path (ctx.AcpDispatchTailAfterReset) was passing
sendPolicy: "allow" unconditionally, which would bypass delivery
suppression in a /reset <tail> turn when the session has sendPolicy deny.

Pass through the resolved sendPolicy so the tail dispatch respects it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: guard before_dispatch hook and ACP tail dispatch under sendPolicy deny

before_dispatch handled replies were leaking through sendFinalPayload
before the suppressDelivery guard was checked. ACP tail dispatch (from
/new <tail>) was being rejected by acp-runtime.ts deny checks instead
of proceeding with delivery suppression handled downstream.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* auto-reply: propagate deny suppression to reply_dispatch

* fix(acp): suppress onReplyStart when user delivery is denied

When sendPolicy resolves to "deny", ACP tail dispatch still invoked
onReplyStart via startReplyLifecycle before the suppressUserDelivery
check. Channels wire onReplyStart to typing indicators, so deny-scoped
sessions could still emit outbound typing events on /reset <tail>
flows and command bypass paths.

Gate startReplyLifecycleOnce on suppressUserDelivery so the lifecycle
is marked started but the callback is skipped. Payload delivery was
already suppressed; this closes the typing-indicator leak flagged by
Codex review (PR openclaw#65461 P1/P2).

* fix(acp): route non-tail deny turns through ACP when suppression is wired

tryDispatchAcpReplyHook was returning early for non-tail, non-command ACP
turns under sendPolicy: "deny", causing ACP-bound sessions to fall back
to the embedded reply path instead of flowing through acpManager.runTurn.
That diverged ACP session state, tool calls, and memory whenever
delivery suppression was active.

Now the early-return only fires when sendPolicy is "deny" AND the event
lacks suppressUserDelivery — i.e., when downstream delivery suppression
is not wired up. When suppressUserDelivery is set, dispatch-acp-delivery
already drops outbound sends (see onReplyStart / deliver guards), so ACP
can safely run the turn with state consistency preserved.

Existing behavior preserved:
- Command bypass still overrides deny
- Tail dispatch still overrides deny
- Plain-text deny turns without suppression still short-circuit

Addresses Codex bot P1 feedback on openclaw#65461.

* fix: gate empty-body typing indicator behind suppressTyping (openclaw#53328)

* fix: guard plugin-binding + fast-abort outbound paths under sendPolicy deny

The original PR computed suppressDelivery inside the try block, which was
after two outbound paths:

1. The plugin-owned binding block (sendBindingNotice calls for
   unavailable/declined/error outcomes, plus the plugin's own "handled"
   outcome) ran before the suppressDelivery flag existed, so plugin
   notices still leaked under deny.
2. The fast-abort path dispatched "Agent was aborted." via
   routeReplyToOriginating / sendFinalReply before the flag existed.

Move resolveSendPolicy() above the plugin-binding block so suppressDelivery
covers every outbound path downstream, matching the PR description's claim
that "all outbound paths are guarded by the flag."

Plugin-bound inbound handling under deny: plugin handlers can emit
outbound replies we cannot rewind, so skip the claim hook entirely under
deny and fall through to normal (suppressed) agent processing.
touchConversationBindingRecord still runs so binding activity stays
tracked.

Fast-abort under deny: still run the abort and record the completed
state, just don't emit the abort reply.

Tests:
- suppresses the fast-abort reply under sendPolicy deny
- delivers the fast-abort reply normally when sendPolicy is allow
  (regression guard)
- skips plugin-bound claim hook under deny and falls through to
  suppressed agent dispatch

Addresses Codex review findings on PR openclaw#65461.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(tts): allow OpenClaw temp directory paths in reply media normalizer (openclaw#63511)

Merged via squash.

Prepared head SHA: 0e9a6da
Co-authored-by: jetd1 <15795935+jetd1@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06

* docs(changelog): note sendPolicy suppressDelivery + BB Private API cache fixes (openclaw#66220)

Two recently-merged fixes that shipped without CHANGELOG entries:

- PR openclaw#65461 (sendPolicy deny suppresses delivery, not inbound processing,
  closes openclaw#53328) — squash 0362f21
- PR openclaw#65447 (BB lazy-refresh Private API on send to prevent reply
  threading degradation, closes openclaw#43764) — squash 85cfba6

Backfilling under `## Unreleased` > `### Fixes` before the next release cut.

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(changelog): add 2026.4.12 dedupe note

* fix: recover reasoning-only OpenAI turns (openclaw#66167)

* openclaw-11f.1: retry reasoning-only OpenAI turns

Regeneration-Prompt: |
  Patch the embedded runner so a signed reasoning-only assistant turn with no user-visible text is treated as recoverable instead of silently ending the run. Keep the change focused on the active OpenAI GPT-style path, retry the turn with an explicit visible-answer continuation instruction, and fall back to the existing incomplete-turn error handling only after retries are exhausted. Add regression coverage for the helper classification and for the outer run loop retry behavior, and keep unrelated provider behavior unchanged.

* openclaw-11f.1: address reasoning-only review feedback

Regeneration-Prompt: |
  Follow up on PR review feedback for the reasoning-only retry patch. Keep the fix narrow: move the retry limit into a named constant alongside the other retry-policy values, document why the limit is 2, and prevent reasoning-only auto-retries after any side effects so the runner falls back to the existing caution path instead of risking duplicate actions. Add regression coverage for the side-effect guard and the named limit behavior.

* openclaw-11f.1: drop local pebbles artifacts

Regeneration-Prompt: |
  Remove accidentally committed local pebbles tracker artifacts from the PR branch without changing runtime code. Keep the cleanup limited to deleting the tracked .pebbles files from version control, and rely on local git excludes for future pebbles activity so these files stay out of diffs.

* openclaw-11f.1: tighten reasoning-only retry guards

Regeneration-Prompt: |
  Follow up on the remaining review feedback for the reasoning-only retry path. Keep the fix narrow: do not auto-retry a reasoning-only turn when the assistant already terminated with stopReason error, and evaluate the OpenAI-specific retry guard against the provider/model metadata of the assistant turn that actually produced the partial output rather than the outer run configuration. Add regression coverage for both behaviors in the incomplete-turn runner tests.

* openclaw-11f.1: retry empty GPT turns once

Regeneration-Prompt: |
  Extend the embedded runner's GPT-style incomplete-turn recovery with a separate generic empty-response retry path. Keep it narrower than the existing reasoning-only recovery: one retry only, replay-safe only, no side effects, no assistant error turns, and scoped to the active assistant provider/model metadata. Add explicit warning logs when the empty-response retry triggers and when its single retry budget is exhausted, and add regression coverage for the success and exhaustion cases without changing broader provider fallback behavior.

* openclaw-11f.1: harden reasoning-only retry completion checks

Regeneration-Prompt: |
  Follow up on the remaining review feedback for the GPT-style recovery path. Keep the change narrow: only retry reasoning-only turns when there is no visible assistant answer yet, and if the reasoning-only retry budget is exhausted without any visible answer, surface the existing incomplete-turn error instead of treating reasoning-only payloads as a successful completion. Add focused regression coverage for both scenarios and preserve the adjacent empty-response retry behavior.

* openclaw-11f.1: preserve profile cooldown on retry exhaustion

Regeneration-Prompt: |
  Follow up on the final review comment for the GPT-style recovery path. Keep the change narrow: when the reasoning-only retry budget is exhausted and the run returns the incomplete-turn error early, preserve the same auth-profile cooldown behavior that the normal incomplete-turn branch already applies so multi-profile failover continues to work consistently. Verify the touched runner suites still pass.

* fix: recover GPT-style empty turns

Regeneration-Prompt: |
  Add the required changelog entry for the PR that hardens embedded GPT-style recovery of reasoning-only and empty-response turns. Keep the changelog update under ## Unreleased > ### Fixes, append-only, and include the PR number plus author attribution on the same line.

* test: launch macos parallels gateway in guest

* docs(changelog): tidy unreleased entries

* fix(outbound): suppress relay status placeholder leaks

* fix(ci): avoid frozen hook test clock hangs

* fix(slack): isolate doctor contract API (openclaw#63192)

* Slack: isolate doctor contract API

* chore: changelog

* fix(slack): move doctor changelog entry to Unreleased

* Plugins: lock Slack doctor sidecar metadata

* Slack: fix changelog entry placement

---------

Co-authored-by: @zimeg <zim@o526.net>
Co-authored-by: George Pickett <gpickett00@gmail.com>

* test(qa-lab): cover GPT-style broken turns

* test: extend macos parallels gateway timeout

* build: refresh a2ui bundle hash

* fix(hooks): pass workspaceDir in gateway session reset internal hook context (openclaw#64735)

* fix(hooks): pass workspaceDir in gateway session reset internal hook context

The gateway path (performGatewaySessionReset) omitted workspaceDir when
creating the internal hook event, while the plugin hook path
(emitGatewayBeforeResetPluginHook) in the same file correctly resolved and
passed it.  This caused the session-memory handler to fall back to
resolveAgentWorkspaceDir from the session key, which for default-agent
keys resolves to the shared default workspace instead of the per-agent
workspace.  Daily notes and memory files were written to the wrong
workspace in multi-agent setups.

Closes openclaw#64528

* docs(changelog): add session-memory workspace reset note

* fix(changelog): remove conflict markers

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* docs(gateway): Document Docker-out-of-Docker Paradox and constraint (openclaw#65473)

* docs: Detail Docker-out-of-Docker paradox and host path requirements

* docs: fix spelling inside sandboxing.md

* fix: grammar typo as suggested by Greptile

* Agents: fix Windows drive path join for read/sandbox tools (openclaw#54039) (openclaw#66193)

* Agents: fix Windows drive path join for read/sandbox tools (openclaw#54039)

* fix(agents): harden Windows file URL path mapping

* fix(agents): reject encoded file URL separators

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* test: bound canvas auth helper waits

* chore(release): prepare 2026.4.14 beta

* build: prune runtime dependency type declarations

* fix: include apiKey in codex provider catalog to unblock models.json loading (openclaw#66180)

Merged via squash.

Prepared head SHA: ce61934
Co-authored-by: hoyyeva <63033505+hoyyeva@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Reviewed-by: @BruceMacD

* fix(slack): align interaction auth with allowlists (openclaw#66028)

* fix(slack): align interaction auth with allowlists

* fix(slack): address review followups

* fix(slack): preserve explicit owners with wildcard

* chore: append Claude comments resolution worklog

* fix(slack): harden interaction auth with default-deny, mandatory actor binding, and channel type validation

- Add interactiveEvent flag to authorizeSlackSystemEventSender for stricter
  interactive control authorization
- Default-deny when no allowFrom or channel users are configured for
  interactive events (block actions, modals)
- Require expectedSenderId for all interactive event types; block actions
  pass Slack-verified userId, modals pass metadata-embedded userId
- Reject ambiguous channel types for interactive events to prevent DM
  authorization bypass via channel-type fallback
- Add comprehensive test coverage for all new behaviors

* fix(slack): scope interactive owner/allowFrom enforcement to interactive paths only

* fix(slack): preserve no-channel interactive default

* Update context-engine-maintenance test

* chore: remove USER.md worklog artifact

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* changelog: note Slack interactive auth allowlist alignment (openclaw#66028)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>

* fix(media): fail closed on attachment canonicalization (openclaw#66022)

* fix(media): fail closed on attachment canonicalization

* fix(media): clarify attachment skip failures

* fix(media): preserve attachment URL fallback

* fix(media): preserve getPath URL fallback on blocked local paths

* changelog: note media attachment canonicalization fail-closed (openclaw#66022)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* Guard dangerous gateway config mutations (openclaw#62006)

* fix(gateway): guard dangerous config alias

* fix(gateway): ignore reordered dangerous flags

* fix(gateway): use id-based mapping identity and honor legacy alias baseline

* fix(gateway): tighten dangerous config matching

* fix(gateway): strip IPv6 brackets in isRemoteGatewayTarget hostname check

* fix(gateway): detect tunneled remote targets

* fix(gateway): match id-less hook mappings by fingerprint, not index

* fix(gateway): detect env-selected remote targets

* fix(gateway): resolve remote-target guard from live config, not captured opts

* fix(gateway): resolve remote-target guard from live config, not captured opts

* fix(gateway): treat loopback OPENCLAW_GATEWAY_URL as local when mode is not remote

* fix(gateway): preserve legacy dangerous hook edits

* fix(gateway): block dangerous plugin reactivation

* fix(gateway): handle dotted plugin IDs in dangerous-flag checks

* fix(gateway): honor plugin policy activation

* fix(gateway): block remote plugin activation changes via allow/deny/enabled

* fix(gateway): broaden loopback url detection

* fix(gateway): resolve plugin IDs by longest-prefix match

* fix(gateway): block remote slot activation

* fix(gateway): preserve legacy mapping identity during id+field transitions

* fix(gateway): block remote load-path and channel activation changes

* test(gateway): fix remote config mock typing

* fix(gateway): guard auto-enabled dangerous plugins

* fix(gateway): address P1 review comments on remote gateway mutation guards

- Treat all OPENCLAW_GATEWAY_URL targets as remote for mutation guards to prevent SSH tunnel bypasses
- Always load config fresh in isRemoteGatewayTargetForAgentTools to detect session changes
- Expand remote activation guard to cover auto-enable paths (auth.profiles, models.providers, agents.defaults, agents.list, tools.web.fetch.provider)
- Respect plugins.deny in manifest-missing fallback to prevent false negatives
- Fix hook mapping identity matching to properly handle id-less mappings by fingerprint
- Update tests to reflect new secure behavior for env-sourced gateway URLs

* fix(gateway): prevent hook mapping swap attacks via fingerprint-only matching

When both current and next tokens have fingerprints, match ONLY by fingerprint.
This prevents replacing one dangerous hook mapping with a different one at the
same array index from being incorrectly treated as 'already present'.

The previous fallback to index-based matching allowed bypasses where an attacker
could swap dangerous mappings at the same index without triggering the guard.

* fix(gateway): honor allowlist in fallback guard

* fix(gateway): treat empty plugin allowlist as unrestricted in manifest-missing fallback

* docs: update USER.md worklog for empty-allowlist fix

* fix(gateway): resolve review comments — type safety, auto-enable resilience, remote hardening edits

* docs: update USER.md worklog for review comment resolution

* fix(gateway): block remaining remote setup auto-enable paths

* fix(gateway): simplify dangerous config mutation guard to set-diff approach

Replace 400+ lines of hook fingerprinting, remote gateway detection,
plugin activation tracking, and auto-enable enumeration with a simple
set-diff against collectEnabledInsecureOrDangerousFlags — the same
enumeration openclaw security audit already uses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove USER.md audit log from PR

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* changelog: note gateway-tool dangerous config mutation guard (openclaw#62006)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(telegram): persist topic-name cache

* test(telegram): cover topic-name cache reload

* docs(changelog): note telegram topic-name persistence

* fix(telegram): allow topic cache without session runtime

* fix(telegram): persist topic cache via default runtime

* fix: move telegram topic-cache changelog to unreleased (openclaw#66107)

* test: enforce npm pack budget in install smoke

* fix: restore pnpm check

* test: remove timer dependency from telegram topic cache tests

* fix: avoid inline dotenv secrets in systemd unit during service repair (openclaw#66249) (thanks @tmimmanuel)

* fix(daemon): avoid inline dotenv secrets in systemd unit during service repair

* fix(daemon): sanitize systemd envfile and dedupe state-dir resolution

* fix(daemon): fail on multiline dotenv values for systemd envfile

* test(daemon): cover systemd envfile staging

* fix: keep systemd envfile overrides intact (openclaw#66249) (thanks @tmimmanuel)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* test: cover gateway wake startup gating

* test: stabilize gateway wake gating regression

* fix: tighten inbound replay typing

* test: align feishu replay helper typing

* test: update model fallback auth store mock

* test: refresh cron and mcp typed fixtures

* test: use cron embedded runtime mock

* test: mock model fallback source check

* fix: align latest main type drift

* fix: remove agent config lint suppression

* test: align cron runtime seams

* test: align agent session resolver mocks

* test: align cron model error expectations

* test: drop removed agent scope suppression

* fix: keep baileys plugin-local

* test: align post-rebase full-suite drift

* fix: mirror baileys root dependency

* test: bound docker fs bridge probes

* test: keep telegram cache boundary compatible

* fix: cache external plugin catalog lookups in auto-enable (openclaw#66246) (thanks @yfge)

* fix: cache external plugin catalog lookups in auto-enable

Fixes openclaw#66159

* test: restore readFileSync spy in plugin auto-enable test

* refactor: distill plugin auto-enable cache path

* fix: cache external plugin catalog lookups in auto-enable (openclaw#66246) (thanks @yfge)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* Agents: clarify local model context preflight (openclaw#66236)

Merged via squash.

Prepared head SHA: 11bfaf1
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Reviewed-by: @ImLukeF

* fix: harden approvals get timeout handling (openclaw#66239) (thanks @neeravmakwana)

* fix(cli): harden approvals get timeout handling

* fix(cli): sanitize approvals timeout notes

* fix(cli): distill approvals timeout note

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix: honor configured store limits (openclaw#66240) (thanks @neeravmakwana)

* fix(media): honor configured store limits

* fix(media): report effective source limits

* refactor(media): distill configured limit wiring

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(browser): relax default hostname SSRF guard

* fix(browser): use loopback policy for json-new fallback

* fix(browser): preserve explicit strict SSRF config

* fix: add browser SSRF follow-up changelog entry (openclaw#66386)

* fix(browser): preserve legacy strict SSRF alias

* feat(active-memory): instrument embedded runs

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: Bob <dutifulbob@gmail.com>
Co-authored-by: dutifulbob <261991368+dutifulbob@users.noreply.github.com>
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: mazhe-nerd <106217973+mazhe-nerd@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Minijus-Sa <minijus.savickas@hostinger.com>
Co-authored-by: EVA <admin@100yen.org>
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Brian <95547369+zhuisDEV@users.noreply.github.com>
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
Co-authored-by: Mariano <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>
Co-authored-by: Omar Shahine <omarshahine@users.noreply.github.com>
Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ptah.ai <11701+ptahdunbar@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Pavan Kumar Gondhi <pgondhi@nvidia.com>
Co-authored-by: pashpashpash <nik@vault77.ai>
Co-authored-by: 屈定 <niudear@foxmail.com>
Co-authored-by: 屈定 <mrdear@users.noreply.github.com>
Co-authored-by: Altay <altay@uinaf.dev>
Co-authored-by: Agustin Rivera <31522568+eleqtrizit@users.noreply.github.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: rafaelreis-r <57492577+rafaelreis-r@users.noreply.github.com>
Co-authored-by: Byron <loong0306@163.com>
Co-authored-by: loong0306 <loong0306@gmail.com>
Co-authored-by: Nova <nova@openknot.ai>
Co-authored-by: Val Alexander <bunsthedev@gmail.com>
Co-authored-by: zhangfan49 <zhangfan49@baidu.com>
Co-authored-by: mbelinky <mbelinky@users.noreply.github.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: Xiaoshuai Zhang <i@jetd.one>
Co-authored-by: jetd1 <15795935+jetd1@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Co-authored-by: Josh Lehman <josh@martian.engineering>
Co-authored-by: ShihChi Huang <shh@theonlyperson.com>
Co-authored-by: @zimeg <zim@o526.net>
Co-authored-by: George Pickett <gpickett00@gmail.com>
Co-authored-by: Subash Natarajan <suboss87@gmail.com>
Co-authored-by: Joe LaPenna <jlapenna@gmail.com>
Co-authored-by: ly85206559 <ly85206559@163.com>
Co-authored-by: Eva H <63033505+hoyyeva@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Co-authored-by: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com>
Co-authored-by: 拐爷&&老拐瘦 <geyunfei@gmail.com>
Co-authored-by: Luke <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: Neerav Makwana <neeravmakwana@gmail.com>
Co-authored-by: Bastion <bastion@agents.angventures.io>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…hanks @ptahdunbar)

* feat(telegram): expose forum topic names in agent context

Telegram Bot API does not provide a method to look up forum topic names
by thread ID. This adds an in-memory LRU cache that learns topic names
from service messages (forum_topic_created, forum_topic_edited,
forum_topic_closed, forum_topic_reopened) and seeds from
reply_to_message.forum_topic_created as a fallback for pre-existing
topics.

The resolved topic name is surfaced as:
- TopicName in MsgContext (available to {{TopicName}} in templates)
- topic_name in the agent prompt metadata block
- topicName in plugin hook event metadata

Includes unit tests for the topic-name-cache module (11 tests including
eviction and read-recency).

Known limitation: cache is in-memory only; after a restart it falls back
to the creation-time name until a rename event is observed.

* refactor(telegram): distill topic name flow

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…hanks @ptahdunbar)

* feat(telegram): expose forum topic names in agent context

Telegram Bot API does not provide a method to look up forum topic names
by thread ID. This adds an in-memory LRU cache that learns topic names
from service messages (forum_topic_created, forum_topic_edited,
forum_topic_closed, forum_topic_reopened) and seeds from
reply_to_message.forum_topic_created as a fallback for pre-existing
topics.

The resolved topic name is surfaced as:
- TopicName in MsgContext (available to {{TopicName}} in templates)
- topic_name in the agent prompt metadata block
- topicName in plugin hook event metadata

Includes unit tests for the topic-name-cache module (11 tests including
eviction and read-recency).

Known limitation: cache is in-memory only; after a restart it falls back
to the creation-time name until a rename event is observed.

* refactor(telegram): distill topic name flow

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…hanks @ptahdunbar)

* feat(telegram): expose forum topic names in agent context

Telegram Bot API does not provide a method to look up forum topic names
by thread ID. This adds an in-memory LRU cache that learns topic names
from service messages (forum_topic_created, forum_topic_edited,
forum_topic_closed, forum_topic_reopened) and seeds from
reply_to_message.forum_topic_created as a fallback for pre-existing
topics.

The resolved topic name is surfaced as:
- TopicName in MsgContext (available to {{TopicName}} in templates)
- topic_name in the agent prompt metadata block
- topicName in plugin hook event metadata

Includes unit tests for the topic-name-cache module (11 tests including
eviction and read-recency).

Known limitation: cache is in-memory only; after a restart it falls back
to the creation-time name until a rename event is observed.

* refactor(telegram): distill topic name flow

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Include Telegram forum topic name in inbound metadata

2 participants