* refactor(skills): use explicit skill-command scope APIs
* test(skills): cover scoped listing and telegram allowlist
* fix(skills): add mergeSkillFilters edge-case tests and simplify dead code
Cover unrestricted-co-tenant and empty-allowlist merge paths in
skill-commands tests. Remove dead ternary in bot-handlers pagination.
Add clarifying comments on undefined vs [] filter semantics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(skills): collapse scope functions into single listSkillCommandsForAgents
Replace listSkillCommandsForAgentIds, listSkillCommandsForAllAgents, and
the deprecated listSkillCommandsForAgents with a single function that
accepts optional agentIds and falls back to all agents when omitted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(skills): harden realpathSync race and add missing test coverage
- Wrap fs.realpathSync in try-catch to gracefully skip workspaces that
disappear between existsSync and realpathSync (TOCTOU race).
- Log verbose diagnostics for missing/unresolvable workspace paths.
- Add test for overlapping allowlists deduplication on shared workspaces.
- Add test for graceful skip of missing workspaces.
- Add test for pagination callback without agent suffix (default agent).
- Clean up temp directories in skill-commands tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(telegram): warn when nativeSkillsEnabled but no agent route is bound
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use runtime.log instead of nonexistent runtime.warn
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The followup runner (which processes queued messages) was calling
runEmbeddedPiAgent without currentChannelId or currentThreadTs.
This meant the message tool's toolContext had no channel routing
info, causing reactions (and other target-inferred actions) to
fail with 'Action react requires a target' on queued messages.
Pass originatingTo as currentChannelId so the message tool can
infer the reaction target from context, matching the behavior
of the initial (non-queued) agent run.
The /\s+/g whitespace normalizer collapsed newlines along with spaces/tabs,
destroying paragraph structure in multi-line messages before they reached
the LLM. Use /[^\S\n]+/g to only collapse horizontal whitespace while
preserving line breaks.
Closes#32216
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds group context fields to MessageSentHookContext so hooks can
correlate sent events with received events for the same conversation.
Previously, message:received included isGroup/groupId but message:sent
did not, forcing hooks to use mismatched identifiers (e.g. groupId vs
numeric chat ID) when tracking conversations.
Fields are derived from MsgContext in dispatch-from-config and threaded
through route-reply and deliver via the mirror parameter.
Addresses feedback from matskevich (production user, 550+ events)
reported on PR #6797.
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 `fromMe` flag from Baileys' WAMessage.key was only used for
access-control filtering and then discarded. This meant agents
could not distinguish owner-sent messages from contact messages
in DM conversations (everything appeared as from the contact).
Add `fromMe` to `WebInboundMessage`, store it during message
construction, and thread it through `buildInboundLine` →
`formatInboundEnvelope` so DM transcripts prefix owner messages
with `(self):`.
Closes#32061
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When enforceFinalTag is active (Google providers), stripBlockTags
correctly returns empty for text without <final> tags. However, the
handleMessageEnd fallback recovered raw text, bypassing this protection
and leaking internal reasoning (e.g. "**Applying single-bot mention
rule**NO_REPLY") to Discord.
Guard the fallback with enforceFinalTag check: if the provider is
supposed to use <final> tags and none were seen, the text is treated
as leaked reasoning and suppressed.
Also harden stripSilentToken regex to allow bold markdown (**) as
separator before NO_REPLY, matching the pattern Gemini Flash Lite
produces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES. If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.
Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts). enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately. clearSessionQueues cleans up the callback
cache alongside the queue state.
* feat(agents): support `thinkingDefault: "adaptive"` for Anthropic models
Anthropic's Opus 4.6 and Sonnet 4.6 support adaptive thinking where the
model dynamically decides when and how much to think. This is now
Anthropic's recommended mode and `budget_tokens` is deprecated on these
models.
Add "adaptive" as a valid thinking level:
- Config: `agents.defaults.thinkingDefault: "adaptive"`
- CLI: `/think adaptive` or `/think auto`
- Pi SDK mapping: "adaptive" → "medium" effort at the pi-agent-core
layer, which the Anthropic provider translates to
`thinking.type: "adaptive"` with `output_config.effort: "medium"`
- Provider fallbacks: OpenRouter and Google map "adaptive" to their
respective "medium" equivalents
Closes#30880
Made-with: Cursor
* style(changelog): format changelog with oxfmt
* test(types): fix strict typing in runtime/plugin-context tests
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>