Skip to content

Standalone OpenCode-compatible portal packages (coordinate with guardian state refactor #433) #491

Description

@itlackey

Goal

Publish the portal adapters (@openpalm/discord-portal, @openpalm/slack-portal) as standalone npm packages that work against any OpenCode server, not just the OpenPalm guardian. This both serves a "power user / your own OpenCode server" use case and completes the #434 / #432 thesis that portal adapters are standard OpenCode-SDK clients (the guardian's value is validation, not transport).

Spun out of the 0.12.0 portals-rename + build/packaging review. Code is already ~90% there: zero @openpalm/* workspace deps (only @opencode-ai/sdk + discord.js/@slack/bolt), env-overridable OPENCODE_BASE_URL, no template rendering.

The architectural crux — and why this coordinates with the guardian state refactor (#433)

The guardian has two distinct state concerns today; the portals only need a client-side mirror of the first:

  1. Session reuseforward.ts:resolveSessionTarget maps ${portalId}:${sessionKey} → an existing OpenCode session (the buffered session cache, GUARDIAN_SESSION_TTL_MS). This is what makes multi-turn work. Portals currently rely on the guardian honoring the x-openpalm-session-key header to get this; a plain OpenCode server ignores the header and session.create() makes a fresh session every turn → multi-turn breaks.
  2. Ownership / authzownership.ts maps sessionId → principal (+ requestID → principal for permissions) for per-principal isolation. Pure guardian concern; no standalone equivalent.

Proposed direction: move session-reuse client-side into the portal (a Map<threadKey, sessionId>, optionally locally persisted). Then:

Coordination checklist

Standalone packaging gaps (from the investigation)

  • Session reuse client-side (the one architectural gap above) — ~20 lines/portal, or in a shared kit.
  • Auth UXreadRequiredSecret() accepting a direct env var (e.g. OPENCODE_PASSWORD, DISCORD_BOT_TOKEN) with _FILE fallback (keeps Compose-secrets working). Against plain OpenCode the single Basic password is the auth; PRINCIPAL_ID becomes a free-form username.
  • Branding — Slack hardcodes "Ask OpenPalm" / "OpenPalm on Slack" (modal titles, Home tab) → make configurable (SLACK_BOT_NAME).
  • CLI entrypointsbin/ wrappers + bin field (the container entrypoint instantiates the class today).
  • Extract @openpalm/portal-kit (M, follow-on) — runtime.ts / oc-events.ts / OcClient / session-map are byte-identical across discord+slack; extract to one shared package (also the home for the client-side session-reuse logic).
  • Publish — flip "private": true → false on both; add publish-npm-package steps; reconcile release-package-groups.json (units.portals). Decide the Bun-runtime requirement (engines.bun) — it narrows the Node audience; consider a Node-compatible build target.
  • Docs — rewrite both portals/*/README.md for a standalone audience.

Security framing (must be explicit in docs)

Pointing a portal straight at OpenCode loses the guardian's per-principal isolation, rate limiting, and content moderation — every user in a server shares one session namespace. Frame as: "Runs best behind the OpenPalm guardian (or another auth/rate-limiting reverse proxy). Standalone is for personal / small-trusted-team use against your own OpenCode server."

Effort

First shippable increment: S (~half a day) — session-reuse + auth UX + branding + bin/ + private:false + publish steps + READMEs. The @openpalm/portal-kit extraction is a separate M. Recommend doing the session-reuse / state split with #433 first so the guardian and portal don't diverge.

Related: #433, #487, #434, #432, #436.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions