feat(daemon): Slack thread-to-agent bidirectional linking (FRI-47)#13
Merged
feat(daemon): Slack thread-to-agent bidirectional linking (FRI-47)#13
Conversation
7a3b6d2 to
d7ef5ea
Compare
Adds the persistence layer for bidirectional Slack thread-to-agent linking. thread_connections uses agent_name as PK (one thread per agent) and thread_ts as UNIQUE (one agent per thread), enforcing the 0-or-1 constraint at the DB level. Also adds reaction helpers (addReaction/removeReaction) to slack/helpers, thread_ts support to slack_reply, and thread connection documentation to the orchestrator/builder/helper system prompts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
thread-registry.ts: in-memory maps + SQLite persistence via Drizzle query helpers, idle timer management, connect/disconnect/touchActivity, and startup recovery that prunes expired rows and restores live timers. lifecycle.ts: notifyThreadConnect/notifyThreadDisconnect send mail to the target agent with connection details and disconnect reason. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion thread-tools.ts: orchestrator-only MCP server with thread_connect and thread_disconnect tools that manage the registry, 🔗 reactions, Slack posts, and agent mail notifications. events.ts: detects thread_ts on incoming messages; if the thread is connected, forwards to the agent via mail and returns early (skips the orchestrator queue). worker.ts: self-constructs createSlackTools(new WebClient(SLACK_BOT_TOKEN)) in allMcpServers so all builders/helpers have slack_reply with thread_ts support without needing a live WebClient passed over IPC. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
index.ts: call initThreadRegistry after preflight with an onIdleDisconnect callback that posts a Slack notice and removes the 🔗 reaction. Injects friday-threads into the mail poller's orchestrator mcpServers. events.ts: adds friday-threads to the orchestrator's mcpServers in processQueue so thread_connect/thread_disconnect are available during interactive Slack sessions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…read-tools thread-registry.test.ts: covers connect/disconnect lifecycle, 0-or-1 constraint, stolen-connection path, touchActivity, idle timer firing, and initThreadRegistry startup recovery (prune expired, restore live). helpers.test.ts: adds addReaction/removeReaction tests covering success, permanent errors (message_not_found, already_reacted, no_reaction), and transient errors (ratelimited, request_timeout). thread-tools.test.ts: covers happy paths for thread_connect and thread_disconnect, plus agent-not-found, thread-owned, and stolen-connection error cases. Also fixes initThreadRegistry to clear in-memory maps before rebuilding from DB — ensures a clean slate on restart and makes tests deterministic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
architecture.md: - Add thread-registry.ts and thread-tools.ts to module table - Add 'Thread Message Path' subsection to Message Flow section - Add thread_connections to the Database Layer table - Update testing coverage table with new test files decisions.md: - Add ADR-023: thread_connections added to existing friday.db via Drizzle migration rather than a separate file. Documents the why (shared WAL + Drizzle pattern, DB-level uniqueness constraints) and startup recovery behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
d7ef5ea to
c6d03eb
Compare
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
Implements bidirectional Slack thread ↔ agent linking (FRI-47). Once connected, a user can converse directly with a running Builder or Helper in a Slack thread without routing every message through the Orchestrator.
What changed
New files:
packages/shared/src/db/threads.ts— Drizzle query helpers forthread_connectionspackages/shared/drizzle/0001_aromatic_shocker.sql— migration addingthread_connectionstableservices/friday/src/slack/thread-registry.ts— in-memory maps + SQLite persistence, idle timers, startup recoveryservices/friday/src/slack/thread-tools.ts— orchestrator-only MCP server:thread_connect/thread_disconnectservices/friday/src/slack/thread-registry.test.ts— 13 registry testsservices/friday/src/slack/thread-tools.test.ts— 6 tool handler testsModified files:
packages/shared/src/db/schema.ts— addsthreadConnectionstable definitionpackages/shared/src/db/index.ts— exports threads query helpersservices/friday/src/agent/tools.ts— adds optionalthread_tsparam toslack_replyservices/friday/src/agent/lifecycle.ts— addsnotifyThreadConnect/notifyThreadDisconnectservices/friday/src/agent/worker.ts— self-constructscreateSlackTools(new WebClient(SLACK_BOT_TOKEN))inallMcpServers(workers run in child_process.fork — live objects can't cross IPC)services/friday/src/agent/prime.ts— thread connection docs in orchestrator, builder, helper promptsservices/friday/src/slack/events.ts— thread message routing: ifthread_tsis connected, forward to agent via mail, skip orchestrator queueservices/friday/src/slack/helpers.ts— addsaddReaction/removeReactionwith permanent/transient error classification, 6 new testsservices/friday/src/index.ts— wiresinitThreadRegistry(withonIdleDisconnectcallback) andfriday-threadsMCP serverdocs/architecture.md— new modules, thread message flow path,thread_connectionstable, updated test coveragedocs/decisions.md— ADR-023: why thread_connections lives in existing friday.dbMessage flow examples
Initial connect: Orchestrator calls
thread_connect→:link:added → thread post "Connected tobuilder-foo" → agent receives mail with channel_id + thread_tsUser message + agent reply: User types in thread → events.ts detects thread_ts → mailSend
[thread] hello→ agent wakes → callsslack_replywith thread_ts → reply lands in threadIdle timeout (2h): Timer fires → thread post "Disconnected (idle timeout)." →
:link:removed → agent notifiedManual disconnect:
thread_disconnect("builder-foo")→ thread post "Disconnected." →:link:removed → agent notifiedStolen connection: New
thread_connectfor same agent → old thread gets "Disconnected — agent connected to new thread: " →:link:removed from old anchor → new connection establishedArchitecture decisions
thread_connectionsis a Drizzle migration on the existingfriday.db—agent_name PK+thread_ts UNIQUEenforce 0-or-1 at the DB level (see ADR-023 in decisions.md).worker.tsruns inchild_process.fork()— liveWebClientobjects cannot be passed over IPC. Each worker createsnew WebClient(process.env.SLACK_BOT_TOKEN).friday-threadsis injected only into the orchestrator's MCP server maps. Builders/helpers receive thread connections via mail and reply viaslack_reply.Test plan
pnpm test— 374 tests, 27 test files, all passpnpm --filter @friday/daemon exec tsc --noEmit— cleanpnpm --filter @friday/shared build— clean🤖 Generated with Claude Code