Skip to content

feat(agents): install-token + chat-claim primitives for WhatsApp deep-link install#361

Closed
buremba wants to merge 1 commit into
feat/install-identity-provisioningfrom
feat/gateway-instance-routing
Closed

feat(agents): install-token + chat-claim primitives for WhatsApp deep-link install#361
buremba wants to merge 1 commit into
feat/install-identity-provisioningfrom
feat/gateway-instance-routing

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented Apr 25, 2026

Summary

The backend half of the WhatsApp deep-link install flow. The landing page (next PR) will use the token endpoint to render a `https://wa.me/?text=install:` button; the gateway hot-path PR (follow-up) will use the lookup + claim helpers to actually receive and route the install message.

New: `install-token.ts`

  • `mintInstallToken(userId, templateAgentId, ttl=15min)` and `verifyInstallToken(message)`.
  • Format: `install:<base64url(payload)>.<base64url(hmac)>`. HMAC-SHA256 over a domain-separated key derived from `ENCRYPTION_KEY`.
  • Stateless — no DB row, single-instance compatible with K8s replicas.

New: `install-token-routes.ts`

  • `POST /api/install/token` (auth required) — signed-in user supplies `{templateAgentId}`, gets back `{token, expiresInSeconds}`. Landing page builds the wa.me link client-side.

New: `installed-agent-lookup.ts`

  • `findInstalledAgentByIdentity(platform, platformUserId, templateAgentId)` — single SQL hop from inbound JID to the user's installed agent instance, via `entity_identities` → `$member` → `organization` → `agents.template_agent_id`.
  • `claimInstallFromChat(message, platform, platformUserId)` — verifies the token, resolves the user's personal org, calls `installAgentFromTemplate`, and links the platform identity onto the `$member`.

Tests
7 unit tests for the token: mint/verify round-trip, HMAC tamper detection, expiry, malformed input, secret rotation.

Stacked on

`feat/install-identity-provisioning` (#359) — uses the `linkWhatsAppToMember` helper added there.

Out of scope (follow-up PR)

The gateway-side wiring in `message-handler-bridge.ts` that:

  1. Detects `install:` and calls `claimInstallFromChat`.
  2. For any other inbound message on a connection with `templateAgentId`, calls `findInstalledAgentByIdentity` and routes to the resolved agent. On miss, replies with the install URL.

That PR also adds a new optional `getInstalledAgentLookup()` method on `CoreServices` so owletto-backend can inject the implementation. Touches the hot path — deserves its own focused review.

Test plan

  • 7 unit tests pass.
  • Typecheck clean.
  • Manual: `POST /api/install/token -d '{"templateAgentId":"..."}'` while signed in returns a token; calling `verifyInstallToken` on it round-trips correctly.
  • Manual: feed the token to `claimInstallFromChat` with platform=whatsapp + a JID — confirm install runs and `entity_identities` rows for `wa_jid`/`phone` are written.

…deep-link flow

This is the backend half of the per-user-org WhatsApp install: a
short-lived HMAC-signed token, a way to mint it, and a way to claim it
when an inbound chat message arrives carrying the token.

New:

- install-token.ts: mintInstallToken(userId, templateAgentId, ttl) and
  verifyInstallToken(message). Format
  `install:<base64url(payload)>.<base64url(hmac)>`. HMAC-SHA256 over a
  domain-separated key derived from ENCRYPTION_KEY. 15-minute TTL.
  Stateless — no DB row.
- install-token-routes.ts: POST /api/install/token. Signed-in user
  posts {templateAgentId}, gets back {token, expiresInSeconds}. The
  landing page builds the wa.me/<bot-phone>?text=install:<token> link
  client-side.
- installed-agent-lookup.ts:
  - findInstalledAgentByIdentity(platform, platformUserId,
    templateAgentId) — single SQL hop from inbound JID/Slack-id to the
    agent instance in the user's personal org via entity_identities.
  - claimInstallFromChat(message, platform, platformUserId) — verifies
    an `install:<token>` message and runs the install end-to-end:
    resolves the user's personal org, calls installAgentFromTemplate,
    and links the platform identity (e.g. wa_jid + phone) onto the
    user's $member.

Tests:

- 7 unit tests for mint/verify round-trip, HMAC tamper detection,
  expiry, malformed input, secret rotation.

What's NOT in this PR:

- The gateway-side wiring that calls these helpers from
  message-handler-bridge. That's a separate PR — it touches the
  hot-path resolveAgentId / commandDispatcher pipeline and deserves
  focused review. The two helpers above are the building blocks; the
  gateway PR will add a CoreServices method that injects them and a
  branch in the message handler that:
    1. If text matches `install:<token>`, call claimInstallFromChat
       and reply with success / install URL.
    2. Otherwise, if connection has templateAgentId, call
       findInstalledAgentByIdentity. Hit → route to that agent.
       Miss → reply with the install URL.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@buremba
Copy link
Copy Markdown
Member Author

buremba commented Apr 25, 2026

Closing — overengineered. The HMAC install-token + wa.me deep-link flow adds ~400 LOC (token mint/verify, claim endpoint, chat-side prefix handler, signing secret config) for a marginal UX win (users not typing 11 digits). The simple phone-form approach already shipped in #359 is enough for v1, with out-of-band confirmation (bot greets new JID and lets the user reply NO if not them) covering the JID-stealing risk.

Replacing with a manifest endpoint + simpler landing page that uses the existing /api/install path. See the new install-manifest PR.

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