Addresses greptile review: collapses the if-guard + assignment into
a single ??= expression so TypeScript can narrow the type without
a non-null assertion.
Without this fix, the bundler can emit multiple copies of internal-hooks
into separate chunks. registerInternalHook writes to one Map instance
while triggerInternalHook reads from another — resulting in hooks that
silently fire with zero handlers regardless of how many were registered.
Reproduce: load a hook via hooks.external.entries (loader reads one chunk),
then send a message:transcribed event (get-reply imports a different chunk).
The handler list is empty; the hook never runs.
Fix: use globalThis.__openclaw_internal_hook_handlers__ as a shared
singleton. All module copies check for and reuse the same Map, ensuring
registrations are always visible to triggers.
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.
Arrow function passed to registerInternalHook was implicitly returning
the number from Array.push(), which is not assignable to void | Promise<void>.
Use block body to discard the return value.
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.
resolveAgentModelPrimary() only checks the agent-level model config and
does not fall back to the system-wide default. When users configure a
non-Anthropic provider (e.g. Gemini, Minimax) as their global default
without setting it at the agent level, the slug-generator falls through
to DEFAULT_PROVIDER (anthropic) and fails with a missing API key error.
Switch to resolveAgentEffectiveModelPrimary() which correctly respects
the full model resolution chain including global defaults.
Fixes#25365
Subagent and isolated cron sessions only loaded AGENTS.md and TOOLS.md,
causing subagents to lose their role personality, identity, and user
preferences. Expand MINIMAL_BOOTSTRAP_ALLOWLIST to include the three
missing identity files.
Closes#24852
(cherry picked from commit c33377150eeddb42c2a24f4a48c2d01b5cdf8d3e)
The slug generator was using hardcoded DEFAULT_PROVIDER and DEFAULT_MODEL
instead of resolving from agent config. This caused it to fall back to
anthropic/claude-opus-4-6 even when a cloud model was configured.
Now uses resolveAgentModelPrimary() to get the configured model, with
fallback to defaults if not configured.
Fixes issue where session memory filenames would fail to generate
when using cloud models that require special backends.