7e0b46c227
* 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>
53 lines
1.3 KiB
Go
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)
|
|
}
|