Skip to content

zalouser: fix DM/group routing, media send, mention gating, and auto-restart#33992

Closed
darkamenosa wants to merge 14 commits intomainfrom
tuyenhx/zalouser-fixes-and-group-history
Closed

zalouser: fix DM/group routing, media send, mention gating, and auto-restart#33992
darkamenosa wants to merge 14 commits intomainfrom
tuyenhx/zalouser-fixes-and-group-history

Conversation

@darkamenosa
Copy link
Copy Markdown
Member

Summary

  • Problem: The zalouser extension had several compounding issues — typing indicator silently failed, group mention detection was broken, outbound messages could route to the wrong target (DM vs group), media sending was incomplete, the channel task never restarted when the listener transport died, and group chats had no message history context.
  • Why it matters: Users on Zalo couldn't reliably use group chats (mention gating broken, no history context), media sending was limited to text-only, and a dead listener meant a permanently offline bot until manual restart.
  • What changed: Fixed all issues below, enhanced media pipeline, wired auto-restart, and added group history support.
  • What did NOT change (scope boundary): Core gateway logic, other channels, no new npm dependencies.

Tasks

  • Fix typing indicator — typing indicator was silently failing; now works in both DM and group chats
  • Fix requireMention in group chat — mention detection was broken, bot responded to all messages regardless of mention gating config
  • Enhance ID resolver — zca-js uses user:<id> and group:<id> prefixes for routing; added explicit prefix-based resolver to prevent messages from being sent to the wrong target (DM vs group)
  • Enhance media sending — added full Zalo media support: image, video, voice, and generic file attachments. Known limitation: voice messages are only playable on Zalo web UI, not mobile, due to a zca-js upstream constraint
  • Fix auto-restart on listener death — zalouser never exited/restarted its account task when the listener transport died, while the gateway runner already has robust auto-restart/backoff when a channel task ends. Now the channel task exits cleanly so the gateway can restart it
  • Add historyLimit for group chat — group chats previously had no message history context, causing the bot to lose memory between messages. New optional historyLimit config retains recent messages for context

Demo

A video walkthrough demonstrating all fixes and features is available — link will be added in PR comments.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • 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

  • None

User-visible / Behavior Changes

  • Typing indicator now works in Zalo DM and group chats
  • requireMention correctly gates group messages (bot only responds when mentioned)
  • Group chats support historyLimit config to retain message context across turns
  • Media sending: image, video, voice, and generic file attachments now supported
  • Known: voice playback only works on Zalo web UI due to zca-js limitation
  • Bot auto-restarts when Zalo listener transport dies (previously stayed offline permanently)

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No (same Zalo API surface via zca-js)
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22+ / Bun
  • Integration/channel: Zalo (zalouser extension)

Steps

  1. Configure zalouser with requireMention enabled and historyLimit set
  2. Send a message in a Zalo group mentioning the bot
  3. Send media (image/voice/file) via the bot
  4. Kill the Zalo listener transport and observe auto-restart

Expected

  • Bot responds only when mentioned in groups, with message history context
  • Media sends correctly; typing indicator shows during processing
  • Bot auto-restarts if listener drops

Actual

  • All of the above now works as expected

Evidence

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

Human Verification (required)

  • Verified scenarios: DM routing, group routing with mention gating, media send (image/voice/file), typing indicator, auto-restart on listener death, group history retention
  • Edge cases checked: voice playback limitation (web-only), group vs DM ID disambiguation, listener reconnect backoff
  • What you did not verify: Android/iOS Zalo client voice playback (known zca-js limitation)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? New optional historyLimit field in zalouser config
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert to previous zalouser extension version
  • Files/config to restore: extensions/zalouser/
  • Known bad symptoms reviewers should watch for: Messages routing to wrong target (DM vs group), bot not restarting after disconnect

Risks and Mitigations

  • Risk: Voice messages only playable on Zalo web UI, not mobile
    • Mitigation: This is a zca-js upstream limitation, documented in channel docs

sendZaloTypingEvent now returns after successful send and throws
when typing is unsupported. Added error logging on typing start
failure in monitor.
- Deduplicate mention IDs via Set in extractMentionIds
- Add fallback to getOwnId when fetchAccountInfo shape changes
- Narrow fetchAccountInfo return type to { profile: User }
- Fail closed when requireMention=true but detection unavailable
- Add test for mention-unavailable fail-closed behavior
Add explicit target parsing with group:/user: prefixes so the bot
correctly routes outbound messages to groups vs DMs. Supports
aliases (g:, g-, u:, dm:, zlu:, zalouser:) and passes isGroup
flag to sendMessageZalouser.
@darkamenosa darkamenosa added the channel: zalouser Channel integration: zalouser label Mar 4, 2026
@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation size: XL maintainer Maintainer-authored PR labels Mar 4, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR addresses multiple compounding issues in the zalouser extension: broken mention gating, silent typing indicator failures, DM/group routing ambiguity, incomplete media sending, missing auto-restart on listener death, and absent group message history. The fixes are well-scoped and backed by a new test suite (channel.sendpayload.test.ts, monitor.group-gating.test.ts).

  • Mention gating fix (monitor.ts): resolveMentionGatingWithBypass is now wired with the correct explicit-mention fields from the inbound message, resolving the broken requireMention behaviour. The fail-closed path (drop when detection is unavailable) is also properly guarded.
  • DM vs group routing (channel.ts): normalizePrefixedTarget and parseZalouserOutboundTarget resolve group:<id> / user:<id> prefixes (and their short aliases) before passing the thread ID to sendMessageZalouser, preventing messages from going to the wrong target.
  • Media pipeline (zalo-js.ts): Audio files are uploaded via uploadAttachment and then sent as voice via sendVoice; images/videos/files use inline attachment in sendMessage. The new resolveMediaFileName helper covers a wide range of MIME types.
  • Auto-restart (monitor.ts): monitorZalouserProvider now uses Promise.withResolvers to suspend until the listener terminates (settleSuccess) or fails (settleFailure). When the listener reports an error or a watchdog gap, the function rejects — letting the gateway runner's existing restart/backoff logic kick in.
  • Group history (monitor.ts): Skipped (non-mentioned) messages are stored in historyState.groupHistories keyed by session, prepended to the next dispatched message's context, and cleared after dispatch.
  • Race condition: processMessage is fire-and-forget inside the listener callback, so two group messages can process concurrently. The shared groupHistories Map is read and written between await points, meaning a rapidly-typed unmentioned message followed immediately by a mentioned one may miss the pending history entry on the first dispatch.
  • Voice asset resolution: resolveUploadedVoiceAsset only matches fileType === "others" or "video". If zca-js ever returns a different type the thrown error message lacks the actual fileType, making failures hard to diagnose.
  • sendPayload edge case (channel.ts): lastResult is read with a non-null assertion after the chunker loop but is never initialised; an empty-chunk result would return undefined. The !text guard upstream makes this unreachable in practice, but an explicit guard would remove the silent assumption.

Confidence Score: 4/5

  • This PR is safe to merge; the identified issues are minor or theoretical and do not affect the core correctness of the fixes.
  • The changes are well-structured, accompanied by meaningful test coverage, and fix real regressions. The race condition in group history is the most significant new concern but has a narrow impact window and minor consequences (one history entry could be dropped in rapid-fire group messages). The resolveUploadedVoiceAsset diagnostics gap and the lastResult uninitialized-variable edge case are low-severity. No existing behaviour is regressed.
  • extensions/zalouser/src/monitor.ts (group history race condition) and extensions/zalouser/src/zalo-js.ts (voice asset resolution diagnostics)

Last reviewed commit: 7949003

Comment on lines +785 to 802
onMessage: (msg) => {
if (stopped) {
return;
}
logVerbose(core, runtime, `[${account.accountId}] inbound message`);
statusSink?.({ lastInboundAt: Date.now() });
processMessage(
msg,
account,
config,
core,
runtime,
{ historyLimit, groupHistories },
statusSink,
).catch((err) => {
runtime.error(`[${account.accountId}] Failed to process message: ${String(err)}`);
});
},
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.

Race condition in group history state

processMessage is dispatched without await inside the listener callback (line ~791), so two messages arriving close together will execute concurrently. The groupHistories Map is shared across all concurrent invocations.

A concrete scenario: message A (unmentioned) and message B (mentioned) arrive nearly simultaneously.

  1. Both calls start their async chain (resolveZaloGroupContext, sendZalouserDeliveryAcks, resolveSenderCommandAuthorization).
  2. If B's awaits complete first, B reaches buildPendingHistoryContextFromMap while A hasn't yet reached recordPendingHistoryEntryIfEnabled — so B reads an empty history.
  3. A then records its entry, but B has already dispatched its reply context without including A's message.

This means a user message immediately followed by a mention could silently drop the preceding context entry from the pending history. Consider serialising per-group message processing (e.g. a per-historyKey promise chain or mutex) to guarantee ordering.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zalouser/src/monitor.ts
Line: 785-802

Comment:
**Race condition in group history state**

`processMessage` is dispatched without `await` inside the listener callback (line ~791), so two messages arriving close together will execute concurrently. The `groupHistories` Map is shared across all concurrent invocations.

A concrete scenario: message A (unmentioned) and message B (mentioned) arrive nearly simultaneously.

1. Both calls start their async chain (`resolveZaloGroupContext`, `sendZalouserDeliveryAcks`, `resolveSenderCommandAuthorization`).
2. If B's awaits complete first, B reaches `buildPendingHistoryContextFromMap` while A hasn't yet reached `recordPendingHistoryEntryIfEnabled` — so B reads an **empty** history.
3. A then records its entry, but B has already dispatched its reply context without including A's message.

This means a user message immediately followed by a mention could silently drop the preceding context entry from the pending history. Consider serialising per-group message processing (e.g. a per-`historyKey` promise chain or mutex) to guarantee ordering.

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

Comment on lines +431 to +452
function resolveUploadedVoiceAsset(
uploaded: Array<{
fileType?: string;
fileUrl?: string;
fileName?: string;
}>,
): { fileUrl: string; fileName?: string } | undefined {
for (const item of uploaded) {
if (!item || typeof item !== "object") {
continue;
}
const fileType = item.fileType?.toLowerCase();
const fileUrl = item.fileUrl?.trim();
if (!fileUrl) {
continue;
}
if (fileType === "others" || fileType === "video") {
return { fileUrl, fileName: item.fileName?.trim() || undefined };
}
}
return 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.

resolveUploadedVoiceAsset may silently miss audio attachments

The helper only returns an asset when fileType is "others" or "video". If zca-js ever returns fileType === "audio" or another string for an uploaded audio buffer, the function returns undefined and the call site throws:

Failed to resolve uploaded audio URL for voice message

This would surface as a silent send failure (caught by the outer try/catch and returned as { ok: false }). It would be worth at minimum logging the actual fileType values seen in the uploaded response when the helper returns undefined, so the failure is diagnosable:

if (!voiceAsset) {
  const seen = uploaded.map((u) => u.fileType).join(", ");
  throw new Error(`Failed to resolve uploaded audio URL for voice message (fileTypes: ${seen})`);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zalouser/src/zalo-js.ts
Line: 431-452

Comment:
**`resolveUploadedVoiceAsset` may silently miss audio attachments**

The helper only returns an asset when `fileType` is `"others"` or `"video"`. If `zca-js` ever returns `fileType === "audio"` or another string for an uploaded audio buffer, the function returns `undefined` and the call site throws:

```
Failed to resolve uploaded audio URL for voice message
```

This would surface as a silent send failure (caught by the outer `try/catch` and returned as `{ ok: false }`). It would be worth at minimum logging the actual `fileType` values seen in the uploaded response when the helper returns `undefined`, so the failure is diagnosable:

```ts
if (!voiceAsset) {
  const seen = uploaded.map((u) => u.fileType).join(", ");
  throw new Error(`Failed to resolve uploaded audio URL for voice message (fileTypes: ${seen})`);
}
```

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Additional Comments (1)

extensions/zalouser/src/channel.ts
lastResult may be read before assignment

lastResult is declared but never initialised. If chunks is empty (theoretically possible if a custom chunker returns [] for a whitespace-only text that passed the !text guard), the loop body never runs and return lastResult! returns undefined, which will break callers expecting a ZaloSendResult.

The non-null assertion ! silences TypeScript's uninitialized-variable check here. A minimal safeguard:

      const outbound = zalouserPlugin.outbound!;
      const limit = outbound.textChunkLimit;
      const chunks = limit && outbound.chunker ? outbound.chunker(text, limit) : [text];
      if (chunks.length === 0) {
        return { channel: "zalouser", messageId: "" };
      }
      let lastResult: Awaited<ReturnType<NonNullable<typeof outbound.sendText>>>;
      for (const chunk of chunks) {
        lastResult = await outbound.sendText!({ ...ctx, text: chunk });
      }
      return lastResult!;
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/zalouser/src/channel.ts
Line: 643-650

Comment:
**`lastResult` may be read before assignment**

`lastResult` is declared but never initialised. If `chunks` is empty (theoretically possible if a custom `chunker` returns `[]` for a whitespace-only text that passed the `!text` guard), the loop body never runs and `return lastResult!` returns `undefined`, which will break callers expecting a `ZaloSendResult`.

The non-null assertion `!` silences TypeScript's uninitialized-variable check here. A minimal safeguard:

```suggestion
      const outbound = zalouserPlugin.outbound!;
      const limit = outbound.textChunkLimit;
      const chunks = limit && outbound.chunker ? outbound.chunker(text, limit) : [text];
      if (chunks.length === 0) {
        return { channel: "zalouser", messageId: "" };
      }
      let lastResult: Awaited<ReturnType<NonNullable<typeof outbound.sendText>>>;
      for (const chunk of chunks) {
        lastResult = await outbound.sendText!({ ...ctx, text: chunk });
      }
      return lastResult!;
```

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 794900379a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 352 to +354
const peer = isGroup
? { kind: "group" as const, id: chatId }
: { kind: "group" as const, id: senderId };
: { kind: "direct" as const, id: senderId };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve DM session isolation for default dmScope

Changing DM peers to kind: "direct" makes Zalo DM routing depend on session.dmScope; with the default dmScope="main", resolveAgentRoute collapses all direct chats into the main session key, so different DM senders can share context/history. The previous group peer workaround avoided this, so this change reintroduces cross-user context bleed for any deployment still on main-scope DMs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is for making the behavior align with other plugin.

const rows = groups.map((group) =>
mapGroup({
id: String(group.groupId),
id: `group:${String(group.groupId)}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep directory group IDs usable with members lookup

Prefixing directory group IDs as group:<id> here breaks the directory groups members flow, because listGroupMembers forwards groupId unchanged into listZaloGroupMembers, which calls api.getGroupInfo(groupId) with the raw value. Copying an ID from listGroups now passes group:<id> into a path that expects the native Zalo group ID, causing failed or empty member lookups.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed the problem

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fed1f260a7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const info = await api.fetchAccountInfo();
const user =
info && typeof info === "object" && "profile" in info ? (info.profile as User) : (info as User);
const user = info.profile as User;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle legacy fetchAccountInfo response shapes

getZaloUserInfo now assumes fetchAccountInfo() always returns { profile: User }, but this same commit still treats account-info shape as variable in resolveOwnUserId (falling back when profile is missing). If zca-js returns the legacy raw User object, info.profile is undefined and this function returns null, which drops account identity in status/probe/tooling flows despite a valid session. Keep the prior { profile } | User normalization (or share a helper) so account info remains available across response shapes.

Useful? React with 👍 / 👎.

@darkamenosa
Copy link
Copy Markdown
Member Author

Working to resolve these check. Here is a demo of the PR: https://youtu.be/Kuplh9AD3U8

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b5db7c8b9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const { senderAllowedForCommands, commandAuthorized } = await resolveSenderCommandAuthorization({
cfg: config,
rawBody,
const configGroupAllowFrom = (account.config.groupAllowFrom ?? []).map((v) => String(v));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize groupAllowFrom names to sender IDs

groupAllowFrom is consumed as raw strings here, but the startup normalization path in monitorZalouserProvider only resolves allowFrom names to numeric friend IDs. When operators configure groupAllowFrom with display names (which the new docs/config shape allow), resolveDmGroupAccessWithLists ends up checking numeric senderId values against unresolved names and can block all group messages under groupPolicy="allowlist". groupAllowFrom should go through the same name→ID resolution path as allowFrom before access checks.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 474bc798b9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (stopped || params.abortSignal.aborted) {
return;
}
cleanup();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Register active listener before invoking failure cleanup

If api.listener emits error/closed immediately during startup, failListener now calls cleanup() before the listener is recorded in activeListeners; later in the same startup path, activeListeners.set(...) still executes, but subsequent cleanup() calls are short-circuited by stopped, so that stale entry is never removed. In that state, restart attempts hit the early "already running" guard and the account can no longer recover automatically after the initial startup fault.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This review is not correct for the current zca-js behavior

@KennyDizi
Copy link
Copy Markdown

awesome 🚀

@darkamenosa
Copy link
Copy Markdown
Member Author

@shakkernerd If you have time, pls help me to have a look on this to see if I'm on right direction. So I can move on.

steipete added a commit that referenced this pull request Mar 8, 2026
@steipete
Copy link
Copy Markdown
Contributor

steipete commented Mar 8, 2026

Landed manually on top of current main because the branch had drifted.

Landed commit:
fcdc1a1

Source PR head:
40f6f14

What shipped:

  • kept canonical Zalo DM routing with isolated direct-session keys instead of regressing to shared main DM sessions
  • preserved legacy Zalo DM session continuity on upgrade when old group-shaped session keys already exist
  • preserved provider-native g- / u- target ids in outbound send and directory flows
  • added regression coverage and changelog/docs updates for zalouser: fix DM/group routing, media send, mention gating, and auto-restart #33992

Verified before landing:

  • pnpm lint
  • pnpm build
  • pnpm test

Thanks @darkamenosa.

@steipete steipete closed this Mar 8, 2026
Saitop pushed a commit to NomiciAI/openclaw that referenced this pull request Mar 8, 2026
GordonSH-oss pushed a commit to GordonSH-oss/openclaw that referenced this pull request Mar 9, 2026
jenawant pushed a commit to jenawant/openclaw that referenced this pull request Mar 10, 2026
dhoman pushed a commit to dhoman/chrono-claw that referenced this pull request Mar 11, 2026
senw-developers pushed a commit to senw-developers/va-openclaw that referenced this pull request Mar 17, 2026
V-Gutierrez pushed a commit to V-Gutierrez/openclaw-vendor that referenced this pull request Mar 17, 2026
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 22, 2026
Co-authored-by: Tom <[email protected]>
(cherry picked from commit fcdc1a1)
alexey-pelykh added a commit to remoteclaw/remoteclaw that referenced this pull request Mar 22, 2026
… CI fixes (#1794)

* fix: honor explicit Synology Chat rate-limit env values

Landed from contributor PR openclaw#39197 by @scoootscooob.

Co-authored-by: scoootscooob <[email protected]>
(cherry picked from commit af9d76b)

* fix: honor zero-valued voice-call STT settings

Landed from contributor PR openclaw#39196 by @scoootscooob.

Co-authored-by: scoootscooob <[email protected]>
(cherry picked from commit 28b72e5)

* fix: honor explicit OpenAI TTS speed values

Landed from contributor PR openclaw#39318 by @ql-wade.

Co-authored-by: ql-wade <[email protected]>
(cherry picked from commit 442f2c3)

* Voice Call: allowlist realtime STT api key fixtures

(cherry picked from commit b8b6569)

* Voice Call: read TTS internals in tests

(cherry picked from commit b1f7cf4)

* Voice Call: read realtime STT internals in tests

(cherry picked from commit 244aabb)

* test: fix gate regressions

(cherry picked from commit 56cd008)

* refactor: normalize voice-call runtime defaults

(cherry picked from commit 3087893)

* refactor: preserve explicit mock voice-call values

(cherry picked from commit f6c7ff3)

* fix(ci): resolve type regressions on main

(cherry picked from commit f721141)

* refactor(voice-call): share tts deep merge

(cherry picked from commit ed43743)

* fix: land openclaw#33992 from @darkamenosa

Co-authored-by: Tom <[email protected]>
(cherry picked from commit fcdc1a1)

* fix(ci): repair zalouser CI failures

(cherry picked from commit 06ffef8)

* fix: resolve cherry-pick type errors in zalouser extension

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: resolve cherry-pick type error in voice-call config test — adapt SecretRef to string apiKey

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
Co-authored-by: scoootscooob <[email protected]>
Co-authored-by: ql-wade <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: Tom <[email protected]>
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: zalouser Channel integration: zalouser docs Improvements or additions to documentation maintainer Maintainer-authored PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants