Skip to content

fix(matrix): preserve shared send aliases and voice intent#52607

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
vincentkoc:vincentkoc-code/matrix-send-aliases-voice-intent
Mar 23, 2026
Merged

fix(matrix): preserve shared send aliases and voice intent#52607
vincentkoc merged 1 commit into
openclaw:mainfrom
vincentkoc:vincentkoc-code/matrix-send-aliases-voice-intent

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • Problem: Matrix send actions accept the shared tool parameter shapes used elsewhere, but the Matrix action adapter only read media and dropped asVoice / audioAsVoice before the plugin send layer.
  • Why it matters: media-only sends from shared tool callers can miss the attachment path, and voice-intent sends fall back to generic audio instead of the Matrix voice path.
  • What changed: forward mediaUrl, filePath, and path aliases plus asVoice / audioAsVoice through actions.ts, tool-actions.ts, and the Matrix action wrapper; add focused regression coverage; add a changelog entry crediting @psacc and @vincentkoc.
  • What did NOT change (scope boundary): no broader Matrix refactor, no transport changes, and no changes to non-Matrix channels.

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

User-visible / Behavior Changes

  • Matrix message actions now accept shared media parameter aliases (mediaUrl, filePath, path) in addition to media.
  • Matrix message actions now preserve asVoice / audioAsVoice so supported audio sends can reach the Matrix voice-message path.

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: not verified in a runtime environment from this shell
  • Runtime/container: this shell does not have node, pnpm, or bun available
  • Integration/channel (if any): Matrix plugin send action path
  • Relevant config (redacted): multi-account Matrix send actions using shared tool-call parameter shapes

Steps

  1. Invoke a Matrix message action with path or filePath instead of media, optionally with asVoice: true.
  2. Observe the pre-fix adapter path drop the alias and voice flag before sendMessageMatrix.
  3. Apply this patch and repeat.

Expected

  • The adapter normalizes the shared alias fields to mediaUrl and forwards voice intent to the Matrix send layer.

Actual

  • Before this patch, the Matrix adapter only read media and ignored asVoice / audioAsVoice.

Evidence

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

Human Verification (required)

  • Verified scenarios: code-path inspection across extensions/matrix/src/actions.ts, extensions/matrix/src/tool-actions.ts, and extensions/matrix/src/matrix/actions/messages.ts; added focused regression tests for both adapter layers.
  • Edge cases checked: media-only sends, shared alias fields, and asVoice propagation to the send wrapper.
  • What you did not verify: I could not run the local test scripts from this shell because node, pnpm, and bun are unavailable here.

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
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR
  • Files/config to restore: extensions/matrix/src/actions.ts, extensions/matrix/src/tool-actions.ts, extensions/matrix/src/matrix/actions/messages.ts
  • Known bad symptoms reviewers should watch for: Matrix send actions ignoring shared media aliases or voice-intent flags

Risks and Mitigations

  • Risk: additional alias normalization could drift from other channel adapters later.
    • Mitigation: regression tests pin the accepted alias and voice-flag shapes in the Matrix adapter layer.

@vincentkoc vincentkoc requested a review from a team as a code owner March 23, 2026 03:31
@openclaw-barnacle openclaw-barnacle Bot added channel: matrix Channel integration: matrix gateway Gateway runtime commands Command implementations agents Agent runtime and tooling extensions: kilocode size: M maintainer Maintainer-authored PR labels Mar 23, 2026
@vincentkoc vincentkoc force-pushed the vincentkoc-code/matrix-send-aliases-voice-intent branch from aca9585 to 96827b2 Compare March 23, 2026 03:35
@openclaw-barnacle openclaw-barnacle Bot added size: S and removed gateway Gateway runtime commands Command implementations agents Agent runtime and tooling extensions: kilocode size: M labels Mar 23, 2026
@vincentkoc vincentkoc self-assigned this Mar 23, 2026
@vincentkoc vincentkoc merged commit 50bc625 into openclaw:main Mar 23, 2026
14 checks passed
@vincentkoc vincentkoc deleted the vincentkoc-code/matrix-send-aliases-voice-intent branch March 23, 2026 03:35
@greptile-apps

greptile-apps Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two related bugs in the Matrix action adapters: media-only sends that used shared parameter aliases (mediaUrl, filePath, path) were silently dropped, and voice-intent flags (asVoice / audioAsVoice) were never forwarded through the dispatch chain. The fix is applied at two layers — the ChannelMessageActionAdapter (actions.ts) and the lower-level tool-action handler (tool-actions.ts) — with the messages.ts wrapper updated to accept and forward the new audioAsVoice option. Regression tests are added for both adapter layers.

The PR also includes a large number of out-of-scope changes (~40 non-Matrix files) consolidating direct extension imports into plugin-sdk re-export files. While these appear to be mechanical import-path refactors, they are not mentioned in the PR description's stated scope boundary and touch a broad surface area of the codebase.

  • Core fix is correct: alias resolution and voice-flag forwarding work as intended across both adapter layers.
  • Voice-flag priority inconsistency: actions.ts checks asVoice before audioAsVoice; tool-actions.ts checks them in the reverse order. In the normal actions.ts → tool-actions.ts path this has no effect (the value is normalised to audioAsVoice before dispatch), but direct callers of handleMatrixAction supplying both flags will observe different priority than the higher-level adapter — worth aligning.
  • readRawParam called twice per flag check in tool-actions.ts: the ternary evaluates typeof readRawParam(...) for the check and then calls it again for the value; extracting to a local variable would be cleaner.
  • The ~40 non-Matrix import-consolidation changes are untested by the new regression suite and should ideally be reviewed in their own PR to keep change surface clearly bounded.

Confidence Score: 4/5

  • The Matrix-specific fix is correct and well-tested; a voice-flag priority inconsistency between the two adapter layers is a minor style concern with no impact on the normal call path.
  • The core bug fix (alias forwarding + voice-flag propagation) is solid, the regression tests cover the intended scenarios, and the change is backward compatible. A P2 priority-order inconsistency between actions.ts and tool-actions.ts is the only remaining concern. The large number of out-of-scope import-refactor changes across ~40 non-Matrix files widens the review surface beyond what the PR description describes, which is why this falls short of a 5.
  • The ~40 non-Matrix import-consolidation files (src/agents/, src/plugin-sdk/, src/auto-reply/, etc.) are outside the stated scope and should receive a quick sanity-check to confirm they are purely mechanical re-exports with no behaviour change.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/matrix/src/tool-actions.ts
Line: 235-241

Comment:
**Voice-flag priority differs from `actions.ts`**

In `actions.ts` `asVoice` is checked first (wins over `audioAsVoice`), but here `audioAsVoice` is checked first. When a caller passes *both* flags with different values the two code paths will disagree. It also calls `readRawParam` twice per flag (read-then-read pattern), which can be simplified by caching the results.

```suggestion
        const rawAudioAsVoice = readRawParam(params, "audioAsVoice");
        const rawAsVoice = readRawParam(params, "asVoice");
        const audioAsVoice =
          typeof rawAsVoice === "boolean"
            ? rawAsVoice
            : typeof rawAudioAsVoice === "boolean"
              ? rawAudioAsVoice
              : undefined;
```

This aligns the priority (`asVoice` wins) with the `actions.ts` layer and avoids the repeated lookups.

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

---

This is a comment left during a code review.
Path: extensions/matrix/src/tool-actions.ts
Line: 235-240

Comment:
**Voice-flag priority is reversed relative to `actions.ts`**

In `actions.ts`, `asVoice` takes priority over `audioAsVoice` (lines 177–180). Here in `tool-actions.ts` the order is flipped: `audioAsVoice` wins. This means a direct caller of `handleMatrixAction` supplying both `asVoice: false` and `audioAsVoice: true` would get `audioAsVoice: true` — the opposite of what the higher-level adapter would produce from the same input.

In practice the two paths rarely converge (by the time a call travels through `actions.ts` the value is already normalised to `audioAsVoice`), but keeping the priority consistent prevents surprising edge cases and makes reasoning about the overall pipeline easier.

Consider aligning `tool-actions.ts` to check `asVoice` first, matching the `actions.ts` behavior:

```suggestion
        const rawAudioAsVoice = readRawParam(params, "audioAsVoice");
        const rawAsVoice = readRawParam(params, "asVoice");
        const audioAsVoice =
          typeof rawAsVoice === "boolean"
            ? rawAsVoice
            : typeof rawAudioAsVoice === "boolean"
              ? rawAudioAsVoice
              : undefined;
```

This also removes the double invocation of `readRawParam` per flag (the current ternary calls it once for the `typeof` check and again to retrieve the value).

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

Reviews (1): Last reviewed commit: "fix(matrix): preserve send aliases and v..." | Re-trigger Greptile

Comment on lines +235 to 241
typeof readRawParam(params, "audioAsVoice") === "boolean"
? (readRawParam(params, "audioAsVoice") as boolean)
: typeof readRawParam(params, "asVoice") === "boolean"
? (readRawParam(params, "asVoice") as boolean)
: undefined;
const result = await sendMatrixMessage(to, content, {
mediaUrl: mediaUrl ?? undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Voice-flag priority differs from actions.ts

In actions.ts asVoice is checked first (wins over audioAsVoice), but here audioAsVoice is checked first. When a caller passes both flags with different values the two code paths will disagree. It also calls readRawParam twice per flag (read-then-read pattern), which can be simplified by caching the results.

Suggested change
typeof readRawParam(params, "audioAsVoice") === "boolean"
? (readRawParam(params, "audioAsVoice") as boolean)
: typeof readRawParam(params, "asVoice") === "boolean"
? (readRawParam(params, "asVoice") as boolean)
: undefined;
const result = await sendMatrixMessage(to, content, {
mediaUrl: mediaUrl ?? undefined,
const rawAudioAsVoice = readRawParam(params, "audioAsVoice");
const rawAsVoice = readRawParam(params, "asVoice");
const audioAsVoice =
typeof rawAsVoice === "boolean"
? rawAsVoice
: typeof rawAudioAsVoice === "boolean"
? rawAudioAsVoice
: undefined;

This aligns the priority (asVoice wins) with the actions.ts layer and avoids the repeated lookups.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/matrix/src/tool-actions.ts
Line: 235-241

Comment:
**Voice-flag priority differs from `actions.ts`**

In `actions.ts` `asVoice` is checked first (wins over `audioAsVoice`), but here `audioAsVoice` is checked first. When a caller passes *both* flags with different values the two code paths will disagree. It also calls `readRawParam` twice per flag (read-then-read pattern), which can be simplified by caching the results.

```suggestion
        const rawAudioAsVoice = readRawParam(params, "audioAsVoice");
        const rawAsVoice = readRawParam(params, "asVoice");
        const audioAsVoice =
          typeof rawAsVoice === "boolean"
            ? rawAsVoice
            : typeof rawAudioAsVoice === "boolean"
              ? rawAudioAsVoice
              : undefined;
```

This aligns the priority (`asVoice` wins) with the `actions.ts` layer and avoids the repeated lookups.

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

Comment on lines +235 to 240
typeof readRawParam(params, "audioAsVoice") === "boolean"
? (readRawParam(params, "audioAsVoice") as boolean)
: typeof readRawParam(params, "asVoice") === "boolean"
? (readRawParam(params, "asVoice") as boolean)
: undefined;
const result = await sendMatrixMessage(to, content, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Voice-flag priority is reversed relative to actions.ts

In actions.ts, asVoice takes priority over audioAsVoice (lines 177–180). Here in tool-actions.ts the order is flipped: audioAsVoice wins. This means a direct caller of handleMatrixAction supplying both asVoice: false and audioAsVoice: true would get audioAsVoice: true — the opposite of what the higher-level adapter would produce from the same input.

In practice the two paths rarely converge (by the time a call travels through actions.ts the value is already normalised to audioAsVoice), but keeping the priority consistent prevents surprising edge cases and makes reasoning about the overall pipeline easier.

Consider aligning tool-actions.ts to check asVoice first, matching the actions.ts behavior:

Suggested change
typeof readRawParam(params, "audioAsVoice") === "boolean"
? (readRawParam(params, "audioAsVoice") as boolean)
: typeof readRawParam(params, "asVoice") === "boolean"
? (readRawParam(params, "asVoice") as boolean)
: undefined;
const result = await sendMatrixMessage(to, content, {
const rawAudioAsVoice = readRawParam(params, "audioAsVoice");
const rawAsVoice = readRawParam(params, "asVoice");
const audioAsVoice =
typeof rawAsVoice === "boolean"
? rawAsVoice
: typeof rawAudioAsVoice === "boolean"
? rawAudioAsVoice
: undefined;

This also removes the double invocation of readRawParam per flag (the current ternary calls it once for the typeof check and again to retrieve the value).

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/matrix/src/tool-actions.ts
Line: 235-240

Comment:
**Voice-flag priority is reversed relative to `actions.ts`**

In `actions.ts`, `asVoice` takes priority over `audioAsVoice` (lines 177–180). Here in `tool-actions.ts` the order is flipped: `audioAsVoice` wins. This means a direct caller of `handleMatrixAction` supplying both `asVoice: false` and `audioAsVoice: true` would get `audioAsVoice: true` — the opposite of what the higher-level adapter would produce from the same input.

In practice the two paths rarely converge (by the time a call travels through `actions.ts` the value is already normalised to `audioAsVoice`), but keeping the priority consistent prevents surprising edge cases and makes reasoning about the overall pipeline easier.

Consider aligning `tool-actions.ts` to check `asVoice` first, matching the `actions.ts` behavior:

```suggestion
        const rawAudioAsVoice = readRawParam(params, "audioAsVoice");
        const rawAsVoice = readRawParam(params, "asVoice");
        const audioAsVoice =
          typeof rawAsVoice === "boolean"
            ? rawAsVoice
            : typeof rawAudioAsVoice === "boolean"
              ? rawAudioAsVoice
              : undefined;
```

This also removes the double invocation of `readRawParam` per flag (the current ternary calls it once for the `typeof` check and again to retrieve the value).

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: matrix Channel integration: matrix maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Matrix multi-account: tool-action message may send from wrong account

1 participant