From ce12d6eaf65b8a20caaf5c7e273e89fe7d05dcfe Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Wed, 15 Jul 2026 21:07:33 +0000 Subject: [PATCH 1/7] chore: require Inkbox SDK 0.5.0 --- inkbox_codex/doctor.py | 2 +- inkbox_codex/gateway.py | 2 +- inkbox_codex/setup_wizard.py | 4 ++-- pyproject.toml | 2 +- tests/test_setup_wizard.py | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/inkbox_codex/doctor.py b/inkbox_codex/doctor.py index aabe80a..df3f45e 100644 --- a/inkbox_codex/doctor.py +++ b/inkbox_codex/doctor.py @@ -33,7 +33,7 @@ def run_doctor() -> List[Tuple[str, bool, str]]: import inkbox # noqa: F401 checks.append(("inkbox SDK", True, "installed")) except ImportError: - checks.append(("inkbox SDK", False, "pip install 'inkbox>=0.4.15,<1.0.0'")) + checks.append(("inkbox SDK", False, "pip install 'inkbox>=0.5.0,<1.0.0'")) try: import aiohttp # noqa: F401 diff --git a/inkbox_codex/gateway.py b/inkbox_codex/gateway.py index b48fe4c..f32484a 100644 --- a/inkbox_codex/gateway.py +++ b/inkbox_codex/gateway.py @@ -509,7 +509,7 @@ async def run(self) -> None: if not AIOHTTP_AVAILABLE: raise RuntimeError("aiohttp is not installed; run: pip install aiohttp") if not INKBOX_AVAILABLE: - raise RuntimeError("inkbox SDK is not installed; run: pip install 'inkbox>=0.4.15,<1.0.0'") + raise RuntimeError("inkbox SDK is not installed; run: pip install 'inkbox>=0.5.0,<1.0.0'") if not self.cfg.api_key or not self.cfg.identity: raise RuntimeError("INKBOX_API_KEY and INKBOX_IDENTITY must be set (see README)") diff --git a/inkbox_codex/setup_wizard.py b/inkbox_codex/setup_wizard.py index 10f1587..6a98784 100644 --- a/inkbox_codex/setup_wizard.py +++ b/inkbox_codex/setup_wizard.py @@ -35,8 +35,8 @@ # Packages the wizard itself needs to talk to Inkbox during setup. The # gateway's Codex CLI dependency is checked by doctor. -INKBOX_REQUIREMENTS = ("inkbox>=0.4.20,<1.0.0", "aiohttp>=3.9") -MIN_INKBOX_VERSION = (0, 4, 20) +INKBOX_REQUIREMENTS = ("inkbox>=0.5.0,<1.0.0", "aiohttp>=3.9") +MIN_INKBOX_VERSION = (0, 5, 0) _BRACKETED_PASTE_PATTERN = re.compile(r"\x1b\[\s*200~|\x1b\[\s*201~") # Bundled avatar attached to the agent's Inkbox contact card during setup. diff --git a/pyproject.toml b/pyproject.toml index 41afc18..e9d5462 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Inkbox bridge for Codex — talk to your coding agent over email, requires-python = ">=3.10" dependencies = [ "aiohttp>=3.9", - "inkbox>=0.4.24,<1.0.0", + "inkbox>=0.5.0,<1.0.0", "segno>=1.5", # terminal QR codes for the iMessage connect step ] diff --git a/tests/test_setup_wizard.py b/tests/test_setup_wizard.py index ec4a104..3bd2386 100644 --- a/tests/test_setup_wizard.py +++ b/tests/test_setup_wizard.py @@ -88,7 +88,7 @@ def test_install_command_prefers_uv_when_available(monkeypatch): "install", "--python", "/tmp/venv/bin/python", - "inkbox>=0.4.20,<1.0.0", + "inkbox>=0.5.0,<1.0.0", "aiohttp>=3.9", ]] @@ -98,10 +98,10 @@ def test_install_command_falls_back_to_pip_and_ensurepip(monkeypatch): monkeypatch.setattr(setup_wizard.shutil, "which", lambda _name: None) assert setup_wizard._install_commands() == [ - [["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.20,<1.0.0", "aiohttp>=3.9"]], + [["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.5.0,<1.0.0", "aiohttp>=3.9"]], [ ["/tmp/venv/bin/python", "-m", "ensurepip", "--upgrade"], - ["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.4.20,<1.0.0", "aiohttp>=3.9"], + ["/tmp/venv/bin/python", "-m", "pip", "install", "inkbox>=0.5.0,<1.0.0", "aiohttp>=3.9"], ], ] @@ -120,7 +120,7 @@ def fail_import(): out = capsys.readouterr().out assert "/tmp/venv/bin/python" in out assert "uv pip install --python" in out - assert "inkbox>=0.4.20,<1.0.0" in out + assert "inkbox>=0.5.0,<1.0.0" in out # ---------------------------------------------------------------------- From 764bc5e4b347227c45279eb260d35858c70c30fd Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Sat, 18 Jul 2026 01:58:52 +0000 Subject: [PATCH 2/7] feat: align organization-wide contacts --- README.md | 4 +++- inkbox_codex/prompts.py | 7 ++++--- inkbox_codex/tools.py | 6 +++--- tests/test_prompts.py | 4 +++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4feda27..e11aaec 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,9 @@ The agent reaches you (or third parties) through an in-process MCP server: - `inkbox_list_text_conversations` · `inkbox_get_text_conversation` — browse SMS threads and history. - `inkbox_list_imessage_conversations` · `inkbox_get_imessage_conversation` — browse iMessage threads and history (find the `conversation_id` to send into). - `inkbox_lookup_contact` · `inkbox_list_contacts` · `inkbox_get_contact` — resolve and read address-book contacts (reverse-lookup by email/phone, free-text search, or full record by id). -- `inkbox_create_contact` · `inkbox_update_contact` · `inkbox_delete_contact` — save, edit, and remove contacts. Reads and writes are filtered server-side to what this identity may see. vCard export/import is not exposed. +- `inkbox_create_contact` · `inkbox_update_contact` · `inkbox_delete_contact` — save, edit, and remove organization-wide contacts. vCard export/import is not exposed. + +Contacts and generated contact facts are organization-wide. Contact `notes` remain user-managed profile text, separate from source-grounded generated facts. Correspondence still follows the configured identity's channel permissions. Unified correspondence and generated-fact tools are deferred until the installed SDK exposes those resources; this plugin does not use raw API calls as a substitute. On a live call, the OpenAI Realtime voice agent additionally gets `consult_agent`, `register_post_call_action` / `edit_post_call_action` / `delete_post_call_action`, and `hang_up_call` — see [Voice](#voice). diff --git a/inkbox_codex/prompts.py b/inkbox_codex/prompts.py index 18c265a..1999ed0 100644 --- a/inkbox_codex/prompts.py +++ b/inkbox_codex/prompts.py @@ -86,7 +86,7 @@ # Inkbox contacts -Codex can read and write Inkbox contacts visible to this configured identity. +Codex can read and write organization-wide Inkbox contacts. - Use inkbox_list_contacts for name-based searches like "who is Alex?". - Use inkbox_lookup_contact when you have an exact or partial email/phone filter. @@ -94,8 +94,9 @@ - Use inkbox_create_contact when the user asks you to save a new person or contact card. - Use inkbox_update_contact when the user asks you to change an existing contact; look up the contact first if you do not already have its UUID. - Use inkbox_delete_contact only after the target contact is explicit and confirmed. -- There is no vCard export/import, contact access, or contact rule tool in this harness. -- Contact tools operate only on contacts visible/writable to the configured identity. +- There is no vCard export/import or contact rule tool in this harness. Contacts do not have per-identity access grants. +- Contact notes are user-managed profile text. Generated contact facts are separate, source-grounded organization-wide memory; do not copy or overwrite generated facts through contact notes. +- Contact correspondence remains scoped to this identity's authorized channels. The installed SDK exposes neither unified correspondence nor generated-fact reads, so do not claim those tools exist or reconstruct them with raw requests. """.strip() diff --git a/inkbox_codex/tools.py b/inkbox_codex/tools.py index 8134f2f..ddab9b0 100644 --- a/inkbox_codex/tools.py +++ b/inkbox_codex/tools.py @@ -219,7 +219,7 @@ def _str_list(desc: str = "") -> JsonSchema: ), ToolSpec( "inkbox_list_contacts", - "Search the address book by free text.", + "Search the organization-wide address book by free text.", _schema({ "q": _str("Search query."), "order": _str("Sort order: recent or name."), @@ -240,7 +240,7 @@ def _str_list(desc: str = "") -> JsonSchema: "preferred_name": _str(), "company_name": _str(), "job_title": _str(), - "notes": _str(), + "notes": _str("User-managed contact notes, separate from generated contact facts."), "emails": _str_list(), "phones": _str_list(), }), @@ -255,7 +255,7 @@ def _str_list(desc: str = "") -> JsonSchema: "preferred_name": _str(), "company_name": _str(), "job_title": _str(), - "notes": _str(), + "notes": _str("User-managed contact notes, separate from generated contact facts."), "emails": _str_list(), "phones": _str_list(), }, ["contact_id"]), diff --git a/tests/test_prompts.py b/tests/test_prompts.py index ffb8fb0..f1b9b43 100644 --- a/tests/test_prompts.py +++ b/tests/test_prompts.py @@ -74,11 +74,13 @@ def test_channel_prompt_mentions_identity_and_dir(): assert "You MUST call inkbox_place_call in that same turn" in text assert "Do not merely text that" in text assert "replace the call with an SMS reply" in text - assert "Codex can read and write Inkbox contacts" in text + assert "organization-wide Inkbox contacts" in text assert "inkbox_create_contact" in text assert "inkbox_update_contact" in text assert "inkbox_delete_contact" in text assert "vCard export/import" in text + assert "Generated contact facts are separate" in text + assert "correspondence remains scoped to this identity" in text def test_strip_markdown(): From 00e0f6ded5729d0997107f1975442071c9c4a64a Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Sun, 19 Jul 2026 23:36:29 +0000 Subject: [PATCH 3/7] ci: test prerelease SDK source --- .github/workflows/tests.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 08ae8a7..6c3d7d5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,12 +51,19 @@ jobs: with: python-version: "3.12" + - uses: astral-sh/setup-uv@v8.1.0 + with: + version: "latest" + - uses: actions/setup-node@v7 with: node-version: "22" - name: Install bridge + test deps - run: pip install -e . pytest + run: | + uv pip install --system \ + "inkbox @ git+https://github.com/inkbox-ai/inkbox.git@b2bb11a45c9f4dc89f3bcfbadbd4de5d66d0e9e5#subdirectory=sdk/python" \ + -e . pytest # @alpha is the prerelease channel cut from codex main near-daily — the # freshest main build available without compiling the host from source. From ec33bde70a7c81f90343a9d68d7bf7c172365846 Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Mon, 20 Jul 2026 04:43:01 +0000 Subject: [PATCH 4/7] Keep contact access identity-scoped --- README.md | 4 +--- inkbox_codex/prompts.py | 7 +++---- inkbox_codex/tools.py | 6 +++--- tests/test_prompts.py | 4 +--- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7b582d9..ef39cbb 100644 --- a/README.md +++ b/README.md @@ -260,9 +260,7 @@ The agent reaches you (or third parties) through an in-process MCP server: - `inkbox_list_text_conversations` · `inkbox_get_text_conversation` — browse SMS threads and history. - `inkbox_list_imessage_conversations` · `inkbox_get_imessage_conversation` — browse iMessage threads and history (find the `conversation_id` to send into). - `inkbox_lookup_contact` · `inkbox_list_contacts` · `inkbox_get_contact` — resolve and read address-book contacts (reverse-lookup by email/phone, free-text search, or full record by id). -- `inkbox_create_contact` · `inkbox_update_contact` · `inkbox_delete_contact` — save, edit, and remove organization-wide contacts. vCard export/import is not exposed. - -Contacts and generated contact facts are organization-wide. Contact `notes` remain user-managed profile text, separate from source-grounded generated facts. Correspondence still follows the configured identity's channel permissions. Unified correspondence and generated-fact tools are deferred until the installed SDK exposes those resources; this plugin does not use raw API calls as a substitute. +- `inkbox_create_contact` · `inkbox_update_contact` · `inkbox_delete_contact` — save, edit, and remove contacts. Reads and writes are filtered server-side to what this identity may see. vCard export/import is not exposed. On a live call, the OpenAI Realtime voice agent additionally gets `consult_agent`, `register_post_call_action` / `edit_post_call_action` / `delete_post_call_action`, and `hang_up_call` — see [Voice](#voice). diff --git a/inkbox_codex/prompts.py b/inkbox_codex/prompts.py index 1999ed0..18c265a 100644 --- a/inkbox_codex/prompts.py +++ b/inkbox_codex/prompts.py @@ -86,7 +86,7 @@ # Inkbox contacts -Codex can read and write organization-wide Inkbox contacts. +Codex can read and write Inkbox contacts visible to this configured identity. - Use inkbox_list_contacts for name-based searches like "who is Alex?". - Use inkbox_lookup_contact when you have an exact or partial email/phone filter. @@ -94,9 +94,8 @@ - Use inkbox_create_contact when the user asks you to save a new person or contact card. - Use inkbox_update_contact when the user asks you to change an existing contact; look up the contact first if you do not already have its UUID. - Use inkbox_delete_contact only after the target contact is explicit and confirmed. -- There is no vCard export/import or contact rule tool in this harness. Contacts do not have per-identity access grants. -- Contact notes are user-managed profile text. Generated contact facts are separate, source-grounded organization-wide memory; do not copy or overwrite generated facts through contact notes. -- Contact correspondence remains scoped to this identity's authorized channels. The installed SDK exposes neither unified correspondence nor generated-fact reads, so do not claim those tools exist or reconstruct them with raw requests. +- There is no vCard export/import, contact access, or contact rule tool in this harness. +- Contact tools operate only on contacts visible/writable to the configured identity. """.strip() diff --git a/inkbox_codex/tools.py b/inkbox_codex/tools.py index ddab9b0..8134f2f 100644 --- a/inkbox_codex/tools.py +++ b/inkbox_codex/tools.py @@ -219,7 +219,7 @@ def _str_list(desc: str = "") -> JsonSchema: ), ToolSpec( "inkbox_list_contacts", - "Search the organization-wide address book by free text.", + "Search the address book by free text.", _schema({ "q": _str("Search query."), "order": _str("Sort order: recent or name."), @@ -240,7 +240,7 @@ def _str_list(desc: str = "") -> JsonSchema: "preferred_name": _str(), "company_name": _str(), "job_title": _str(), - "notes": _str("User-managed contact notes, separate from generated contact facts."), + "notes": _str(), "emails": _str_list(), "phones": _str_list(), }), @@ -255,7 +255,7 @@ def _str_list(desc: str = "") -> JsonSchema: "preferred_name": _str(), "company_name": _str(), "job_title": _str(), - "notes": _str("User-managed contact notes, separate from generated contact facts."), + "notes": _str(), "emails": _str_list(), "phones": _str_list(), }, ["contact_id"]), diff --git a/tests/test_prompts.py b/tests/test_prompts.py index f1b9b43..ffb8fb0 100644 --- a/tests/test_prompts.py +++ b/tests/test_prompts.py @@ -74,13 +74,11 @@ def test_channel_prompt_mentions_identity_and_dir(): assert "You MUST call inkbox_place_call in that same turn" in text assert "Do not merely text that" in text assert "replace the call with an SMS reply" in text - assert "organization-wide Inkbox contacts" in text + assert "Codex can read and write Inkbox contacts" in text assert "inkbox_create_contact" in text assert "inkbox_update_contact" in text assert "inkbox_delete_contact" in text assert "vCard export/import" in text - assert "Generated contact facts are separate" in text - assert "correspondence remains scoped to this identity" in text def test_strip_markdown(): From 56a689e5fc60ffa513382bdf5df0530a57769be1 Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Mon, 20 Jul 2026 04:44:19 +0000 Subject: [PATCH 5/7] Pin validation to updated SDK source --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6c3d7d5..3c3773b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: - name: Install bridge + test deps run: | uv pip install --system \ - "inkbox @ git+https://github.com/inkbox-ai/inkbox.git@b2bb11a45c9f4dc89f3bcfbadbd4de5d66d0e9e5#subdirectory=sdk/python" \ + "inkbox @ git+https://github.com/inkbox-ai/inkbox.git@bcff3526d700c2be6546e7ebdbff60073a6ec7e0#subdirectory=sdk/python" \ -e . pytest # @alpha is the prerelease channel cut from codex main near-daily — the From 3b0688aa2e6ef5bf2dfada4c7370681ad52b7b95 Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Mon, 20 Jul 2026 06:41:09 +0000 Subject: [PATCH 6/7] Pin tests to corrected SDK head --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3c3773b..7e59def 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,7 +62,7 @@ jobs: - name: Install bridge + test deps run: | uv pip install --system \ - "inkbox @ git+https://github.com/inkbox-ai/inkbox.git@bcff3526d700c2be6546e7ebdbff60073a6ec7e0#subdirectory=sdk/python" \ + "inkbox @ git+https://github.com/inkbox-ai/inkbox.git@8f4b37c56cd153a2acc6cfabe27dbbeb43510f00#subdirectory=sdk/python" \ -e . pytest # @alpha is the prerelease channel cut from codex main near-daily — the From 8411771757c1b565043723f3f6e33bd6b06585e9 Mon Sep 17 00:00:00 2001 From: alex-w-99 Date: Mon, 20 Jul 2026 07:10:48 +0000 Subject: [PATCH 7/7] Align SDK runtime requirements --- .github/workflows/tests.yml | 2 +- README.md | 6 +++--- install.sh | 12 ++++++------ pyproject.toml | 2 +- tests/live/test_sms.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7e59def..ba74cf0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.12"] + python-version: ["3.11", "3.12"] steps: - uses: actions/checkout@v7 diff --git a/README.md b/README.md index ef39cbb..e47b14c 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,13 @@ ## Prerequisites - **Codex installed and logged in.** The bridge drives a real Codex session, so the `codex` CLI has to be on the machine and authenticated — install it ([developers.openai.com/codex](https://developers.openai.com/codex)), then either sign in with a ChatGPT/Codex login or set `OPENAI_API_KEY`. `inkbox-codex doctor` checks for it. -- **Python 3.10+.** The installer finds one and builds the bridge its own venv. +- **Python 3.11+.** The installer finds one and builds the bridge its own venv. - **macOS or Linux.** Boot persistence uses a systemd user unit on Linux and a launchd agent on macOS. - **An Inkbox agent** — nothing to set up in advance; the setup wizard self-signs up for you (or takes an existing API key). ## Get started — one command -This finds a Python 3.10+, installs the bridge in its own venv, puts `inkbox-codex` on your PATH, and runs the setup wizard: +This finds a Python 3.11+, installs the bridge in its own venv, puts `inkbox-codex` on your PATH, and runs the setup wizard: ```bash curl -fsSL https://raw.githubusercontent.com/inkbox-ai/codex-plugin/main/install.sh | bash @@ -70,7 +70,7 @@ you (phone) ── SMS / iMessage / email / call ──▶ Inkbox ──▶ ## Manual install -If you'd rather not run the installer (any Python 3.10+ environment): +If you'd rather not run the installer (any Python 3.11+ environment): ```bash pip install -e . diff --git a/install.sh b/install.sh index 142c4f6..facc29d 100755 --- a/install.sh +++ b/install.sh @@ -8,7 +8,7 @@ # # ./install.sh # -# Finds a Python 3.10+, sets up an isolated venv, installs the bridge, puts +# Finds a Python 3.11+, sets up an isolated venv, installs the bridge, puts # `inkbox-codex` on your PATH, then runs the setup wizard. Re-runnable. # # Flags: @@ -54,22 +54,22 @@ ok() { echo " ${GREEN}✓${RESET} $*"; } warn() { echo " ${YELLOW}!${RESET} $*"; } die() { echo "${RED}✗ $*${RESET}" >&2; exit 1; } -# --- 1. find Python 3.10+ -------------------------------------------------- +# --- 1. find Python 3.11+ -------------------------------------------------- find_python() { local c v maj min - for c in python3.13 python3.12 python3.11 python3.10 python3 python; do + for c in python3.13 python3.12 python3.11 python3 python; do command -v "$c" >/dev/null 2>&1 || continue v="$("$c" -c 'import sys;print("%d.%d"%sys.version_info[:2])' 2>/dev/null || echo 0.0)" maj="${v%.*}"; min="${v#*.}" - if [ "$maj" = "3" ] && [ "$min" -ge 10 ] 2>/dev/null; then + if [ "$maj" = "3" ] && [ "$min" -ge 11 ] 2>/dev/null; then echo "$c"; return 0 fi done return 1 } -step "Looking for Python 3.10+" -PY="$(find_python)" || die "No Python 3.10+ found. Install python3.11+ and re-run." +step "Looking for Python 3.11+" +PY="$(find_python)" || die "No Python 3.11+ found. Install Python 3.11 or newer and re-run." ok "using $($PY --version 2>&1) at $(command -v "$PY")" # --- 2. get the source ----------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index e9d5462..853e7c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "codex-plugin" version = "0.1.4" description = "Inkbox bridge for Codex — talk to your coding agent over email, SMS, iMessage, and voice" -requires-python = ">=3.10" +requires-python = ">=3.11" dependencies = [ "aiohttp>=3.9", "inkbox>=0.5.0,<1.0.0", diff --git a/tests/live/test_sms.py b/tests/live/test_sms.py index fdf6edb..e5476f9 100644 --- a/tests/live/test_sms.py +++ b/tests/live/test_sms.py @@ -124,10 +124,10 @@ def _settle_inbound(sms) -> set: def _wait_new_inbound(sms, before: set, timeout_s: float, context: str) -> str: - """Poll for the first inbound not in ``before``; return its body lowercased.""" + """Poll for the earliest inbound not in ``before``; return its body lowercased.""" deadline = time.monotonic() + timeout_s while time.monotonic() < deadline: - for m in _inbound_from_aut(sms): + for m in reversed(_inbound_from_aut(sms)): if m.id not in before: body = getattr(m, "text", "") or "" bad = [x for x in ERROR_MARKERS if x in body.lower()]