* feat(slack): add typingReaction config for DM typing indicator fallback
Adds a reaction-based typing indicator for Slack DMs that works without
assistant mode. When `channels.slack.typingReaction` is set (e.g.
"hourglass_flowing_sand"), the emoji is added to the user's message when
processing starts and removed when the reply is sent.
Addresses #19809
* test(slack): add typingReaction to createSlackMonitorContext test callers
* test(slack): add typingReaction to test context callers
* test(slack): add typingReaction to context fixture
* docs(changelog): credit Slack typingReaction feature
* test(slack): align existing-thread history expectation
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This feature allows different topics within a Telegram forum supergroup to route
to different agents, each with isolated workspace, memory, and sessions.
Key changes:
- Add agentId field to TelegramTopicConfig type for per-topic routing
- Add zod validation for agentId in topic config schema
- Implement routing logic to re-derive session key with topic's agent
- Add debug logging for topic agent overrides
- Add unit tests for routing behavior (forum topics + DM topics)
- Add config validation tests
- Document feature in docs/channels/telegram.md
This builds on the approach from PR #31513 by @Sid-Qin with additional fixes
for security (preserved account fail-closed guard) and test coverage.
Closes#31473
Adds two new internal hook events that fire after media/link processing:
- message:transcribed: fires when audio has been transcribed, providing
the transcript text alongside the original body and media metadata.
Useful for logging, analytics, or routing based on spoken content.
- message:preprocessed: fires for every message after all media + link
understanding completes. Gives hooks access to the fully enriched body
(transcripts, image descriptions, link summaries) before the agent sees it.
Both hooks are added in get-reply.ts, after applyMediaUnderstanding and
applyLinkUnderstanding. message:received and message:sent are already
in upstream (f07bb8e8) and are not duplicated here.
Typed contexts (MessageTranscribedHookContext, MessagePreprocessedHookContext)
and type guards (isMessageTranscribedEvent, isMessagePreprocessedEvent) added
to internal-hooks.ts alongside the existing received/sent types.
Test coverage in src/hooks/message-hooks.test.ts.
The session-store cache used only mtime for invalidation. In fast CI
runs (especially under bun), test writes to the session store can
complete within the same filesystem mtime granularity (~1s on HFS+/ext4),
so the cache returns stale data. This caused non-deterministic failures
in model precedence tests where a session override written to disk was
not observed by the next loadSessionStore() call.
Fix: add file size as a secondary cache invalidation signal. The cache
now checks both mtimeMs and sizeBytes — if either differs from the
cached values, it reloads from disk.
Changes:
- cache-utils.ts: add getFileSizeBytes() helper
- sessions/store.ts: extend SessionStoreCacheEntry with sizeBytes field,
check size in cache-hit path, populate size on cache writes
- sessions.cache.test.ts: add regression test for same-mtime rewrite
When echoTranscript is enabled in tools.media.audio config, the
transcription text is sent back to the originating chat immediately
after successful audio transcription — before the agent processes it.
This lets users verify what was heard from their voice note.
Changes:
- config/types.tools.ts: add echoTranscript (bool) and echoFormat
(string template) to MediaUnderstandingConfig
- media-understanding/apply.ts: sendTranscriptEcho() helper that
resolves channel/to from ctx, guards on isDeliverableMessageChannel,
and calls deliverOutboundPayloads best-effort
- config/schema.help.ts: help text for both new fields
- config/schema.labels.ts: labels for both new fields
- media-understanding/apply.echo-transcript.test.ts: 10 vitest cases
covering disabled/enabled/custom-format/no-audio/failed-transcription/
non-deliverable-channel/missing-from/OriginatingTo/delivery-failure
Default echoFormat: '📝 "{transcript}"'
Closes#32102
Addresses #31699 — config .bak files persist with sensitive data.
Changes:
- Explicitly chmod 0o600 on all .bak files after creation, instead of
relying on copyFile to preserve source permissions (not guaranteed on
all platforms, e.g. Windows, NFS mounts).
- Clean up orphan .bak files that fall outside the managed 5-deep
rotation ring (e.g. PID-stamped leftovers from interrupted writes,
manual backups like .bak.before-marketing).
- Add tests for permission hardening and orphan cleanup.
The backup ring itself is preserved — it's a valuable recovery mechanism.
This PR hardens the security surface by ensuring backup files are
always owner-only and stale copies don't accumulate indefinitely.