Skip to content

feat: let signed-in visitors lend NPCs their own CORE integrations#72

Open
omjeem wants to merge 4 commits into
RedPlanetHQ:mainfrom
omjeem:feat/visitor-integration-grants
Open

feat: let signed-in visitors lend NPCs their own CORE integrations#72
omjeem wants to merge 4 commits into
RedPlanetHQ:mainfrom
omjeem:feat/visitor-integration-grants

Conversation

@omjeem

@omjeem omjeem commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Today an NPC can only use the integrations the town owner wired into its mdx (permissions.integrations), acting on the owner's connected accounts.

This PR lets a signed-in visitor lend an NPC their own CORE integrations for a conversation, so the NPC can act on the visitor's account (e.g. read the visitor's own Postman workspace) instead of the owner's — without the owner ever
seeing the visitor's credentials. The visitor stays in control: they grant from an in-chat popover and can revoke from the same place anytime.

How it works

  • Consent UI — a "Share access" popover in the chat header, shown only to signed-in visitors (hidden for the town owner, who grants via mdx, and for NPCs that declare no integrations). It lists the visitor's own connected CORE integrations that this NPC is configured to use. Toggle + Save grants; uncheck + Save revokes; re-opening the chat lets them change it anytime. When none of the NPC's integrations are connected, an empty state names what to connect in CORE.
  • Persistence — new VisitorIntegrationGrant table (npc × visitor × slug), soft-deleted via revokedAt so grant history stays auditable and re-granting is an upsert. Pure DB helpers live in visitor-grants.ts.
  • Token routingbuildNpcTools now builds up to two integration sources (owner + visitor) and routes each execute_integration_action to the correct token by integration_account_id (globally unique in CORE). One NPC can act
    on the owner's and the visitor's accounts in the same conversation. Memory, tasks and skills always stay on the owner token — only integrations route to the visitor.
  • IdentityresolveViewer now returns userId (null for anonymous guests). The owner is treated as a non-granter: visitor-access returns isOwner and the server rejects owner writes, so the feature is enforced at both the UI and API layers.
  • No CORE change — the visitor uses their own token on their own account (not cross-user delegation), so CORE's existing per-token scoping covers it.
  • Auditing — each integration call is tagged with a source string (town:npc:<id>:resident|visitor) so CORE's call log can attribute which NPC, and whose account, made the call.

Testing

  1. As the town owner, deploy a town with an NPC that declares integrations in its mdx. Confirm the owner does not see the "Share access" button.
  2. Sign in as a different user, visit via the share link, open that NPC's chat — the button appears.
  3. Connect an integration (e.g. Postman) in CORE, open "Share access", grant it.
  4. Ask the NPC to use it → it acts on your account. Uncheck + Save to revoke.
  5. As an anonymous guest, the button prompts CORE sign-in first.

Migration: pnpm db:migrate (or db:deploy) applies 20260720000000_visitor_integration_grant.

Limitations / scope (v1)

  1. Integration-level, owner-scoped menu. A visitor can only grant the integrations the NPC already declares in its mdx — not arbitrary tools from their account. This keeps the NPC's capability surface owner-defined; the popover answers "which of my accounts does this NPC want," not "give this NPC anything." (Straightforward to open up later if desired.)
  2. Level-1 grants only (whole integration). Granting an integration grants all of its actions. The table has a reserved actions[] column for a future per-tool (level-2) whitelist — it can land later without a migration.
  3. Grants reset on town deploy. Deploy recreates NPC rows (delete + recreate), which cascades the grants away. Acceptable for now; a later change can switch deploy to upsert-by-id to preserve them.
  4. Signed-in visitors only. The button appears only for a signed-in visitor. The town owner never sees it — they grant via mdx, and the server treats the owner as a non-granter (its writes are rejected, not just hidden). Anonymous guests are sent through CORE sign-in first, since they have no account to lend.
  5. Owner + visitor overlap. If both grant the same integration, both accounts are surfaced to the model (labeled resident/you) and it's told to prefer the visitor's own. This is a prompt-level preference; enforcement is the token routing.

Follow-ups (not in this PR)

  • The guest speaker block in the chat system prompt still frames every tool as returning the resident's context. With visitor grants that's no longer strictly true and can make some NPCs deflect visitor-owned tools — a prompt tweak to teach it about belongs_to: "you" accounts is a good next step.
  • Per-tool (level-2) grant UI, and deciding whose aura pays for visitor-token calls.
visitor-integration-grants.mp4

Closes #48.

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.

Allow users to connect external tools in CORE and grant tool access to Agents/NPCs

1 participant