Add explicit phone call voice stacks - #52
Merged
Merged
Conversation
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Executive Summary
Description
The setup wizard now pauses before phone setup, presents the three supported voice stacks through the native OpenClaw selector, validates Realtime access before saving it, updates incoming-call routing, and reuses an already validated administrative credential when an authority change requires one. Hosted outbound calls send
mode=hosted_agent, a bounded reason, and the configured voicemail-detection value while omitting a per-call authority override so the saved Voice AI authority remains authoritative.Completed Voice AI calls are reconciled through OpenClaw's native agent and tool lifecycle hooks. Required SMS follow-ups are journaled before provider execution, bound to the exact call/session/run/tool/recipient, settled only from positive tool evidence, and recovered without duplicate sends after interruption. Durable replay data excludes transcript bodies; completed and terminal receipts retain only deduplication metadata and sanitized settlement outcomes.
For direct email and SMS turns, a successfully completed cross-channel action now closes the source channel with the plugin's existing
[SILENT]sentinel when the user did not also request a reply there. Explicit multipart requests still receive their requested source-channel reply.When
publicUrlowns local call media, the plugin now registers the exact account-aware WebSocket upgrade route and defers the handshake until the existing signed call bridge accepts it. Health checks compare the explicitly selected stack with the exact live server route and report stale actions, WebSocket URLs, and webhook callbacks without changing legacy absent-stack semantics. An explicit externalcallWebsocketUrlremains externally owned.The package is bumped from
0.2.7to0.2.8and pins the published Inkbox SDK0.5.9. The Dockerfile remains an explicitly documented manual-testing shell, not a published, CI, or release artifact.Reason
Phone handling needs one explicit user-selected stack, and Inkbox Voice AI calls must return transcripts and remaining actions to the user's primary agent. Outbound hosted calls should inherit the administrator-approved saved authority unless a caller explicitly narrows it. Post-call side effects must survive failures without trusting model prose, losing a safe correction, or duplicating an externally committed send.
Decisions
[SILENT]is unavailable on that first correction and available only after it fails.hosted_agentwith no callbacks and local stacks to useauto_acceptwith the exact current WebSocket and no stale webhook; preserve legacy behavior only when no stack was saved.Testing
Local verification on
6d30de1b55520524dd2a8a33468dd6a300086e4f:npm test— 518 tests passed across 40 files.npm run typecheck— passed.npm run build— passed.npm pack --dry-run— passed; package version is0.2.8.python3 -m pytest tests/live/test_voice_helpers.py -q— 9 helper tests passed; credential-gated live calls were not run locally.ruff checkon the voice suite, andgit diff --check— passed.Exact-head automation:
6d30de1b55520524dd2a8a33468dd6a300086e4f.6d30de1b55520524dd2a8a33468dd6a300086e4f, including channels, all four A2A scenarios, all four serialized voice scenarios, and external events.Live-test inventory:
0.5.9, selects local TTS/STT explicitly, and disables voicemail detection. Mock-model readiness probes now bound both connection and total request time, the channel job has a 25-minute cap, the voice job has a 15-minute cap, and failure diagnostics/artifacts also run on cancellation. This proves channel behavior against the release artifact, closes configuration drift where a call-capable run could accidentally inherit hosted/realtime routing or voicemail detection, and prevents a stalled readiness request or live scenario from consuming an unbounded runner while preserving evidence on abnormal termination.test_email_request_gets_sms_responseandtest_sms_request_gets_email_response: retain token-correlated cross-channel delivery under the explicit local voice configuration. The SMS-to-email case now keeps one shared 200-second budget, snapshots IDs and server timestamps for both email owners and both SMS owners before every fresh request, requires exactly one email with the exact AUT sender, driver recipient, and current token on both email owners, and permits one retry only after proving zero email and zero SMS side effects. A wrong recipient/channel/token, partial owner result, late prior-attempt row, or duplicate is terminal, with bounded duplicate-observation grace across both attempt tokens. This gate exposed a real product gap where the requested email succeeded but OpenClaw surfaced an incomplete-turn error as a source-channel SMS; the scoped[SILENT]completion policy closes that gap without masking tool/API failures, wrong delivery, late settlement, or duplicate sends.test_reports_sender_details: explicitly requests the full email address as well as the full phone number while retaining exact name/email/phone assertions. This closes the observed prompt/assertion gap where the model truthfully said an email was on file but did not spell it out because only the phone had been explicitly requested.test_email_request_gets_callandtest_sms_request_gets_call: require fresh exact-number call legs from both the driver and agent API owners, persistedvoicemail_detection=disabled, no duplicate AUT calls, and per-attempt diagnostics. One fresh request is allowed only after a completely empty first model turn; a partial/tool/API result is never retried. This closes stale-record, wrong-owner, model-empty-turn, hidden plugin-failure, and duplicate-dial false positives.test_inbound_call_inkbox_tts_stt: refreshes the exact driver-owned call record and proves persisted voicemail detection is disabled in addition to two-way speech and Inkbox STT/TTS. This closes the gap where request arguments or an old call record could pass without proving stored call policy.test_outbound_call_realtime: snapshots the exact target on both API owners, proves a fresh AUT outbound record uses Realtime (use_inkbox_tts=false,use_inkbox_stt=false), and proves voicemail detection is disabled. This prevents the latest unrelated call or driver-owned leg from satisfying the mode assertion.test_outbound_call_realtime_direct_contact_lookup: seeds a known caller and Olivia Parker contact, correlates the direct contact read to the exact AUT call ID in gateway logs, checks Realtime speech fields and disabled voicemail detection, and treats spoken transcript persistence as an additional diagnostic. A second request is permitted only for a completely empty model turn. This closes unit-only direct-tool coverage, late-transcript races, and duplicate-redial masking.test_outbound_call_hosted_and_settles_sms_once: proves exactly one current-run driver/AUT call pair using per-owner server timestamp watermarks and bounded inter-leg skew, hosted mode, reason, equality between the call authority snapshot and saved server authority, disabled voicemail detection, transcript commitment, authoritative open action before hangup, exact sender watermark/tool binding, durable call registry evidence, and exactly one API-accepted post-call SMS with bounded duplicate-observation grace. The canonical spoken stimulus separates the request to create an action from its exact five-word SMS body and requires the agent to read those words back only after persistence. Its run-derived marker uses five distinct common multisyllabic words selected from the fleet's shared speech-tested vocabulary because conventional phonetic-alphabet terms can be normalized into alternate words or familiar phrases across TTS/PSTN/STT. The exact transcript/action/recipient/body/receipt/duplicate assertions remain unchanged. The overall gate replaces first-fresh-pair, non-null-authority, transcript-only, receiver/carrier-only, stale synthetic-row, UUID/string mismatch, and fixed-sleep evidence.test_open_post_call_action_matches_marker_and_sms_intent_across_shapes: covers dictionary/SDK enum shapes and marker separator variants while requiring explicit open status and SMS send intent. This prevents shape or punctuation differences from creating false negatives.test_open_post_call_action_rejects_closed_wrong_marker_and_non_sms_items: rejects closed actions, wrong run markers, and non-SMS/non-send items. This prevents unrelated action text from authorizing hangup or settlement.[inkbox] realtime...) and structured (Inkbox realtime...) renderings only when the direct Inkbox contact tool and exact call ID match. This closes the prefix/case false-negative without allowing call existence alone to pass.0.5.9. These tests do not place calls; the update proves the public SDK bump and plugin install path preserve A2A behavior without claiming voice coverage.0.5.9with explicit local voice/voicemail configuration. These are dispatch/authentication tests and do not place calls; the configuration prevents accidental call-capable drift without overstating their coverage.call.endedacknowledgment without hosted work, and that generated contact memory cannot redirect a post-call SMS away from the authoritative call number.Focused deterministic coverage also includes wizard selection/rollback and pause ordering, hosted default authority, Realtime credential validation, post-call transcript/action reconciliation, exact-target tool settlement, pre-send journaling, wrong/duplicate call blocking, mixed-negation matching, one mandatory correction, crash-between-attempt recovery, terminal replay suppression, private registry permissions, bounded replay state, and minimal completed/terminal receipts.
New deterministic session contracts prove that successful direct email-to-SMS and SMS-to-email actions suppress the source-channel reply through
[SILENT], while an explicit multipart request can still produce its separately requested source-channel response.Related PRs
Hermes #83, Claude Code #52, Codex #35, and OpenCode #16.