feat(cli): add experimental voice gateway - #8429
Conversation
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an opt-in OpenClaw voice gateway with secure credential loading, authenticated expiring sessions, one committed streamed text turn, an HTTP/NDJSON interface, graceful lifecycle handling, CLI integration, documentation, and comprehensive tests. ChangesVoice gateway
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Runtime
participant VoiceGatewayServer
participant VoiceSessionService
participant OpenClawVoiceClient
Runtime->>VoiceGatewayServer: Create authenticated session
VoiceGatewayServer->>VoiceSessionService: Create session
Runtime->>VoiceGatewayServer: Submit one text turn
VoiceGatewayServer->>VoiceSessionService: Commit turn
VoiceSessionService->>OpenClawVoiceClient: Send chat turn
OpenClawVoiceClient-->>VoiceSessionService: Stream agent events
VoiceSessionService-->>VoiceGatewayServer: Return ordered events
VoiceGatewayServer-->>Runtime: Stream NDJSON response
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/pluginThe overall coverage in commit 79b1425 in the TypeScript / code-coverage/cliThe overall coverage in commit 79b1425 in the Show a code coverage summary of the most impacted files.
Updated |
|
🌿 Preview your docs: https://nvidia-preview-pr-8429.docs.buildwithfern.com/nemoclaw |
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
src/lib/voice-gateway/openclaw-client.ts (2)
114-285: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftSplit
runTurninto smaller units.
runTurnspans about 170 lines and declares eight nested closures that share mutable turn state:clearPending,request,finish,cancelCurrent,handleChat,onmessage,onerror, andonclose. The coding guidelines require low function complexity.Extract the per-turn state and its closures into a private turn-scoped helper class or factory, for example a
TurnSessionthat ownspending,activeRunId,previousText, andterminal.runTurnthen performs the handshake, sendschat.send, and awaits the terminal result.This refactor also gives the transport teardown in the comment on
finisha single owner.Based on coding guidelines: "Keep function complexity low."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/voice-gateway/openclaw-client.ts` around lines 114 - 285, Refactor runTurn into smaller units by introducing a private turn-scoped helper such as TurnSession to own pending requests, queued events, activeRunId, previousText, terminal state, request handling, event handlers, finish, and cancellation. Keep runTurn focused on socket creation, handshake, chat.send, and awaiting the terminal result, while preserving existing outcomes and event behavior. Make the helper the single owner of transport teardown associated with finish.Source: Coding guidelines
218-225: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBound the queued chat events by bytes, not only by count.
MAX_QUEUED_CHAT_EVENTScaps the queue at 128 entries. Each entry can carry text close toMAX_NATIVE_FRAME_BYTES, which is about 2 MB. The queue can therefore retain roughly 270 MB beforeactiveRunIdis set. The window is short, and it requires a hostile or faulty gateway, so this is a resource bound rather than an exploitable defect.Track the accumulated queued text size and fail with
agent_protocol_erroronce it passesVOICE_GATEWAY_MAX_RESPONSE_BYTES.🛠️ Proposed fix
+ let queuedChatBytes = 0;if (activeRunId === null) { - if (queuedChatEvents.length >= MAX_QUEUED_CHAT_EVENTS) { + queuedChatBytes += Buffer.byteLength(chat.text); + if ( + queuedChatEvents.length >= MAX_QUEUED_CHAT_EVENTS || + queuedChatBytes > VOICE_GATEWAY_MAX_RESPONSE_BYTES + ) { finish({ outcome: "failed", reason: "agent_protocol_error" }); socket.close(); return; } queuedChatEvents.push(chat); } else handleChat(chat);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/voice-gateway/openclaw-client.ts` around lines 218 - 225, Update the queueing logic around activeRunId and queuedChatEvents to track the accumulated text byte size of queued chat events, using the same encoding used for payload-size limits. Reject the event and finish with outcome "failed" and reason "agent_protocol_error" before enqueueing when the accumulated size exceeds VOICE_GATEWAY_MAX_RESPONSE_BYTES, while preserving the existing MAX_QUEUED_CHAT_EVENTS limit and reset the byte counter when the queued events are drained or otherwise discarded.src/lib/adapters/http/voice-gateway-server.ts (1)
90-93: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAccept
application/jsonwith media-type parameters.Line 90 compares the raw header with strict equality. A conforming client that sends
content-type: application/json; charset=utf-8receives 415. Parse the media type instead of comparing the full header value.♻️ Proposed fix for the media-type gate
- if (request.headers["content-type"] !== "application/json") { + const mediaType = (request.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase(); + if (mediaType !== "application/json") { reject(new BodyError(415)); return; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/adapters/http/voice-gateway-server.ts` around lines 90 - 93, Update the content-type validation in the request handling flow to parse the header’s media type and accept application/json when parameters such as charset are present, while continuing to reject other media types with BodyError(415).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/voice-gateway/serve.test.ts`:
- Around line 77-82: Remove the typeof check in the fake listener’s listen
method and invoke the final argument directly as the callback, preserving the
existing argument capture, listening state, and fluent return behavior.
In `@src/lib/actions/voice-gateway/serve.ts`:
- Around line 69-84: Update the OpenClaw gateway URL validation in the
surrounding serve flow to require authenticated encrypted transport instead of
allowing ws:// loopback connections: accept only wss: URLs with appropriate peer
validation, or route the credential-bearing OpenClawVoiceClient connection
through authenticated local IPC. Preserve the existing credential-free,
explicit-port, /ws-path, and URL-field restrictions.
In `@src/lib/adapters/http/voice-gateway-server.ts`:
- Around line 142-144: Update the request-body listeners near the existing
`aborted`, `error`, and `end` handlers to remove the deprecated `aborted`
listener and add a `close` listener that calls `finish(new BodyError(400))` only
when `request.readableEnded` is false. Preserve the existing `settled` guard
used by the normal `end` completion path.
In `@src/lib/voice-gateway/credential-file.ts`:
- Around line 43-53: Update the error check in the credential file open logic
around fs.openSync to treat both ELOOP and EMLINK as symbolic-link errors.
Preserve the existing descriptive error and rethrow behavior for all other error
codes.
In `@src/lib/voice-gateway/openclaw-client.test.ts`:
- Around line 34-58: Refactor FakeWebSocket.send to remove all method-dispatch
conditionals and the oversizedFrameAfterConnect flag. Add a per-method Handler
map supplied by each test, make send queue the matching handler linearly, and
expose respond and event for handlers; move connect/chat.send response and frame
scenarios into the individual test handlers, removing firstReply and finalReply
from the fake state.
- Around line 146-163: Update the revocation test around
OpenClawVoiceClient.runTurn to be asynchronous: await the FakeWebSocket open
handshake before calling client.close(), retain the runTurn promise, and assert
its resolved outcome after revocation. Add the required vi import from vitest if
needed to coordinate the handshake, while preserving the existing socket-closed
assertion.
In `@src/lib/voice-gateway/openclaw-client.ts`:
- Around line 160-167: Update the turn lifecycle around finish, settleOpen, and
the socket reference so finish settles any pending open handshake, closes the
WebSocket, and then resolves the terminal result. Assign settleOpen alongside
rejectOpen during handshake setup, and remove redundant direct socket.close()
calls from terminal branches so finish is the single transport-release owner.
In `@test/voice-gateway-integration.test.ts`:
- Around line 51-63: Move the conditional helper logic from the test file into a
new server-harness fixture module alongside PinnedVoiceRuntimeAdapter. Relocate
listen and the afterEach server-cleanup loop into exported listen and
closeListeningServers helpers, then update the test to import and call them so
the test file contains only linear helper calls.
---
Nitpick comments:
In `@src/lib/adapters/http/voice-gateway-server.ts`:
- Around line 90-93: Update the content-type validation in the request handling
flow to parse the header’s media type and accept application/json when
parameters such as charset are present, while continuing to reject other media
types with BodyError(415).
In `@src/lib/voice-gateway/openclaw-client.ts`:
- Around line 114-285: Refactor runTurn into smaller units by introducing a
private turn-scoped helper such as TurnSession to own pending requests, queued
events, activeRunId, previousText, terminal state, request handling, event
handlers, finish, and cancellation. Keep runTurn focused on socket creation,
handshake, chat.send, and awaiting the terminal result, while preserving
existing outcomes and event behavior. Make the helper the single owner of
transport teardown associated with finish.
- Around line 218-225: Update the queueing logic around activeRunId and
queuedChatEvents to track the accumulated text byte size of queued chat events,
using the same encoding used for payload-size limits. Reject the event and
finish with outcome "failed" and reason "agent_protocol_error" before enqueueing
when the accumulated size exceeds VOICE_GATEWAY_MAX_RESPONSE_BYTES, while
preserving the existing MAX_QUEUED_CHAT_EVENTS limit and reset the byte counter
when the queued events are drained or otherwise discarded.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 0e53fb0c-8aa2-4dab-bc79-17306f12e96d
📒 Files selected for processing (17)
ci/source-architecture-budget.jsondocs/reference/commands.mdxsrc/commands/internal/voice-gateway/serve.tssrc/lib/actions/voice-gateway/serve.test.tssrc/lib/actions/voice-gateway/serve.tssrc/lib/adapters/http/voice-gateway-server.tssrc/lib/voice-gateway/contracts.tssrc/lib/voice-gateway/credential-file.test.tssrc/lib/voice-gateway/credential-file.tssrc/lib/voice-gateway/openclaw-client.test.tssrc/lib/voice-gateway/openclaw-client.tssrc/lib/voice-gateway/session-service.test.tssrc/lib/voice-gateway/session-service.tstest/fixtures/voice-gateway/pinned-runtime-adapter.tstest/internal-cli.test.tstest/package-contract/cli/oclif-metadata.test.tstest/voice-gateway-integration.test.ts
PR Review Advisor — No blocking findings reportedAdvisor assessment: No blocking advisor findings reported Model lanes
8 terminology differences from the second opinionAdvisory only. These are normalized differences from the primary terminology receipt.
2 additional E2E selections from the second opinionAdvisory only. The primary lane did not select these E2E jobs or targets.
Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests. 4 semantic terminology decisionsTerminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.
E2E guidanceAdvisory only. A maintainer can dispatch the default E2E suite against this exact revision. Recommended E2E: This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/voice-gateway/openclaw-client.test.ts (1)
46-56: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winExercise events that arrive before the
chat.sendresponse.
FakeWebSocketalways callsrespondon Line 49 before it queues chat events on Lines 50-55. This does not exercise thequeuedChatEventspath inOpenClawVoiceClient.runTurn, which handles matching events received beforeactiveRunIdis set.Add a test-specific scenario that emits a matching event before the response, then assert the ordered result through
runTurn.As per path instructions, verify this through the public
runTurnresult and emitted events rather than fake internals.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/voice-gateway/openclaw-client.test.ts` around lines 46 - 56, Update the FakeWebSocket chat.send scenario and its test to emit a matching expected-run event before respond assigns the run ID, exercising queuedChatEvents in OpenClawVoiceClient.runTurn. Verify through the public runTurn result and emitted event ordering, without asserting fake websocket internals.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/lib/voice-gateway/openclaw-client.test.ts`:
- Around line 46-56: Update the FakeWebSocket chat.send scenario and its test to
emit a matching expected-run event before respond assigns the run ID, exercising
queuedChatEvents in OpenClawVoiceClient.runTurn. Verify through the public
runTurn result and emitted event ordering, without asserting fake websocket
internals.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 8f7b35f9-7b08-4975-aef7-3f3e247cb0c1
📒 Files selected for processing (3)
src/lib/actions/voice-gateway/serve.test.tssrc/lib/voice-gateway/openclaw-client.test.tstest/voice-gateway-integration.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/lib/actions/voice-gateway/serve.test.ts
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/voice-gateway/credential-file.test.ts (1)
95-97: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAssert that credential errors do not expose bearer material.
The current matcher accepts an error that contains both
messageandCREDENTIAL. Assert that each caught error message excludesCREDENTIAL. This verifies the content-free failure contract.Proposed test update
])("rejects a $name before returning credential material (`#8378`)", ({ arrange, message }) => { - expect(() => readPrivateBearerFile(arrange(), "Test credential")).toThrow(message); + let error: unknown; + try { + readPrivateBearerFile(arrange(), "Test credential"); + } catch (caught) { + error = caught; + } + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain(message); + expect((error as Error).message).not.toContain(CREDENTIAL); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/voice-gateway/credential-file.test.ts` around lines 95 - 97, Update the parameterized test around readPrivateBearerFile to capture the thrown error and assert its message excludes the literal CREDENTIAL, while retaining the existing expected-message assertion. Ensure every credential rejection case verifies that bearer material is not exposed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/lib/voice-gateway/credential-file.test.ts`:
- Around line 95-97: Update the parameterized test around readPrivateBearerFile
to capture the thrown error and assert its message excludes the literal
CREDENTIAL, while retaining the existing expected-message assertion. Ensure
every credential rejection case verifies that bearer material is not exposed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: fcdefd2c-db2d-46dd-831b-537018ce76c6
📒 Files selected for processing (17)
ci/source-architecture-budget.jsondocs/reference/commands.mdxsrc/commands/internal/voice-gateway/serve.tssrc/lib/actions/voice-gateway/serve.test.tssrc/lib/actions/voice-gateway/serve.tssrc/lib/adapters/http/voice-gateway-server.tssrc/lib/voice-gateway/contracts.tssrc/lib/voice-gateway/credential-file.test.tssrc/lib/voice-gateway/credential-file.tssrc/lib/voice-gateway/openclaw-client.test.tssrc/lib/voice-gateway/openclaw-client.tssrc/lib/voice-gateway/session-service.test.tssrc/lib/voice-gateway/session-service.tstest/fixtures/voice-gateway/pinned-runtime-adapter.tstest/internal-cli.test.tstest/package-contract/cli/oclif-metadata.test.tstest/voice-gateway-integration.test.ts
🚧 Files skipped from review as they are similar to previous changes (14)
- ci/source-architecture-budget.json
- docs/reference/commands.mdx
- src/lib/actions/voice-gateway/serve.ts
- src/lib/voice-gateway/contracts.ts
- src/lib/actions/voice-gateway/serve.test.ts
- test/fixtures/voice-gateway/pinned-runtime-adapter.ts
- src/commands/internal/voice-gateway/serve.ts
- src/lib/voice-gateway/credential-file.ts
- test/internal-cli.test.ts
- src/lib/voice-gateway/session-service.test.ts
- test/package-contract/cli/oclif-metadata.test.ts
- src/lib/voice-gateway/session-service.ts
- test/voice-gateway-integration.test.ts
- src/lib/voice-gateway/openclaw-client.ts
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
<!-- markdownlint-disable MD041 --> ## Summary Adds the canonical dated changelog entry required before cutting `v0.0.104`. The entry reconciles user-facing changes merged from `v0.0.103` through `8d2b86aaf44968b4f7bc3b714222a73bd28e0403` while excluding hidden and experimental product surfaces. ## Changes - Added `docs/changelog/2026-08-06.mdx` with the exact `## v0.0.104` heading and release themes for local inference, private endpoints, network policy, state authority, lifecycle recovery, uninstall, Hermes, MCP diagnostics, credential safety, and installation guidance. - Source summary links: - [#8399](#8399) -> `docs/changelog/2026-08-06.mdx`: fixed DGX Spark local serving profiles. - [#8418](#8418) -> `docs/changelog/2026-08-06.mdx`: durable llama.cpp lifecycle management. - [#8422](#8422) -> `docs/changelog/2026-08-06.mdx`: recoverable llama.cpp receipt publication. - [#8402](#8402) -> `docs/changelog/2026-08-06.mdx`: remediable DGX Spark storage admission. - [#8391](#8391) -> `docs/changelog/2026-08-06.mdx`: host-local serving recipe contracts. - [#8401](#8401) -> `docs/changelog/2026-08-06.mdx`: serving profile lifecycle provenance. - [#8322](#8322) -> `docs/changelog/2026-08-06.mdx`: guarded llama.cpp route compatibility. - [#8272](#8272) -> `docs/changelog/2026-08-06.mdx`: explicitly trusted private endpoints with stable policy pins and CA trust. - [#8431](#8431) -> `docs/changelog/2026-08-06.mdx`: Personal onboarding policy tier and its trust boundary. - [#8143](#8143) -> `docs/changelog/2026-08-06.mdx`: manifest-derived state authority. - [#7859](#7859) -> `docs/changelog/2026-08-06.mdx`: side-effect-free lifecycle lock timeouts. - [#8262](#8262) -> `docs/changelog/2026-08-06.mdx`: managed gateway lease waiting. - [#8339](#8339) -> `docs/changelog/2026-08-06.mdx`: continued journaled rebuild recreation. - [#8373](#8373) -> `docs/changelog/2026-08-06.mdx`: restore readiness after compatibility decisions. - [#8443](#8443) -> `docs/changelog/2026-08-06.mdx`: fail-closed malformed registry handling. - [#8419](#8419) -> `docs/changelog/2026-08-06.mdx`: bounded recovery for a gateway that never served. - [#8486](#8486) -> `docs/changelog/2026-08-06.mdx`: target-scoped registry recovery. - [#8259](#8259) -> `docs/changelog/2026-08-06.mdx`: scoped uninstall ordering and retry safety. - [#8457](#8457) -> `docs/changelog/2026-08-06.mdx`: desktop metadata exclusion during uninstall. - [#8026](#8026) -> `docs/changelog/2026-08-06.mdx`: typed Hermes configuration policy. - [#8242](#8242) -> `docs/changelog/2026-08-06.mdx`: Hermes WhatsApp session diagnostics. - [#8344](#8344) -> `docs/changelog/2026-08-06.mdx`: patched Hermes image and dependency checks. - [#8491](#8491) -> `docs/changelog/2026-08-06.mdx`: bounded MCP discovery timeout. - [#8490](#8490) -> `docs/changelog/2026-08-06.mdx`: MCP shadow diagnostics. - [#7619](#7619) -> `docs/changelog/2026-08-06.mdx`: web-search credential isolation. - [#8476](#8476) -> `docs/changelog/2026-08-06.mdx`: stable preflight advisory identifiers. - [#8452](#8452) -> `docs/changelog/2026-08-06.mdx`: user-local CLI resolution. - [#8481](#8481) -> `docs/changelog/2026-08-06.mdx`: remote network-policy terminal guidance. - Product-scope exclusions: [#8429](#8429) remains experimental; [#8261](#8261) remains feature-gated; and portable-profile changes [#8408](#8408), [#8415](#8415), [#8446](#8446), [#8458](#8458), [#8462](#8462), and [#8506](#8506) are not promoted as supported product surfaces. ## Type of Change - [ ] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [x] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [ ] Tests added or updated for changed behavior - [x] Existing tests cover changed behavior — justification: `npx vitest run test/changelog-docs.test.ts` passed 6/6 and validates dated changelog structure and published links. - [ ] Tests not applicable — justification: - [x] Docs updated for user-facing behavior changes - [ ] Docs not applicable — justification: - [ ] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [ ] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Documentation Writer Review - [x] Documentation writer subagent reviewed the completed changes - Result: `docs-updated` - Evidence: `docs/changelog/2026-08-06.mdx`; release-range scope, writing rules, documentation style, skip terms, exact names, threat-boundary wording, and published routes reviewed; changelog tests and docs build passed. - Agent: Codex Desktop <!-- docs-review-head-sha: 02b51ae --> <!-- docs-review-agents-blob-sha: c69aad4 --> ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: Not applicable; no DGX Station host preparation script changed. - Station profile/scenario: Not applicable. - Result: Not applicable. - Supporting evidence: Not applicable. ## Verification - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run validate:pr` passed after refreshing `origin/main` when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: `npx vitest run test/changelog-docs.test.ts` passed 6/6. - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes — command/result: Not applicable to a single changelog entry. - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [x] `npm run docs` builds without warnings (doc changes only) - [x] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) The new dated changelog file includes the required parser-safe SPDX header and intentionally has no frontmatter, matching the changelog contract and existing entries. --- Signed-off-by: Charan Jagwani <cjagwani@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added release notes for v0.0.104. * Documented fixes for local model runtimes, private endpoints, network policies, state recovery, uninstall behavior, safety updates, MCP diagnostics, credential isolation, and installation guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
Add a hidden, explicitly gated OpenClaw voice gateway that routes one committed text turn for one authenticated runtime session. The adapter keeps routing authority in NemoClaw, binds only to loopback, and exposes no supported public product surface.
Related Issue
Fixes #8378
Changes
nemoclaw internal voice-gateway serve, gated by the exact valueNEMOCLAW_EXPERIMENTAL_VOICE_GATEWAY=1before credentials are read or a socket is opened.chat.send, generated session and idempotency identifiers, and exact session/run event filtering.The service boundary is required by #8378 so a separately authenticated voice runtime can submit one committed turn without receiving authority to choose an agent, sandbox, upstream endpoint, OpenClaw session, or run. The session service and integration tests protect that boundary.
Type of Change
Quality Gates
Documentation Writer Review
docs-updateddocs/reference/commands.mdx; exact-head review PASS with no actionable findings;npm run docscompleted with 0 errors and 2 existing unprinted warnings.DGX Station Hardware Evidence
Verification
Signed-off-by:line and every commit appears asVerifiedin GitHubpre-commit,commit-msg, andpre-pushhooks passed, ornpm run validate:prpassed after refreshingorigin/mainwhen hooks were skipped or unavailablenpm run typecheck:cli; 37/37 focused CLI tests; 10/10 focused integration tests;npm run build:cli; 5/5 package metadata contract tests;npm run checks:repository; andnpm run docsall pass on the rebased change set.npm testfor broad runtime/test-harness changes;npm run checkfor repo-wide validation/coverage changes — Initialnpm test: 26,648 passed and 19 failed. Three documentation-parity failures caused by this change were fixed and their focused suites pass. The remaining 16 failures are unrelated environment/current-main failures involving future npm registry artifacts and package output, DGX detection, Hermes privilege topology, remediation fixtures, and plugin type-check output. No maintainer waiver is claimed.npm run docsbuilds without warnings (doc changes only) — 0 errors and 2 existing unprinted warnings.Signed-off-by: Julie Yaunches jyaunches@nvidia.com
Summary by CodeRabbit