feat: subscribe to existing Slack channels (one session per thread)#16
Open
spacedragon wants to merge 24 commits into
Open
feat: subscribe to existing Slack channels (one session per thread)#16spacedragon wants to merge 24 commits into
spacedragon wants to merge 24 commits into
Conversation
Spec for subscribing the Slack adapter to pre-existing channels with a one-session-per-thread model and built-in human-in-the-loop, implemented entirely within the adapter (no OpenACP core changes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anup per thread Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- resolveThreadSession no longer passes userId into the agentName slot (broke spawn) - detect resumable threads via persisted getRecordByThread, not live-only getSessionByThread - thread /command responses in subscription threads Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenACP removed `InstallContext.legacyConfig` in the 2026-04-05 config-legacy-removal. The install() hook still destructured and read it, so the plugin failed to build against current @openacp/cli (>=2026.415) with TS2339: Property 'legacyConfig' does not exist on type 'InstallContext'. The legacy-config migration path is dead code now (OpenACP no longer surfaces legacy config to plugins), so remove it. Fresh installs go straight to the interactive setup wizard, unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
setupSlack() always ran the interactive clack wizard, which cancels on non-TTY stdin (CI, scripted `npm run setup`, automation). When SLACK_BOT_TOKEN, SLACK_APP_TOKEN and SLACK_SIGNING_SECRET are all present in the environment, persist them via settings.setAll() and return early, skipping the wizard. Optional SLACK_ALLOWED_USER_IDS, SLACK_CHANNEL_PREFIX and SLACK_NOTIFICATION_CHANNEL_ID are honored too. The interactive flow is unchanged when the env vars are absent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `mentionAnyChannel` setting. When true, classifySubscription synthesizes
a mention-only subscription for any channel not explicitly listed in
subscribedChannels, so the bot responds to @mentions in every channel it has
been invited to without requiring a channel ID. Explicit subscribedChannels
entries still take precedence (e.g. for trigger:"all").
- types.ts: add mentionAnyChannel boolean (default false) to the settings schema
- subscription-router.ts: SubscriptionContext.mentionAnyChannel (optional);
synthesize {channelId, trigger:"mention"} when set and channel not listed
- event-router.ts: pass config.mentionAnyChannel into the SubscriptionContext
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change DM behavior to match subscribed channels: a top-level DM is now treated as a trigger:"all" subscription, so each top-level DM starts its own threaded session and the bot replies in that thread (instead of one persistent session replying inline at the DM top level). - classifySubscription: DM channels (D…) resolve to trigger:"all", gated by respondToDms; thread replies continue a known session. - Remove the bespoke DM path: resolveDmSession, handleDmSession, onDmSession callback, _dmResolveQueue, and the legacy DM block. - renameSessionThread: skip thread-bound sessions — the channel is shared and Slack rejects conversations.rename on a DM (not_authorized). Fixes a spurious warning observed in the live test. Build clean; 149 passed / 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets the Slack adapter subscribe to pre-existing Slack channels and run an AI agent against their messages — entirely within the adapter, no OpenACP core changes.
Model: one session per thread.
@mention(or, per-channel config, any top-level message) starts a new session bound to that message's thread; the agent works and replies inside that thread.--resume, full context) — including after a restart.Config (backward-compatible, defaults to
[]):trigger:"mention"(default) or"all". Invite the bot to each channel.How it works
src/subscription-router.ts:classifySubscription(ignore / start / continue) +resolveThreadSession(binds a thread to a session, reusing the existinghandleNewSession({ createThread: false })primitive — no new Slack channel is created).event-router.tsruns the classifier before the unchanged legacy routing (owned-channel / DM / notification paths are byte-for-byte preserved).thread_tswhen the session is subscription-bound; notification-channel posts are not threaded.Safety
deleteSessionThread+/openacp-archiveguard onmeta.threadTs).getRecordByThread), not just live sessions.Test Plan
npm test— 139 passed, 1 skipped (new:subscription-router.test.ts13 tests, plus event-router / text-buffer / activity-tracker / permission-handler additions).@OpenACP ...at top level → a thread opens and the agent replies in-thread.Notes
docs/superpowers/.error/cancelledstate can dead-end a subscription thread (resume returns the "use /new" message, which isn't the subscription entry point). Pre-existing resume-design behavior, surfaced here.🤖 Generated with Claude Code