Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7558977
feat: add phone call voice stacks
dimavrem22 Jul 31, 2026
ba48a1e
ci: acknowledge local plugin lifecycle helper
dimavrem22 Jul 31, 2026
c52c313
chore: bump plugin version to 0.2.8
dimavrem22 Jul 31, 2026
e8eb2f6
fix: make hosted call setup recoverable
dimavrem22 Jul 31, 2026
844a306
fix: roll back setup on prompt aborts
dimavrem22 Jul 31, 2026
3b12cab
fix: gate delivery retries by failure policy
dimavrem22 Jul 31, 2026
e407946
fix: pin hosted SMS follow-ups to caller
dimavrem22 Aug 1, 2026
b93646f
test: enforce terminal iMessage silence
dimavrem22 Aug 1, 2026
24e38f5
fix: settle hosted call SMS actions from tool hooks
dimavrem22 Aug 1, 2026
3581f30
fix: honor negated post-call SMS requests
dimavrem22 Aug 1, 2026
4adde5e
test: normalize hosted call mode enums
dimavrem22 Aug 1, 2026
b789966
fix: harden hosted call settlement correlation
dimavrem22 Aug 1, 2026
187c04f
fix: make hosted SMS settlement restart-safe
dimavrem22 Aug 1, 2026
b4cfe17
test: assert hosted mode on caller record
dimavrem22 Aug 1, 2026
70617fe
fix: enable hosted settlement conversation hook
dimavrem22 Aug 1, 2026
e178510
test: verify hosted SMS from sender receipt
dimavrem22 Aug 1, 2026
fbc21bd
test: stabilize hosted voice completion proof
dimavrem22 Aug 1, 2026
feab8e8
test: reserve hosted duplicate grace
dimavrem22 Aug 1, 2026
7d44bb0
test: gate hosted hangup on persisted action
dimavrem22 Aug 1, 2026
cc56cbc
test: complete voice stack acceptance
dimavrem22 Aug 1, 2026
7588ea2
test: harden live voice proof gates
dimavrem22 Aug 1, 2026
2812fbf
ci: disable voicemail detection in a2a
dimavrem22 Aug 1, 2026
b13f0e4
fix: preserve realtime credentials across stack changes
dimavrem22 Aug 1, 2026
296e8f6
test: harden cross-channel email recovery
dimavrem22 Aug 1, 2026
32b956e
fix: suppress completed cross-channel source replies
dimavrem22 Aug 1, 2026
643406a
test: request complete sender details
dimavrem22 Aug 1, 2026
8c6f8e8
ci: bound local readiness probes
dimavrem22 Aug 1, 2026
d6c3db0
ci: cap live channel jobs
dimavrem22 Aug 1, 2026
6b8ee80
fix: verify and serve selected voice routes
dimavrem22 Aug 1, 2026
f7d3944
test: clarify hosted action stimulus
dimavrem22 Aug 1, 2026
167a307
test: harden hosted speech marker proof
dimavrem22 Aug 1, 2026
6d30de1
test: use robust hosted speech markers
dimavrem22 Aug 1, 2026
acb6e03
docs: describe live CI coverage
dimavrem22 Aug 1, 2026
192338a
fix: allow first-party contact detail access
dimavrem22 Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.git
.github
node_modules
coverage
dist
*.log
.env
.env.*
12 changes: 2 additions & 10 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,15 @@ jobs:

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: a4dd76b534e33c0a8352148d8f0e9ab25199f05b
path: .ci/inkbox

- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm

- name: Install deps + latest OpenClaw host
run: |
npm ci --prefix .ci/inkbox/sdk/typescript
npm run build --prefix .ci/inkbox/sdk/typescript
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@latest
npm ci
npm install --no-save --package-lock=false openclaw@latest
npm ls openclaw || true

- name: Typecheck vs latest OpenClaw
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/live-a2a.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ jobs:
python-version: "3.12"

- name: Install protocol driver
run: pip install 'inkbox==0.5.8'
run: pip install 'inkbox==0.5.9'

- name: Build plugin
run: |
npm ci
npm install --no-save --package-lock=false \
@inkbox/sdk@0.5.8 openclaw@2026.5.27
@inkbox/sdk@0.5.9 openclaw@2026.5.27
npm run build

- name: Install OpenClaw
Expand Down Expand Up @@ -98,11 +98,12 @@ jobs:
print(client.mailboxes.list()[0].email_address.split("@", 1)[0])
PY
)"
openclaw plugins install -l "$GITHUB_WORKSPACE"
openclaw plugins install -l --dangerously-force-unsafe-install "$GITHUB_WORKSPACE"
openclaw plugins enable inkbox || true
openclaw config set channels.inkbox.enabled true --strict-json
openclaw config set channels.inkbox.apiKey "$OPENCLAW_INKBOX_API_KEY"
openclaw config set channels.inkbox.identity "$HANDLE"
openclaw config set channels.inkbox.voicemailDetection disabled
openclaw config set channels.inkbox.signingKey "$OPENCLAW_INKBOX_SIGNING_KEY"
openclaw config set tools.allow '["inkbox"]' --strict-json
openclaw config set tools.profile full
Expand Down
27 changes: 10 additions & 17 deletions .github/workflows/live-channels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ concurrency:
jobs:
live:
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
max-parallel: 1 # legs share the AUT identity → must run one at a time
Expand All @@ -50,12 +51,6 @@ jobs:

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: a4dd76b534e33c0a8352148d8f0e9ab25199f05b
path: .ci/inkbox

- name: Set up env paths (gateway log)
run: echo "GATEWAY_LOG=$RUNNER_TEMP/gateway.log" >> "$GITHUB_ENV"

Expand All @@ -70,16 +65,12 @@ jobs:

- name: Install live-test deps (Inkbox SDK + pytest)
run: |
pip install \
"inkbox @ git+https://github.com/inkbox-ai/inkbox.git@a4dd76b534e33c0a8352148d8f0e9ab25199f05b#subdirectory=sdk/python" \
pytest
pip install 'inkbox==0.5.9' pytest

- name: Build the plugin
run: |
npm ci --prefix .ci/inkbox/sdk/typescript
npm run build --prefix .ci/inkbox/sdk/typescript
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@2026.5.27
npm ci
npm install --no-save --package-lock=false openclaw@2026.5.27
npm run build

- name: Install OpenClaw host
Expand Down Expand Up @@ -133,11 +124,13 @@ jobs:
echo "AUT handle: $HANDLE"
# Install the plugin BEFORE writing channels.inkbox.* — the channel id
# only validates once the plugin is registered.
openclaw plugins install -l "$GITHUB_WORKSPACE"
openclaw plugins install -l --dangerously-force-unsafe-install "$GITHUB_WORKSPACE"
openclaw plugins enable inkbox || true
openclaw config set channels.inkbox.enabled true --strict-json
openclaw config set channels.inkbox.apiKey "$OPENCLAW_INKBOX_API_KEY"
openclaw config set channels.inkbox.identity "$HANDLE"
openclaw config set channels.inkbox.voiceStack inkbox_tts_stt
openclaw config set channels.inkbox.voicemailDetection disabled
openclaw config set channels.inkbox.signingKey "$OPENCLAW_INKBOX_SIGNING_KEY"
openclaw config set tools.allow '["inkbox"]' --strict-json
# Onboarding defaults tools.profile to "coding", whose base allowlist
Expand All @@ -151,7 +144,7 @@ jobs:
nohup python3 "$GITHUB_WORKSPACE/tests/live/mock_openai.py" 8088 > "$RUNNER_TEMP/mock.log" 2>&1 &
echo $! > "$RUNNER_TEMP/mock.pid"
for i in $(seq 1 10); do
curl -sf http://127.0.0.1:8088/v1/models >/dev/null && { echo "mock model ready"; exit 0; }
curl -sf --connect-timeout 1 --max-time 3 http://127.0.0.1:8088/v1/models >/dev/null && { echo "mock model ready"; exit 0; }
sleep 1
done
echo "::error::mock model did not start"; cat "$RUNNER_TEMP/mock.log"; exit 1
Expand Down Expand Up @@ -192,7 +185,7 @@ jobs:
# Failure-only: these logs carry live phone/email/message content and this repo
# (and its Action logs/artifacts) is public.
- name: Dump logs (on failure only)
if: failure()
if: failure() || cancelled()
run: |
echo "=== gateway.log ==="; cat "$GATEWAY_LOG" || true
echo "=== mock model log ==="; cat "$RUNNER_TEMP/mock.log" 2>/dev/null || true
Expand All @@ -205,7 +198,7 @@ jobs:
kill "$(cat "$RUNNER_TEMP/mock.pid" 2>/dev/null)" 2>/dev/null || true

- name: Upload artifacts (on failure only)
if: failure()
if: failure() || cancelled()
uses: actions/upload-artifact@v7
with:
name: live-logs-${{ matrix.mode }}
Expand Down
20 changes: 6 additions & 14 deletions .github/workflows/live-external-events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ jobs:

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: a4dd76b534e33c0a8352148d8f0e9ab25199f05b
path: .ci/inkbox

- name: Set up env paths (gateway log)
run: echo "GATEWAY_LOG=$RUNNER_TEMP/gateway.log" >> "$GITHUB_ENV"

Expand All @@ -65,16 +59,12 @@ jobs:

- name: Install live-test deps (Inkbox SDK + pytest)
run: |
pip install \
"inkbox @ git+https://github.com/inkbox-ai/inkbox.git@a4dd76b534e33c0a8352148d8f0e9ab25199f05b#subdirectory=sdk/python" \
pytest
pip install 'inkbox==0.5.9' pytest

- name: Build the plugin
run: |
npm ci --prefix .ci/inkbox/sdk/typescript
npm run build --prefix .ci/inkbox/sdk/typescript
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@2026.5.27
npm ci
npm install --no-save --package-lock=false openclaw@2026.5.27
npm run build

- name: Install OpenClaw host
Expand Down Expand Up @@ -115,11 +105,13 @@ jobs:
echo "AUT handle: $HANDLE"
# Install the plugin BEFORE writing channels.inkbox.* — the channel id
# only validates once the plugin is registered.
openclaw plugins install -l "$GITHUB_WORKSPACE"
openclaw plugins install -l --dangerously-force-unsafe-install "$GITHUB_WORKSPACE"
openclaw plugins enable inkbox || true
openclaw config set channels.inkbox.enabled true --strict-json
openclaw config set channels.inkbox.apiKey "$OPENCLAW_INKBOX_API_KEY"
openclaw config set channels.inkbox.identity "$HANDLE"
openclaw config set channels.inkbox.voiceStack inkbox_tts_stt
openclaw config set channels.inkbox.voicemailDetection disabled
openclaw config set channels.inkbox.signingKey "$OPENCLAW_INKBOX_SIGNING_KEY"
openclaw config set tools.allow '["inkbox"]' --strict-json
# Onboarding defaults tools.profile to "coding", whose base allowlist
Expand Down
64 changes: 44 additions & 20 deletions .github/workflows/live-voice.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
name: Live — voice calls (Inkbox TTS/STT + realtime)
name: Live — voice calls (Inkbox TTS/STT + realtime + Voice AI)

# Boots the agent-under-test (AUT) gateway plus a driver process that bridges the
# other side of a real phone call over its own Inkbox tunnel. Two matrix legs:
# other side of a real phone call over its own Inkbox tunnel. Four matrix legs:
# inbound_inkbox — driver calls the agent; agent answers with Inkbox STT/TTS.
# outbound_realtime — driver texts "call me"; agent calls back via the realtime API.
# outbound_realtime_contact — realtime answers a seeded contact question with
# a direct Inkbox contact read during the call.
# outbound_hosted — driver requests a call; Inkbox Voice AI calls, records an
# SMS commitment, and OpenClaw settles it after hangup.
# Each leg verifies the stored call transcript shows the agent spoke to the caller.
# Real model + real calls, so this runs only on ready (non-draft) PRs + dispatch, and
# shares the AUT tunnel lock with the other live suites.
Expand Down Expand Up @@ -47,20 +51,15 @@ concurrency:
jobs:
voice:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
max-parallel: 1 # legs share the AUT identity → one at a time
matrix:
scenario: ${{ fromJSON(inputs.include_inbound && '["inbound_inkbox","outbound_realtime"]' || '["outbound_realtime"]') }}
scenario: ${{ fromJSON(inputs.include_inbound && '["inbound_inkbox","outbound_realtime","outbound_realtime_contact","outbound_hosted"]' || '["outbound_realtime","outbound_realtime_contact","outbound_hosted"]') }}

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: a4dd76b534e33c0a8352148d8f0e9ab25199f05b
path: .ci/inkbox

- name: Set up env paths
run: |
echo "GATEWAY_LOG=$RUNNER_TEMP/gateway.log" >> "$GITHUB_ENV"
Expand All @@ -80,16 +79,12 @@ jobs:
# uvicorn[standard] (not bare uvicorn): the driver accepts a WebSocket
# upgrade, which needs the websockets extra.
run: |
pip install \
"inkbox @ git+https://github.com/inkbox-ai/inkbox.git@a4dd76b534e33c0a8352148d8f0e9ab25199f05b#subdirectory=sdk/python" \
pytest fastapi 'uvicorn[standard]'
pip install 'inkbox==0.5.9' pytest fastapi 'uvicorn[standard]'

- name: Build the plugin
run: |
npm ci --prefix .ci/inkbox/sdk/typescript
npm run build --prefix .ci/inkbox/sdk/typescript
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@2026.5.27
npm ci
npm install --no-save --package-lock=false openclaw@2026.5.27
npm run build

- name: Install OpenClaw host
Expand Down Expand Up @@ -131,23 +126,32 @@ jobs:
echo "AUT handle: $HANDLE"
# Install the plugin BEFORE writing channels.inkbox.* — the channel id
# only validates once the plugin is registered.
openclaw plugins install -l "$GITHUB_WORKSPACE"
openclaw plugins install -l --dangerously-force-unsafe-install "$GITHUB_WORKSPACE"
openclaw plugins enable inkbox || true
# Hosted completion settlement uses before_agent_run to bind the
# exact generated run before accepting any SMS tool evidence.
openclaw config set plugins.entries.inkbox.hooks.allowConversationAccess true --strict-json
openclaw config set channels.inkbox.enabled true --strict-json
openclaw config set channels.inkbox.apiKey "$OPENCLAW_INKBOX_API_KEY"
openclaw config set channels.inkbox.identity "$HANDLE"
openclaw config set channels.inkbox.signingKey "$OPENCLAW_INKBOX_SIGNING_KEY"
openclaw config set channels.inkbox.voicemailDetection disabled
openclaw config set tools.allow '["inkbox"]' --strict-json
# Onboarding defaults tools.profile to "coding", whose base allowlist
# strips the inkbox messaging/contact/call tools. "full" = no restriction.
openclaw config set tools.profile full
if [ "${{ matrix.scenario }}" = "outbound_realtime" ]; then
if [[ "${{ matrix.scenario }}" == outbound_realtime* ]]; then
openclaw config set channels.inkbox.voiceStack openai_realtime
openclaw config set channels.inkbox.voiceRealtime.enabled true --strict-json
openclaw config set channels.inkbox.voiceRealtime.provider openai
openclaw config set channels.inkbox.voiceRealtime.model gpt-realtime-2
openclaw config set channels.inkbox.voiceRealtime.providers.openai.apiKey "$OPENAI_API_KEY"
elif [ "${{ matrix.scenario }}" = "outbound_hosted" ]; then
openclaw config set channels.inkbox.voiceStack inkbox_voice_ai
openclaw config set channels.inkbox.voiceRealtime.enabled false --strict-json
else
# Force the Inkbox STT/TTS path so this leg proves it end to end.
openclaw config set channels.inkbox.voiceStack inkbox_tts_stt
openclaw config set channels.inkbox.voiceRealtime.enabled false --strict-json
fi

Expand All @@ -170,6 +174,25 @@ jobs:
env:
REMOTE_INKBOX_API_KEY: ${{ secrets.REMOTE_INKBOX_API_KEY }}
run: |
if [ "${{ matrix.scenario }}" = "outbound_hosted" ]; then
# This value crosses TTS -> PSTN -> STT before Voice AI records the
# follow-up. Encode the run as five distinct everyday words;
# conventional phonetic-alphabet terms were normalized into
# alternate words or familiar phrases.
DIGITS="${GITHUB_RUN_ID: -4}${GITHUB_RUN_ATTEMPT: -1}"
HOSTED_MARKER="$(python3 "$GITHUB_WORKSPACE/tests/live/voice_marker.py" "$DIGITS")"
echo "HOSTED_POST_CALL_MARKER=$HOSTED_MARKER" >> "$GITHUB_ENV"
# State the action lifecycle and exact body separately. Hosted
# agents sometimes preserved the SMS intent while omitting the
# marker from the summarized post-call action when both were
# compressed into one clause.
export VOICE_DRIVER_LINE="After we hang up, send me one SMS. Create the post-call action now with this exact SMS body: $HOSTED_MARKER. Read those five words back to me after the action is saved. Do not send it during the call."
# The pytest gate, not the driver timer, owns the intentional hangup.
export VOICE_DRIVER_LISTEN=180
elif [ "${{ matrix.scenario }}" = "outbound_realtime_contact" ]; then
export VOICE_DRIVER_LINE="Hi! What email address do you have on file for your contact Olivia Parker? Please read the email out loud, then say goodbye."
export VOICE_DRIVER_LISTEN=40
fi
VOICE_DRIVER_STATE="$DRIVER_STATE" VOICE_DRIVER_PORT=8090 \
python3 "$GITHUB_WORKSPACE/tests/live/voice_driver.py" > "$DRIVER_LOG" 2>&1 &
echo $! > "$RUNNER_TEMP/driver.pid"
Expand All @@ -187,11 +210,12 @@ jobs:
LIVE_VOICE_TIMEOUT: ${{ inputs.timeout_s || '220' }}
run: |
LIVE_REAL_MODEL=1 VOICE_DRIVER_STATE="$DRIVER_STATE" \
python3 -m pytest tests/live/test_voice.py -v
python3 -m pytest \
tests/live/test_voice_helpers.py tests/live/test_voice.py -v

# Failure-only: these logs carry live call content and this repo is public.
- name: Dump logs (on failure only)
if: failure()
if: failure() || cancelled()
run: |
echo "=== gateway.log ==="; cat "$GATEWAY_LOG" || true
echo "=== driver.log ==="; cat "$DRIVER_LOG" || true
Expand Down
26 changes: 4 additions & 22 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,15 @@ jobs:
steps:
- uses: actions/checkout@v7

- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: a4dd76b534e33c0a8352148d8f0e9ab25199f05b
path: .ci/inkbox

- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: |
npm ci --prefix .ci/inkbox/sdk/typescript
npm run build --prefix .ci/inkbox/sdk/typescript
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@2026.5.27
npm ci
npm install --no-save --package-lock=false openclaw@2026.5.27

- name: Typecheck
run: npm run typecheck
Expand All @@ -57,25 +49,15 @@ jobs:
steps:
- uses: actions/checkout@v7

- uses: actions/checkout@v7
with:
repository: inkbox-ai/inkbox
ref: a4dd76b534e33c0a8352148d8f0e9ab25199f05b
path: .ci/inkbox

- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm

- name: Install deps + latest OpenClaw host
run: |
npm ci --prefix .ci/inkbox/sdk/typescript
npm run build --prefix .ci/inkbox/sdk/typescript
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@2026.5.27
npm install --no-save --package-lock=false \
./.ci/inkbox/sdk/typescript openclaw@latest
npm ci
npm install --no-save --package-lock=false openclaw@latest
npm ls openclaw || true

- name: Typecheck vs latest OpenClaw
Expand Down
Loading