Files
kandev/apps/backend/internal/slack/poller.go
T
João Salavisa 7e0b46c227 feat(slack): cookie-mode integration that triages threads via a utility agent (#775)
* feat(slack): add cookie-mode integration that triages messages via utility agent

Adds an install-wide Slack integration that polls the connected user's
own messages for `!kandev <instruction>`, hands the thread context to a
configured utility agent with the Kandev MCP server attached, and
creates the resulting task. Auth uses the user's xoxc- token + d cookie
so it works on locked-down Enterprise Slack tenants where bot installs
aren't possible.

Shared infra:
- ACP host-utility executor accepts McpServers on session/new
- New MCP tool list_repositories_kandev so the triage agent can pick the
  right repo
- Template engine exposes Slack-specific variables for prompt injection
- secretadapter + healthpoll reused from jira/linear

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(slack): address PR review feedback round 9

- Drop dead `var _ = time.Second` no-op and its misleading comment in
  trigger_test.go.
- `RecordAuthHealth` now invalidates the cached client when the probe
  fails, so the poller self-recovers from credential rotation without
  requiring a re-save.
- Stable secondary sort key (`workspace_id DESC`) on the legacy →
  singleton migration so promoted-row selection is deterministic on
  `updated_at` ties.
- Extract `categorySlack` constant in `engine.go` and update the
  `VariableInfo.Category` doc-comment to list the full set of values.
- Realign `slack-integration/spec.md` with what shipped: cookie-mode +
  `!kandev` triage as v1, with bot install / User OAuth / reaction &
  slash & shortcut triggers moved under Future scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 09:03:42 +01:00

53 lines
1.3 KiB
Go

package slack
import (
"context"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/integrations/healthpoll"
)
// Poller probes the stored Slack credentials on the shared 90s auth-health
// cadence, mirroring Linear/Jira. The actual loop lives in
// internal/integrations/healthpoll; this wrapper exists so callers can write
// the familiar `slack.NewPoller(...).Start(ctx)` shape.
type Poller struct {
inner *healthpoll.Poller
}
// NewPoller returns a poller using the default 90s cadence. Returns nil when
// svc is nil so the caller's nil-check still works.
func NewPoller(svc *Service, log *logger.Logger) *Poller {
if svc == nil {
return nil
}
return &Poller{inner: healthpoll.New("slack", svcProber{svc}, log)}
}
// Start launches the background loop.
func (p *Poller) Start(ctx context.Context) {
if p == nil || p.inner == nil {
return
}
p.inner.Start(ctx)
}
// Stop cancels the loop and waits for it to drain.
func (p *Poller) Stop() {
if p == nil || p.inner == nil {
return
}
p.inner.Stop()
}
// svcProber adapts *Service to healthpoll.Prober.
type svcProber struct{ svc *Service }
func (s svcProber) HasConfig(ctx context.Context) (bool, error) {
return s.svc.Store().HasConfig(ctx)
}
func (s svcProber) RecordAuthHealth(ctx context.Context) {
s.svc.RecordAuthHealth(ctx)
}