Skip to content

feat(hooks): add before_llm_call, after_llm_call, before_response_emit modifying hooks#17

Closed
zeroaltitude wants to merge 43 commits intomainfrom
feat/hook-llm-call-response-emit
Closed

feat(hooks): add before_llm_call, after_llm_call, before_response_emit modifying hooks#17
zeroaltitude wants to merge 43 commits intomainfrom
feat/hook-llm-call-response-emit

Conversation

@zeroaltitude
Copy link
Copy Markdown
Owner

Upstream: openclaw#33916

Part 3/3 of hooks split. See upstream PR for full description.

[claude, human developer oversight]

@zeroaltitude zeroaltitude force-pushed the feat/hook-llm-call-response-emit branch 2 times, most recently from a94eed9 to a520ab2 Compare March 7, 2026 17:56
…t modifying hooks

Three new sequential/modifying plugin hooks for LLM call interception
and response gating:

- before_llm_call: fires before every LLM API call. Plugins can
  inspect/modify messages, system prompt, filter tools, or block
  the call entirely. Integrated via stream function wrapping.
- after_llm_call: fires after receiving the LLM response but before
  tool execution. Plugins can filter/modify tool calls or block.
  Integrated via event subscription on message_end events.
- before_response_emit: fires when the agent's final response is
  ready, before delivery. Plugins can modify content or block
  emission. Integrated at the snapshot selection point.

All three run sequentially and merge results across handlers,
following the same pattern as before_tool_call and message_sending.

Enables security plugins to implement:
- Context-aware tool filtering (before_llm_call)
- Tool call auditing/blocking (after_llm_call)
- Output content policies (before_response_emit)

Complements the existing llm_input/llm_output observation hooks
(which are void/fire-and-forget) by adding modifying capability.

36 unit tests across hook runners, stream wrapper, and response emit.

[claude, human developer oversight]
…tion tracking

- after_llm_call is now void (parallel, observational) — modifying tool
  calls requires agent loop interception points that don't exist yet.
  Documented as a future upgrade path.
- before_response_emit block now returns empty string instead of
  undefined, so callers can distinguish blocked from unmodified.
- hookIterationRef declared before event subscription (fixes temporal
  dead zone warning). Subscription now activates for before_llm_call
  too, so iteration counter updates even without after_llm_call hooks.

Addresses greptile + codex-connector review on PR openclaw#33916.
…separate PR)

hook-stream-wrapper.ts incorrectly referenced context_assembled which
is defined in feat/hook-context-assembled-loop-iteration (openclaw#33915), not
this branch. Removed cross-branch dependency so CI passes standalone.

Fixes type errors in CI check job.
…re_response_emit

- before_response_emit: use explicit undefined check instead of truthiness
  for content, so plugins can return empty string as valid modified content
- before_llm_call: check systemPrompt !== undefined instead of truthiness,
  so plugins can clear the system prompt with empty string

Addresses codex P2 reviews on PR openclaw#33916.
…and session save control

- Move clearInternalHooks() to server.impl.ts (before plugin registration)
  to ensure plugins register first, bundled hooks second (FIFO ordering)
- Add session-memory handler extensions: blockSessionSave, sessionSaveRedirectPath,
  sessionSaveContent — allows upstream hooks (e.g. security plugins) to control
  session save behavior
- Update internal-hooks JSDoc documenting command:new context fields
- Align plugin on() opts type to OpenClawPluginHookOptions (drop priority)
- Add hooks.extended.test.ts covering before_llm_call, after_llm_call,
  before_response_emit (context_assembled/loop_iteration tests are in
  hooks.context-loop.test.ts in the companion PR)
- Move clearInternalHooks to server.impl.ts for correct plugin→bundled ordering
- Add session-memory handler extensions (blockSessionSave, redirectPath, customContent)
- Align registry opts type to OpenClawPluginHookOptions
- Add internal-hooks JSDoc for command:new context fields
1. Clear blocked content from session history when before_response_emit
   returns block:true (prevents data leak to subsequent turns)
2. Rewrite ALL text parts in multi-part assistant messages (prevents
   stale/unredacted fragments in parts after the first)
3. Switch hook execution from priority-sorted to FIFO (registration order)
   to match the FIFO contract established in registry.ts

Refactored session message rewriting into rewriteAssistantContent() and
clearAssistantContent() helpers for clarity and reuse.

Note: Greptile/Codex false positives on tools:[] and systemPrompt:""
were verified — [] is truthy in JS and ?? only triggers for null/undefined.
…bridge

after_llm_call is now a modifying hook (sequential execution, merged results).
Plugins can return { block: true } to block ALL tool execution for the turn,
or { toolCalls: [...] } to filter which tool calls are allowed.

Implementation uses a mutable ref bridge (after-llm-call-gate.ts) between
the streaming subscription callback (where after_llm_call fires) and
before_tool_call (where each tool executes). This avoids restructuring the
agent loop while delivering batch tool-call analysis capabilities.

Gate lifecycle:
- Set after after_llm_call returns block/filter decisions
- Checked by before_tool_call before each tool executes
- Cleared on turn_start (prevents stale decisions) and run cleanup

This enables provenance plugin capabilities:
- Batch pattern detection (correlate multiple tool calls in one turn)
- Taint-aware blanket blocking (block all tools when context is tainted)
- Selective tool call removal (filter specific calls from the batch)
…ponse guard

1. Guard before_response_emit: only runs when this turn produced an
   assistant reply (assistantTexts.length > 0). Prevents stale prior-turn
   content from being emitted on timeout/abort turns. (Codex P1)

2. Fix session history rewrite: scan backwards for last assistant message
   instead of assuming messages[-1] is assistant. Prevents silent no-op
   when tool results are appended after the snapshot. (Greptile)

3. Fix after_llm_call gate race: capture iteration at event time and
   compare in .then() callback. Stale results from slow handlers are
   discarded when the turn has advanced. (Codex P1)

4. Export PluginHookAfterLlmCallResult from hooks.ts public API. (Greptile)

5. Fix stale test description: 'parallel (void)' → 'sequentially (modifying)'
   in hooks.llm-response.test.ts. (Greptile)
…ter response-emit

1. Clear after_llm_call gate when hook returns no block/filter (undefined
   or { block: false } without toolCalls). Prevents stale gates from earlier
   message_end events in multi-step tool loops from blocking later tools. (Codex P1)

2. Refresh messagesSnapshot after before_response_emit modifies content,
   so downstream consumers (agent_end, llm_output, cache trace) see
   post-redaction content instead of original pre-hook text. (Codex P2)
…es context

Spread original context object before applying hook modifications so that
provider metadata and other fields attached by upstream wrappers are preserved.
Previously, the effectiveContext was rebuilt from only 3 keys (systemPrompt,
messages, tools), silently dropping any extra fields.
after-llm-call-gate.test.ts (10 tests):
- Block all tools, block with/without reason, filter by toolCallId
- Allow listed tools, no-filter passthrough, session isolation
- Gate overwrite, clear, block-takes-precedence-over-filter

hook-response-emit.test.ts (4 new tests, 15 total):
- Blocked content cleared from session history (PII scrubbing)
- Multi-part text parts all cleared on block
- Multi-part text parts: first rewritten, rest cleared on modify
- Assistant message found even when not last element
…memory changes

- Delete hooks.llm-response.test.ts (328 lines) — complete duplicate of
  hooks.extended.test.ts which has 2 extra after_llm_call tests
- Remove hooks.context-loop.test.ts (160 lines) — belongs to PR openclaw#33915
  (feat/hook-context-assembled-loop-iteration), not this PR
- Revert session-memory handler changes (58 lines) — blockSessionSave
  and sessionSaveRedirectPath are a separate feature; will move to own PR

Net: -512 lines, 49 tests remain (zero coverage loss)
…+ gate error handling

- Move hookCtx declaration above subscription callback to eliminate
  temporal dead zone forward reference
- Use isToolCallBlockType() in after_llm_call tool extraction to handle
  toolUse/functionCall variants (not just toolCall)
- Clear gate on hook error to prevent stale gates blocking tool calls
- Remove session-memory handler changes (separate feature)
Four fixes from deep audit:

1. Document after_llm_call gate as best-effort for async hooks —
   the .then() microtask may not resolve before tool dispatch begins
   for slow/async hook implementations.

2. Add hookRunDisposed flag to prevent late-resolving after_llm_call
   hooks from repopulating the gate after run cleanup. Set before
   clearAfterLlmCallGate in finally block, checked in .then().

3. Block tool calls with missing toolCallId when allowedToolCallIds
   is set — a tool without an ID cannot be verified against the
   allowlist and must be blocked (security gap).

4. Use !== undefined consistently for messages, systemPrompt, and
   tools in hook-stream-wrapper.ts. Any explicit value (including
   [] or '') means the hook wants that exact value; return undefined
   for 'no change'.
…le JSDoc

- Remove dead else branch in before_response_emit (assistantTexts.length
  is guaranteed > 0 by the outer guard)
- Check hookRunDisposed in .catch() handler for consistency (idempotent
  clear, but prevents unnecessary work after run disposal)
- Remove session-memory policy JSDoc from internal-hooks.ts (belongs in
  feat/hook-session-memory-policy PR, not here)
… change

Restores `opts?: { priority?: number }` on the `on()` method and
`.toSorted()` by priority in getHooksForName. The removal was
accidental — priority ordering was part of the existing plugin API.
Changes `next.block ?? acc?.block` to `next.block || acc?.block`
across all 4 merge functions (before_tool_call, after_llm_call,
before_llm_call, before_response_emit).

Once a security plugin sets block: true, a later plugin returning
block: false cannot override it. This is critical for the stated
security use case (taint tracking, prompt injection defense).
…ck, filter to assistant message_end

1. Tools/toolCalls use intersection when both handlers provide lists —
   prevents later handler from widening security plugin's allowlist.
2. Block (modifiedContent === '') now clears ALL assistantTexts via splice,
   not just the last entry. Earlier tool-loop chunks would have escaped.
3. after_llm_call only fires on assistant message_end — non-assistant
   message_end was clearing the gate and dropping computed allowlists.
…onse_emit

Plugin authors targeting PII redaction across full multi-turn tool-loop
runs should use after_llm_call (per-turn) rather than before_response_emit
which only sees the last assistant message. Blocking is always reliable.
Plugins can now access and modify ALL assistant messages from a tool-loop
run, not just the last one. This closes the multi-turn PII redaction gap
identified in review.

Changes:
- PluginHookBeforeResponseEmitEvent gains allContent: string[] (all
  assistant texts from the run, chronological)
- PluginHookBeforeResponseEmitResult gains allContent?: string[] (when
  returned, replaces the full assistantTexts array)
- allContent takes precedence over content when both are returned
- Block now clears ALL assistant messages in session history, not just last
- applyBeforeResponseEmitHook returns a structured result object instead
  of string|undefined for cleaner caller logic
- rewriteAllAssistantContent walks all assistant messages in order
- 21 tests covering allContent modification, precedence, block clearing

Fully backward-compatible: existing plugins using only content still work.
…es only

Block and allContent rewrites now only touch assistant messages from the
current run (using preRunMessageCount), never corrupting previously-delivered
session history. Prior assistant turns are left intact.

Also fixes:
- Missing allContent field in hooks.extended.test.ts fixture (TS error)
- Garbled duplicate 'priority order' comment in hooks.ts
… allContent

Two fixes:
1. after_llm_call gate: move staleness check before the no-result clear
   path. Previously a slow turn-N handler resolving with no result could
   erase turn-N+1's gate decision. Now stale results are discarded first.
2. Empty allContent ([] from plugins suppressing all output) was treated
   as falsy and ignored. Use !== undefined check instead.
If compaction shrinks the message array during a run, preRunMessageCount
may exceed the current length. getRunScopedMessages now falls back to
tail-based extraction (last N assistant messages) when the index is stale,
ensuring block/rewrite operations still target the correct messages.

Also replied to Codex re: intra-turn gate race — pre-existing best-effort
contract, documented at line 1287.
…all gate

Within the same turn, multiple message_end events share the same iteration
counter. A slow async handler from an earlier message_end could resolve
after a later one already set the gate, causing the wrong policy to apply.

Added a monotonic hookMessageEndSeq counter that increments on every
message_end. The .then() handler now checks both eventIteration (cross-turn)
and eventSeq (intra-turn) before touching the gate. The .catch() handler
also checks both before clearing.
…sts; harden fallback

1. before_response_emit now fires even when the last assistant message has
   no text content, as long as assistantTexts has entries. Policy plugins
   get their chance to inspect/block allContent from earlier tool-loop turns.

2. getRunScopedMessages returns empty (not full transcript) when the
   backward scan can't find enough assistants or assistantTextCount is 0.
   This prevents compaction edge cases from corrupting pre-run history.
Accidentally committed .openclaw-tank/memory/2026-03-05.md. Removed from
tracking and added .openclaw-*/ to .gitignore to prevent future leaks.
…ength mismatch

1. before_response_emit merge semantics: content and allContent now use
   first-writer-wins (acc ?? next) instead of last-writer-wins (next ?? acc).
   Once a higher-priority security plugin sets redacted content, later
   utility plugins cannot override it. Matches the one-way latch on block.

2. rewriteAllAssistantContent logs a warning when allContent length differs
   from the assistant message count, making silent data loss visible in logs.
…_call

Consistent with first-writer-wins on content/allContent in
before_response_emit, the intersection latch on tools, and the one-way
latch on block. Once a higher-priority security plugin sanitizes inputs,
later utility plugins cannot override them.
…response_emit merge

Once a higher-priority plugin sets either content or allContent, both
fields are locked for later plugins. Prevents a lower-priority plugin
from bypassing single-message redaction (content) by supplying
allContent (which takes precedence in application), or vice versa.
…afe)

Removes index-based slicing via preRunMessageCount which can become
stale when compaction replaces/merges transcript entries without
changing the array length. Tail scan finds the last N assistant
messages from the end, which is always correct regardless of
compaction state.
…ction warning

- Removed preRunMessageCount from ApplyBeforeResponseEmitParams,
  getRunScopedMessages signature, and all callers. The parameter was
  unused since switching to tail-based scanning.
- Added warn-level log when block fires but getRunScopedMessages
  returns [] (compaction edge case where run boundaries are
  unidentifiable). Response delivery is still suppressed but blocked
  content may persist in session history.
…dMessages

Tool-call-only assistant messages have content (array with tool_use
blocks) but no text. assistantTextCount is derived from streamed text
entries, so the tail scan must match that filter. Using
extractAssistantText().length > 0 instead of 'content' in msg.
before_llm_call block now uses BeforeLlmCallBlockError sentinel class
instead of generic Error. The agent loop catches this specifically and
completes the run gracefully (no error surfaced) — consistent with
before_response_emit block behavior. Previously, blocking threw a
generic error that surfaced as a run failure.
Consistent with block (one-way latch) and content/messages
(first-writer-wins): the higher-priority plugin that originally
blocked also owns the reason. Prevents a lower-priority plugin
from silently overwriting the block reason.
…s only

Tool-call-only assistant messages (content arrays with only tool_use
blocks, no text) are now skipped in rewriteAllAssistantContent,
matching getRunScopedMessages and assistantTexts counting. Prevents
allContent index misalignment when tool-call-only turns are
interspersed with text-bearing turns.
…ges block

- allContent splice now bounded to original assistantTexts.length —
  plugins cannot expand the response beyond what was produced.
- Block with empty runMessages (compaction edge case) now fails closed
  by clearing ALL assistant content in the session, preventing blocked
  text from leaking into future turns or persistence.
clearAllAssistantContent now sets array content to [] instead of
only clearing text parts. Prevents sensitive data in tool_use input
arguments from persisting in session history when a response is
blocked by before_response_emit.
…ors, guard gate clear

1. Test comment: 'h2 overrides' → clarifies h1 never blocked (not a latch scenario)
2. Cross-lock: added NOTE for plugin authors about silent drop behavior
3. Gate clear: guarded by hasHooks('after_llm_call') to avoid no-op on before_llm_call-only
… to PLUGIN_HOOK_NAMES

Required for isPluginHookName validation and registerTypedHook.
Same pattern as the context_assembled/loop_iteration fix.
@zeroaltitude zeroaltitude force-pushed the feat/hook-llm-call-response-emit branch from a520ab2 to 68a78a0 Compare March 7, 2026 19:48
@zeroaltitude zeroaltitude deleted the feat/hook-llm-call-response-emit branch March 7, 2026 21:59
zeroaltitude pushed a commit that referenced this pull request Apr 2, 2026
* feat: add QQ Bot channel extension

* fix(qqbot): add setupWizard to runtime plugin for onboard re-entry

* fix: fix review

* fix: fix review

* chore: sync lockfile and config-docs baseline for qqbot extension

* refactor: 移除图床服务器相关代码

* fix

* docs: 新增 QQ Bot 插件文档并修正链接路径

* refactor: remove credential backup functionality and update setup logic

- Deleted the credential backup module to streamline the codebase.
- Updated the setup surface to handle client secrets more robustly, allowing for configured secret inputs.
- Simplified slash commands by removing unused hot upgrade compatibility checks and related functions.
- Adjusted types to use SecretInput for client secrets in QQBot configuration.
- Modified bundled plugin metadata to allow additional properties in the config schema.

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理

* feat: remove qqbot-media and qqbot-remind skills, add tests for config and setup

- Deleted the qqbot-media and qqbot-remind skills documentation files.
- Added unit tests for qqbot configuration and setup processes, ensuring proper handling of SecretRef-backed credentials and account configurations.
- Implemented tests for local media path remapping, verifying correct resolution of media file paths.
- Removed obsolete channel and remind tools, streamlining the codebase.

* feat: 更新 QQBot 配置模式,添加音频格式和账户定义

* feat: 添加 QQBot 频道管理和定时提醒技能,更新媒体路径解析功能

* fix

* feat: 添加 /bot-upgrade 指令以查看 QQBot 插件升级指引

* feat: update reminder and qq channel skills

* feat: 更新remind工具投递目标地址格式

* feat: Refactor QQBot payload handling and improve code documentation

- Simplified and clarified the structure of payload interfaces for Cron reminders and media messages.
- Enhanced the parsing function to provide clearer error messages and improved validation.
- Updated platform utility functions for better cross-platform compatibility and clearer documentation.
- Improved text parsing utilities for better readability and consistency in emoji representation.
- Optimized upload cache management with clearer comments and reduced redundancy.
- Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation.

* OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift

> openclaw@2026.3.26 check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw
> node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check

[bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts
 ELIFECYCLE  Command failed with exit code 1.
 ELIFECYCLE  Command failed with exit code 1.

* feat: 添加 QQBot 渠道配置及相关账户设置

* fix(qqbot): resolve 14 high-priority bugs from PR openclaw#52986 review

DM routing (7 fixes):
- #1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId)
- #2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId}
- #3: sendTextChunks adds DM branch
- #4: sendMarkdownReply adds DM branch for text and Base64 images
- #5: parseAndSendMediaTags maps DM to targetType:dm + guildId
- #6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field
- #7: handleImage/Audio/Video/FilePayload add DM branches

Other high-priority fixes:
- #8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment
- #9: broadcastMessage uses groupOpenid instead of member_openid for group users
- #10: Unify KnownUser storage - proactive.ts delegates to known-users.ts
- #11: Remove invalid recordKnownUser calls for guild/DM users
- #12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook
- #13: sendPhoto channel unsupported returns error field
- #14: sendTextAfterMedia adds channel and dm branches

Type fixes:
- DeliverEventContext adds guildId field
- MediaTargetContext.targetType adds dm variant
- sendPlainTextReply imgMediaTarget adds DM branch

* fix(qqbot): resolve 2 blockers + 7 medium-priority bugs from PR openclaw#52986 review

Blocker-1: Remove unused dmPolicy config knob
- dmPolicy was declared in schema/types/plugin.json but never consumed at runtime
- Removed from config-schema.ts, types.ts, and openclaw.plugin.json
- allowFrom remains active (already wired into framework command-auth)

Blocker-2: Gate sensitive slash commands with allowFrom authorization
- SlashCommand interface adds requireAuth?: boolean
- SlashCommandContext adds commandAuthorized: boolean
- /bot-logs set to requireAuth: true (reads local log files)
- matchSlashCommand rejects unauthorized senders for requireAuth commands
- trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config

Medium-priority fixes:
- #15: Strip non-HTTP/non-local markdown image tags to prevent path leakage
- #16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa
- #17: getAdminMarkerFile sanitizes accountId to prevent path traversal
- #18: URGENT_COMMANDS uses exact match instead of startsWith prefix match
- #19: isCronExpression validates each token starts with a cron-valid character
- openclaw#20: --token format validation rejects malformed input without colon separator
- openclaw#21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable

* test(qqbot): add focused tests for slash command authorization path

- Unauthorized sender rejected for /bot-logs (requireAuth: true)
- Authorized sender allowed for /bot-logs
- Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders
- Unknown slash commands return null (passthrough)
- Non-slash messages return null
- Usage query (/bot-logs ?) also gated by auth check

* fix(qqbot): align global TTS fallback with framework config resolution

- Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core
  resolveTtsConfig logic: check auto !== 'off', fall back to legacy
  enabled boolean, default to off when neither is set.
- Add pre-check in reply-dispatcher before calling globalTextToSpeech to
  avoid unnecessary TTS calls and noisy error logs when TTS is not
  configured.
- Remove inline as any casts; use OpenClawConfig type throughout.
- Refactor handleAudioPayload into flat early-return structure with
  unified send path (plugin TTS → global fallback → send).

* fix(qqbot): break ESM circular dependency causing multi-account startup crash

The bundled gateway chunk had a circular static import on the channel
chunk (gateway -> outbound-deliver -> channel, while channel dynamically
imports gateway). When two accounts start concurrently via Promise.all,
the first dynamic import triggers module graph evaluation; the circular
reference causes api exports (including runDiagnostics) to resolve as
undefined before the module finishes evaluating.

Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new
text-utils.ts leaf module. outbound-deliver.ts now imports from
text-utils.ts, breaking the cycle. channel.ts re-exports for backward
compatibility.

* fix(qqbot): serialize gateway module import to prevent multi-account startup race

When multiple accounts start concurrently via Promise.all, each calls
await import('./gateway.js') independently. Due to ESM circular
dependencies in the bundled output, the first import can resolve
transitive exports as undefined before module evaluation completes.

Fix: cache the dynamic import promise in a module-level variable so all
concurrent startAccount calls share the same import, ensuring the
gateway module is fully evaluated before any account uses it.

* refactor(qqbot): remove startup greeting logic

Remove getStartupGreetingPlan and related startup greeting delivery:
- Delete startup-greeting.ts (greeting plan, marker persistence)
- Delete admin-resolver.ts (admin resolution, greeting dispatch)
- Remove startup greeting calls from gateway READY/RESUMED handlers
- Remove isFirstReadyGlobal flag and adminCtx

* fix(qqbot): skip octal escape decoding for Windows local paths

Windows paths like C:\Users\1\file.txt contain backslash-digit sequences
that were incorrectly matched as octal escape sequences and decoded,
corrupting the file path. Detect Windows local paths (drive letter or UNC
prefix) and skip the octal decoding step for them.

* fix bot issue

* feat: 支持 TTS 自动开关并清理配置中的 clientSecretFile

* docs: 添加 QQBot 配置和消息处理的设计说明

* rebase

* fix(qqbot): align slash-command auth with shared command-auth model

Route requireAuth:true slash commands (e.g. /bot-logs) through the
framework's api.registerCommand() so resolveCommandAuthorization()
applies commands.allowFrom.qqbot precedence and qqbot: prefix
normalization before any handler runs.

- slash-commands.ts: registerCommand() now auto-routes by requireAuth
  into two maps (commands / frameworkCommands); getFrameworkCommands()
  exports the auth-required set for framework registration; bot-help
  lists both maps
- index.ts: registerFull() iterates getFrameworkCommands() and calls
  api.registerCommand() for each; handler derives msgType from ctx.from,
  sends file attachments via sendDocument, supports multi-account via
  ctx.accountId
- gateway.ts (inbound): replace raw allowFrom string comparison with
  qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and
  uppercase before matching event.senderId
- gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized
  is true (requireAuth:true commands never reach matchSlashCommand)
- command-auth.test.ts: add regression tests for qqbot: prefix
  normalization in the inbound commandAuthorized computation
- slash-commands.test.ts: update /bot-logs tests to expect null
  (command routed to framework, not in local registry)

* rebase and solve conflict

* fix(qqbot): preserve mixed env setup credentials

---------

Co-authored-by: yuehuali <yuehuali@tencent.com>
Co-authored-by: walli <walli@tencent.com>
Co-authored-by: WideLee <limkuan24@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant