Skip to content

Add explicit phone call voice stacks - #52

Merged
dimavrem22 merged 34 commits into
mainfrom
feat/phone-call-voice-stacks
Aug 1, 2026
Merged

Add explicit phone call voice stacks#52
dimavrem22 merged 34 commits into
mainfrom
feat/phone-call-voice-stacks

Conversation

@dimavrem22

@dimavrem22 dimavrem22 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Executive Summary

  • Adds a three-choice phone-call wizard for Inkbox Voice AI, OpenAI Realtime, and Inkbox TTS/STT, including explicit incoming-route synchronization.
  • Places hosted calls with saved Voice AI authority defaults and sends completed-call context back through the primary OpenClaw agent.
  • Makes post-call SMS settlement restart-safe, exact-call/exact-target bound, and duplicate-resistant.

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 publicUrl owns 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 external callWebsocketUrl remains externally owned.

The package is bumped from 0.2.7 to 0.2.8 and pins the published Inkbox SDK 0.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

  • Voice-stack persistence: Store an explicit stack while preserving legacy configurations when the field is absent.
  • Hosted authority: Omit per-call authority so hosted outbound calls use the saved Voice AI default.
  • Administrative credentials: Validate and reuse an available administrative credential only for authority changes, without persisting it as the plugin credential.
  • SMS correction: Require one correction for a missing attempt or deterministic retryable no-commit failure. [SILENT] is unavailable on that first correction and available only after it fails.
  • Ambiguous outcomes: Stop on pending, successful-but-unfinalized, wrong-target, duplicate, timeout, throttling, service, carrier, and unknown outcomes rather than risk a duplicate send.
  • Restart recovery: Resume only a known no-commit initial failure as the single SMS-only correction; any correction attempt or commit ambiguity is terminal.
  • Hook permission: Enable only the host-gated conversation hook needed to bind the exact generated run; completion journals still exclude conversation bodies.
  • Live calling: Explicitly disable voicemail detection in every CI configuration or request capable of placing a call.
  • Cross-channel completion: Suppress source-channel confirmation/error prose only after a requested cross-channel action succeeds and only when no source-channel reply was also requested.
  • Public call media: Serve the canonical account-aware WebSocket route through OpenClaw's plugin upgrade surface; preserve signature verification and bridge-selected response headers, and fail closed before upgrade on rejection.
  • Health contract: Require Voice AI to use hosted_agent with no callbacks and local stacks to use auto_accept with 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 is 0.2.8.
  • python3 -m pytest tests/live/test_voice_helpers.py -q — 9 helper tests passed; credential-gated live calls were not run locally.
  • Python compilation, ruff check on the voice suite, and git diff --check — passed.

Exact-head automation:

  • PR checks — typecheck · unit · contract — passed on exact head 6d30de1b55520524dd2a8a33468dd6a300086e4f.
  • Full stack e2e — passed on exact head 6d30de1b55520524dd2a8a33468dd6a300086e4f, including channels, all four A2A scenarios, all four serialized voice scenarios, and external events.

Live-test inventory:

  • Updatedlive channel mock/real suite: installs published Python/TypeScript SDK 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.
  • Updatedtest_email_request_gets_sms_response and test_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.
  • Updatedtest_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.
  • Updatedtest_email_request_gets_call and test_sms_request_gets_call: require fresh exact-number call legs from both the driver and agent API owners, persisted voicemail_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.
  • Updatedtest_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.
  • Updatedtest_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.
  • Newtest_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.
  • Newtest_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.
  • Newtest_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.
  • Newtest_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.
  • Newdirect-read log normalization helper coverage: accepts both console ([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.
  • Newhosted marker/workflow helper coverage: proves deterministic five-word markers use distinct common multisyllabic vocabulary, survive punctuation, whitespace, hyphen, and case variants, and remain tied to the current run; exhaustively checks all 100,000 five-digit inputs during review; requires the 180-second driver window so pytest owns intentional hangup; and verifies public mismatch diagnostics contain only booleans, counts, and lengths rather than action or message content. This closes ASR separator false negatives, premature driver teardown, and unclassifiable public failure output without weakening exact matching.
  • Updatedlive A2A workflow: runs every existing inbound/outbound single/multi protocol scenario against SDK 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.
  • Updatedlive external-event workflow: runs the existing signed/forged GitHub and signed Inkbox dispatch tests against SDK 0.5.9 with 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.
  • Updatedfour-leg live voice workflow: serializes inbound TTS/STT, outbound Realtime, outbound Realtime direct-contact, and outbound Voice AI scenarios with explicit stack selection and disabled voicemail detection. Serialization closes shared-identity cross-test races; exact scenario configuration prevents one stack from passing another stack's acceptance test.
  • Newpublic-URL WebSocket and health contract suite: proves exact default and named-account upgrade paths, deferred bridge acceptance headers, signed request metadata, text/binary frame transport, explicit and declined subprotocols, fail-closed rejection, failed-registration retry, external URL ownership, startup cleanup, both local stacks, hosted routing, strict stale-route diagnostics, SDK null callbacks, and legacy absent-stack compatibility.
  • Newwizard and completion acceptance cases: prove saved-stack rerun defaults, invalid readline fallback, cancellation before delivery mutation, non-hosted call.ended acknowledgment 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.

@dimavrem22
dimavrem22 merged commit 7b87671 into main Aug 1, 2026
16 checks passed
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