Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ INKBOX_SIGNING_KEY=whsec_xxxxxxxxxxxx
# INKBOX_TUNNEL_NAME=my-agent-handle
# INKBOX_ALLOW_ALL_USERS=true # rely on Inkbox contact rules
# INKBOX_ALLOWED_USERS=+15551234567,me@example.com # optional local allowlist
# INKBOX_COMPANION_MAX_BYTES=200000 # complete initialization input limit
# INKBOX_REQUIRE_SIGNATURE=true
# INKBOX_EXTERNAL_EVENTS_ENABLED=false # wake the agent on unrecognised/unverified external webhooks
# INKBOX_CONTACT_MEMORIES_ENABLED=true # include matched-contact memories in human turns
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ jobs:
with:
node-version: 22

- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"

- name: Build pinned Inkbox SDK wheel
run: tests/ci/retry_install.sh bash tests/ci/build_inkbox_sdk.sh

- name: Install bridge + latest SDK
run: |
tests/ci/retry_install.sh pip install \
"inkbox==0.5.9" \
tests/ci/retry_install.sh uv pip install --system --no-sources \
"$INKBOX_SDK_WHEEL" \
-e . pytest
tests/ci/retry_install.sh pip install -U claude-agent-sdk
tests/ci/retry_install.sh uv pip install --system -U claude-agent-sdk

- name: Install latest Claude Code CLI
run: tests/ci/retry_install.sh npm install -g @anthropic-ai/claude-code
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/live-a2a.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,15 @@ jobs:
with:
node-version: 22

- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"

- name: Build pinned Inkbox SDK wheel
run: tests/ci/retry_install.sh bash tests/ci/build_inkbox_sdk.sh

- name: Install bridge and protocol driver
run: tests/ci/retry_install.sh pip install -e .
run: tests/ci/retry_install.sh uv pip install --system --no-sources "$INKBOX_SDK_WHEEL" -e .

- name: Install Claude Code CLI
run: tests/ci/retry_install.sh npm install -g @anthropic-ai/claude-code
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/live-channels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,17 @@ jobs:
with:
node-version: 22

- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"

- name: Build pinned Inkbox SDK wheel
run: tests/ci/retry_install.sh bash tests/ci/build_inkbox_sdk.sh

- name: Install bridge
run: |
tests/ci/retry_install.sh pip install \
"inkbox==0.5.9" \
tests/ci/retry_install.sh uv pip install --system --no-sources \
"$INKBOX_SDK_WHEEL" \
-e . pytest

- name: Install Claude Code CLI
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/live-external-events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,17 @@ jobs:
with:
node-version: 22

- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"

- name: Build pinned Inkbox SDK wheel
run: tests/ci/retry_install.sh bash tests/ci/build_inkbox_sdk.sh

- name: Install bridge
run: |
tests/ci/retry_install.sh pip install \
"inkbox==0.5.9" \
tests/ci/retry_install.sh uv pip install --system --no-sources \
"$INKBOX_SDK_WHEEL" \
-e . pytest

- name: Install Claude Code CLI
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/live-voice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,19 @@ jobs:
with:
node-version: 22

- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"

- name: Build pinned Inkbox SDK wheel
run: tests/ci/retry_install.sh bash tests/ci/build_inkbox_sdk.sh

# uvicorn[standard] matters: the bare install can't accept WebSocket
# upgrades, and the driver's call-media endpoint is a WebSocket.
- name: Install bridge + driver deps
run: |
tests/ci/retry_install.sh pip install \
"inkbox==0.5.9" \
tests/ci/retry_install.sh uv pip install --system --no-sources \
"$INKBOX_SDK_WHEEL" \
-e . pytest fastapi 'uvicorn[standard]'

- name: Install Claude Code CLI
Expand Down
32 changes: 27 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ on:
branches: [main]
pull_request:

env:
INKBOX_SDK_REV: 449966c885208d41f995d09c54072e012df9eb1a

jobs:
# Offline unit suite — inkbox is mocked in the tests, so install only what
# they import. Runs on every push/PR, drafts included.
# Offline tests exercise SDK pagination with a mocked transport.
unit:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
Expand All @@ -22,8 +24,20 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- uses: astral-sh/setup-uv@v8.1.0
with:
version: "latest"

- name: Build pinned Inkbox SDK wheel
run: |
git init "$RUNNER_TEMP/inkbox-sdk"
git -C "$RUNNER_TEMP/inkbox-sdk" fetch --depth=1 https://github.com/inkbox-ai/inkbox "$INKBOX_SDK_REV"
git -C "$RUNNER_TEMP/inkbox-sdk" checkout --detach FETCH_HEAD
test "$(git -C "$RUNNER_TEMP/inkbox-sdk" rev-parse HEAD)" = "$INKBOX_SDK_REV"
uv build --wheel --out-dir "$RUNNER_TEMP/inkbox-sdk-wheel" "$RUNNER_TEMP/inkbox-sdk/sdk/python"

- name: Install test deps
run: tests/ci/retry_install.sh pip install pytest httpx aiohttp segno "audioop-lts>=0.2.1; python_version >= '3.13'" claude-agent-sdk
run: tests/ci/retry_install.sh uv pip install --system pytest httpx aiohttp segno "audioop-lts>=0.2.1; python_version >= '3.13'" claude-agent-sdk "$RUNNER_TEMP/inkbox-sdk-wheel/inkbox-0.7.3-py3-none-any.whl"

# tests/contract runs in its own job against the LATEST host, not here.
# tests/live is collected but self-skips without the live API keys.
Expand Down Expand Up @@ -52,10 +66,18 @@ jobs:
with:
node-version: 22

- name: Build pinned Inkbox SDK wheel
run: |
git init "$RUNNER_TEMP/inkbox-sdk"
git -C "$RUNNER_TEMP/inkbox-sdk" fetch --depth=1 https://github.com/inkbox-ai/inkbox "$INKBOX_SDK_REV"
git -C "$RUNNER_TEMP/inkbox-sdk" checkout --detach FETCH_HEAD
test "$(git -C "$RUNNER_TEMP/inkbox-sdk" rev-parse HEAD)" = "$INKBOX_SDK_REV"
uv build --wheel --out-dir "$RUNNER_TEMP/inkbox-sdk-wheel" "$RUNNER_TEMP/inkbox-sdk/sdk/python"

- name: Install bridge + latest SDK
run: |
tests/ci/retry_install.sh uv pip install --system \
"inkbox==0.5.9" \
tests/ci/retry_install.sh uv pip install --system --no-sources \
"$RUNNER_TEMP/inkbox-sdk-wheel/inkbox-0.7.3-py3-none-any.whl" \
-e . pytest
tests/ci/retry_install.sh uv pip install --system -U claude-agent-sdk

Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Changelog

## 0.2.13

### Added

- Companion mode loads complete authorized group history into one Claude input,
with isolated conversations, durable recovery, and ordered live messages.
- Group replies retain the email reply-all parent or messaging conversation.
Automatic replies and `inkbox_reply_companion` revalidate access before sending
to the current turn's fixed group target.
- Configurable initialization byte limits, visible failure states, and paused
recovery when Claude's submission outcome is uncertain.
- Exclusive identity ownership, automatic retries before submission, and content
cleanup when Companion access is revoked.

### Changed

- Requires Inkbox SDK `>=0.7.3,<1.0.0`.
- Companion history stays out of local command and approval parsing. Only the
verified sponsor's live replies can answer Companion permission requests.
2 changes: 1 addition & 1 deletion Dockerfile.manual-test
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ RUN apt-get update \
WORKDIR /src/claude-code-plugin
COPY . .
RUN /opt/inkbox-claude/bin/pip install --no-cache-dir \
"inkbox==0.5.9" \
"inkbox==0.7.3" \
&& /opt/inkbox-claude/bin/pip install --no-cache-dir .

ENV PATH="/opt/inkbox-claude/bin:${PATH}" \
Expand Down
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,56 @@ Beyond Inkbox's own events, the `/webhook` endpoint can wake the agent for event
- **iMessage** — `inkbox_send_imessage(..., media_path=...)` (uploaded + sent, ≤10 MB).
- **SMS/MMS** — `inkbox_send_sms(..., media_paths=[...])` (uploaded + sent; `media_urls` also accepts already-hosted URLs).

## Companion mode

Version 0.2.13 supports Companion mode with Inkbox SDK 0.7.3 or newer. It stays
off until an administrator enables it for the identity and selects a sponsor.
Use an agent-scoped API key. A sponsor's qualifying group message initializes
one separate Claude conversation with all available authorized history and the
trigger in one input. Later messages wait for that initialization to finish.
Email recipient branches and new activations have separate conversations;
Companion history is excluded from contact-memory injection.

Automatic replies and `inkbox_reply_companion` revalidate current access and local
sponsor permission before sending to the fixed email reply-all parent or
MMS/iMessage conversation. Contact blocks and existing send requirements still
apply. Group MMS uses one conversation for the same participant set. Group
iMessage requires a supported dedicated line. Companion mode does not grant
permission to send a separate direct message or place a call.

`INKBOX_COMPANION_MAX_BYTES` defaults to `200000`. It bounds snapshot loading and
the assembled UTF-8 input. If history exceeds the limit, initialization fails
explicitly before a Claude query. Nothing is truncated or split into extra
turns. Choose a limit that fits your model's context, including its system prompt
and tools; bytes are not a token estimate. Attachment references and history-gap
notices remain in the input.

Companion state is stored under `~/.inkbox-claude/companion/`, or the corresponding
directory under `INKBOX_CLAUDE_HOME`. Keep this state when upgrading. Pending
hydration and queued messages recover on restart. `/health` exposes counts by
Companion state; each checkpoint records a logical submission ID and any error.
Only one bridge process can own an identity's Companion state in that directory.
Temporary pre-submission failures retry automatically with delays capped at 30
seconds. Revoked access discards captured message content while preserving
deduplication records. Stop the bridge before moving its state directory.
Delivery failures for tracked conversations are logged and saved as
`last_delivery_failure` in their checkpoints for operator review. They do not
start an automatic retry turn or a private contact conversation.

If a query's acceptance or completion is uncertain, the conversation is marked
`paused`. Restarting does not resend it. Stop the bridge and inspect the Claude
transcript using the checkpoint's logical input ID before reconciling the state.
Keep unresolved outcomes paused; deleting checkpoints or resetting them to pending
can repeat actions. An oversized or unavailable activation is marked `failed`;
after correcting a pre-submission failure, an operator may reset its state to
`pending` while the bridge is stopped, then restart to revalidate it.

Historical commands and approval-like text remain conversation data. Only a new,
verified live message from the sponsor can answer a pending permission request
or question. The sponsor must also match an optional local sender allowlist.
Tracked ordinary messages use their own group conversation; their pending
approvals can only be answered by that turn's normally allowed sender.

## Config reference

| Env var | Required | Default | Description |
Expand All @@ -249,6 +299,7 @@ Beyond Inkbox's own events, the `/webhook` endpoint can wake the agent for event
| `INKBOX_SKIP_WEBHOOK_RECONCILE` | no | `false` | Leave webhook subscriptions untouched on start. For deployments that provision them ahead of time, where the destination is fixed or this API key may not change it. They must already point at this bridge's webhook URL, or nothing arrives. |
| `INKBOX_EXTERNAL_EVENTS_ENABLED` | no | `false` | Wake the agent on unrecognised/unverified external webhooks (see [External webhooks](#external-webhooks)). |
| `INKBOX_CONTACT_MEMORIES_ENABLED` | no | `true` | Include matched-contact memories as background context for human conversations and calls. |
| `INKBOX_COMPANION_MAX_BYTES` | no | `200000` | Maximum bytes for Companion snapshot loading and one assembled input. |
| `INKBOX_A2A_PROGRESS_INTERVAL_SECONDS` | no | `180` | Seconds between short progress updates for active inbound A2A tasks; `0` disables periodic updates. |
| `INKBOX_WEBHOOK_SECRET_<NAME>` | per source | - | Verification secret for a registered third-party webhook source (e.g. `INKBOX_WEBHOOK_SECRET_GITHUB`). |
| `INKBOX_BASE_URL` | no | SDK default | Override the Inkbox API base URL. |
Expand All @@ -274,6 +325,7 @@ The agent reaches you (or third parties) through an in-process MCP server:

- `inkbox_whoami` — its own identity: handle, mailbox, and its two calling lines (dedicated phone number + shared iMessage line status).
- `inkbox_send_email` — send email; attach local files with `attachment_paths`.
- `inkbox_reply_companion` replies to the current Companion group with its fixed reply target.
- `inkbox_send_sms` — send SMS/MMS; attach local files with `media_paths` (or hosted `media_urls`).
- `inkbox_send_imessage` — send into an iMessage conversation; attach a local file with `media_path`.
- `inkbox_place_call` — place an outbound voice call through the running gateway with purpose/opening/context, over either line via `origination` (see [Two calling lines](#two-calling-lines)).
Expand All @@ -286,7 +338,7 @@ The agent reaches you (or third parties) through an in-process MCP server:
- `inkbox_list_a2a_tasks` · `inkbox_list_a2a_messages` — page and search this identity's inbound and outbound A2A history, with participant, task, context, role, state, and timestamp filters.
- `inkbox_a2a_complete` · `inkbox_a2a_ask_caller` · `inkbox_a2a_fail` — commit the outcome of a verified inbound A2A task. These tools are rejected outside that task's isolated session.

The bridge requires Inkbox SDK 0.5.9 or newer.
The bridge requires Inkbox SDK 0.7.3 or newer, below 1.0.0.

### Phone call voice stack

Expand Down Expand Up @@ -343,6 +395,12 @@ On a live call, the OpenAI Realtime voice agent additionally gets `consult_agent

## Development

Development installs and PR checks use SDK 0.7.3 from the public
[`inkbox` source at `449966c885208d41f995d09c54072e012df9eb1a`](https://github.com/inkbox-ai/inkbox/tree/449966c885208d41f995d09c54072e012df9eb1a/sdk/python).
The uv source override and lockfile pin that revision; CI builds its wheel before
installing it alongside the bridge. This validates a source build, not a registry
release. The package requirement remains `inkbox>=0.7.3,<1.0.0`.

```bash
python -m pytest
```
Expand Down
2 changes: 1 addition & 1 deletion inkbox_claude/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Inkbox bridge for Claude Code — email, SMS, iMessage, and voice."""

__version__ = "0.2.10"
__version__ = "0.2.13"
Loading
Loading