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:
- Session reuse —
forward.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.
- Ownership / authz —
ownership.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)
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.
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-overridableOPENCODE_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:
forward.ts:resolveSessionTargetmaps${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 thex-openpalm-session-keyheader to get this; a plain OpenCode server ignores the header andsession.create()makes a fresh session every turn → multi-turn breaks.ownership.tsmapssessionId → principal(+requestID → principalfor 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:x-openpalm-session-keybecomes an optional guardian hint (for the guardian's durable, cross-restart, cross-principal reuse) rather than the mechanism multi-turn depends on — a cleaner "standard OpenCode client" contract.Coordination checklist
x-openpalm-session-keyas an optional optimization.contextId → sessionIdmapping is the same shape as portal session reuse; align the contract so all three front ends (portal/OpenCode-proxy/A2A) treat session continuity consistently.Standalone packaging gaps (from the investigation)
readRequiredSecret()accepting a direct env var (e.g.OPENCODE_PASSWORD,DISCORD_BOT_TOKEN) with_FILEfallback (keeps Compose-secrets working). Against plain OpenCode the single Basic password is the auth;PRINCIPAL_IDbecomes a free-form username.SLACK_BOT_NAME).bin/wrappers +binfield (the container entrypoint instantiates the class today).@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)."private": true→ false on both; addpublish-npm-packagesteps; reconcilerelease-package-groups.json(units.portals). Decide the Bun-runtime requirement (engines.bun) — it narrows the Node audience; consider a Node-compatible build target.portals/*/README.mdfor 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-kitextraction 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.