Shared iMessage-line calling, identity-scoped call config, external webhook injection (SDK 0.4.15 parity)#13
Open
dimavrem22 wants to merge 4 commits into
Open
Conversation
…webhook injection from the hermes-agent-plugin reference Brings this bridge to parity with the hermes-agent-plugin reference implementation (its tools.py / adapter.py / setup_wizard.py / realtime.py on main) for the shared iMessage line and external event injection. Two calling lines (dedicated number vs shared iMessage line): - tools.py: inkbox_place_call gains an `origination` argument (dedicated_number / shared_imessage_number) with channel-aware auto-resolution — explicit choice wins; a single enabled line is used as-is; when BOTH lines exist the call follows the CURRENT conversation's channel (iMessage turn -> shared line, SMS/phone turn -> dedicated number; unknown -> dedicated); neither line -> clear error telling the agent to provision a number or enable iMessage. A shared-line call rejected with no_shared_connection returns a legible message (connect over iMessage first, or fall back to the dedicated number). The resolved origination is echoed in the result, with a TypeError retry for SDKs that predate the kwarg. - Channel source: sessions already track the last inbound modality (ContactSession.mode); handle_inbound now mirrors it into ~/.inkbox-codex/channel_hints.json and each session stamps INKBOX_CODEX_CHAT_ID into its MCP tool-server env, so the stdio tool process resolves the current conversation's channel at call time. - inkbox_whoami now returns a "lines" block labelling the dedicated phone line vs the shared iMessage line (whose number is managed by Inkbox and never surfaced), with per-line origination notes. - Outbound call WS URL resolution prefers the identity-scoped incoming-call config row, then the legacy number-scoped field, then the tunnel host — so an iMessage-only identity can place calls. Identity-scoped inbound-call config (gateway): - _patch_identity_objects now registers the incoming-call action via identity.set_incoming_call_action (one row covers the dedicated number AND the shared iMessage line), gated on having a number OR imessage_enabled; the number-scoped phone_numbers.update remains only as a fallback when the SDK lacks the method (and is skipped for iMessage-only identities it cannot express). - _handle_call_ws backfills remote number + direction through an identity-centered calls.get(call_id) round-trip when the upgrade carries no caller metadata (shared-line calls have no owning number). Realtime instructions: - RealtimeCallMeta.agent_imessage_enabled threads the identity's iMessage state into the instruction builder, which now names the dedicated line explicitly and describes the shared iMessage line without ever stating a number for it; calls follow the conversation's channel. Setup wizard channel flow: - iMessage step now runs FIRST (intro copy mentions voice calls over the same shared line) and returns bool(enabled). - Dedicated-number provisioning is a STANDALONE step decoupled from identity creation/signup/API-key paths: prints "Already provisioned: <number>" when one exists, and on provisioning failure points at Inkbox paid tiers (https://inkbox.ai/pricing) plus the raw error and moves on. - Realtime is offered when the identity has a number OR iMessage enabled (flag threaded explicitly since the local identity object may be stale). External webhook injection (new webhook_providers/ package): - Provider registry with drop-in modules (inkbox.py delegating to the SDK's verify_webhook, github.py verifying X-Hub-Signature-256); classify-before-auth in _handle_webhook: the source is identified by its signature header, verified with that source's secret (INKBOX_WEBHOOK_SECRET_<NAME> for third parties), and routing keys off the verified source — never the body's claimed event_type. - Unknown event types wake the agent on a per-source external: session with an act vs do-not-act directive (verified vs unverified), bounded payload surfacing, and request-id dedup; default-off via INKBOX_EXTERNAL_EVENTS_ENABLED, with registered third-party providers bypassing the flag. External-thread replies are never delivered. Also: - inkbox SDK pin gets a floor AND ceiling (>=0.4.15,<1.0.0) in pyproject, the wizard install requirements, and the doctor/gateway hints. - README: "Two calling lines" + "External events" sections, config reference rows, and tool-list updates; channel prompt gains a per-channel "Calling someone" section; .env.example documents the new vars. - Version bump 0.1.0 -> 0.1.1 (pyproject + plugin manifest). - Tests: origination resolution matrix (incl. channel-follow and explicit-wins), place-call handler paths, whoami lines block, identity-scoped incoming-call-action assertions (with legacy fallback), webhook-provider registry/classify/passthrough/dedup suite, wizard order + paid-tier fallback + iMessage-returns-bool + realtime-for-either-line, realtime two-lines instructions, session channel-hint + tool-env stamping, and call-record backfill. Full suite: 235 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings .github/workflows/{tests,canary,live-channels,live-voice}.yml plus
tests/live and tests/contract over from main (this branch predates the CI
stack; the old ci.yml is superseded by tests.yml's unit lane). Every
brought-over pull_request branches filter is widened to
[main, standardization] so the suites fire on PRs targeting this branch.
New live suite: live-external-events.yml boots the AUT gateway with
INKBOX_EXTERNAL_EVENTS_ENABLED=true and a per-run INKBOX_WEBHOOK_SECRET_GITHUB
(generated in the workflow, never committed), then runs two new tests:
- tests/live/test_external_event_intelligence.py — an Inkbox-signed CI
escalation POSTed at the gateway's local /webhook; asserts the real model
reasons "escalation -> call this contact" and actually dials the driver
(polled via calls.list; driver parked on auto_reject).
- tests/live/test_external_event_github.py — the same escalation signed the
GitHub way (X-Hub-Signature-256): a forged signature is 401'd and produces
no call, a valid one wakes the agent and it phones the driver.
Same tunnel-lock concurrency group and ready-PR gating as the other live
suites; secrets reuse the existing CODEX_INKBOX_* / REMOTE_INKBOX_API_KEY /
OPENAI_API_KEY set. Contract suite verified locally against a real codex
binary (5 passed); full unit run 240 passed / 20 skipped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live suites adopted from main assume the gateway auto-accepts Codex's 'allow the inkbox mcp server to run tool ...' elicitations, which main does unconditionally. This branch gates that behind INKBOX_CODEX_AUTO_APPROVE_INKBOX_TOOLS (default off), so every Inkbox tool prompt was escalated as a poll no one answers: cross-channel sends, whoami, and place_call all stalled until the test timeouts, and the pending poll swallowed the next inbound message (the identity-test off-by-one). Set the flag in all three live workflows' gateway env, next to CODEX_APPROVAL_POLICY=never, for the same unattended-runner reason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…le name Run 28649399588: the Inkbox-signed escalation passed but the GitHub one timed out — the gateway log shows the turn WAS injected and the agent auto-approved inkbox_list_contacts three times without ever reaching inkbox_place_call. The suite hardcoded "Jane Doe" but only seeded that contact when the AUT org had NO card for the driver number; ours already carries one under a different name, so the agent was asked to call a contact it could not resolve and (correctly) never dialed. The envelope now addresses the driver by the name on the existing card, seeding Jane Doe only when absent — the same resolution the Inkbox-signed suite already uses, which is exactly why it passed in the same run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Brings this plugin to parity with hermes-agent-plugin main (its #35 shared-line calling + #31 external events), completing the 0.4.15 adoption that #11 only pin-bumped.
What's in here
inkbox>=0.4.15,<1.0.0(restores the breaking-major ceiling).identity.set_incoming_call_action(...)replaces the removed number-scopedphone_numbers.updatecall-config write, gated on (dedicated number OR iMessage enabled) with a guarded legacy fallback; WS-URL read prefersget_incoming_call_action().origination(dedicated_number|shared_imessage_number) oninkbox_place_callwith channel-aware resolution — explicit wins → single-line auto → both lines follow the current conversation's channel → unknown defaults to dedicated; legible message onno_shared_connection. Two-lines terminology in whoami (linesblock), realtime instructions, prompts, docs. The shared line's number is never surfaced.0.1.0 → 0.1.1.Tests
With real inkbox SDK 0.4.15: 235 passed, 0 skipped. CI-like venv: 234 passed, 1 skipped. All standardization-branch tests stay green. New suites: webhook providers (530 lines), origination matrix, wizard flow.
Note: pre-existing fixtures on the base branch use a real-looking number (+16614031457) — flagged for a separate cleanup, not touched here.
CI parity
Second commit adopts the full CI stack from
mainonto this branch (thestandardizationbase predates it):tests/canary/live-channels/live-voiceworkflows +tests/live+tests/contract, withpull_requestbranch filters widened to[main, standardization]so they fire on this stacked PR; the oldci.ymlis dropped (superseded by tests.yml's identical unit lane). Also addslive-external-events.yml+ its live tests — the live proof of the webhook-injection feature.