Skip to content

Commit 7f1bb69

Browse files
kapaleshreyasclaude
andcommitted
feat(slack-bot): Slack bot service — @bot mentions drive ComputerAgent
New file examples/slack-bot.ts. Two bots in one service: POST /slack/claudebot/events → claude-agent-sdk + (optional) Lyzr proxy POST /slack/gitagent/events → gitagent direct (Lyzr via GITCLAW_MODEL_BASE_URL) Maps Slack threads to warm /sandboxes instances: - First @bot mention in a thread creates a sandbox with autoSave to S3 + sessionStore: mongo (cross-process conversation memory) - Replies in the same thread reuse the warm sandbox via /sandboxes/:id/chat - Sandbox idle TTL (30min) fires → autoSave snapshots to S3 → next mention in the thread restores from snapshot transparently - Mongo collection slack_threads tracks <bot>:<channel>:<thread_ts> → {sandboxId, snapshotId, sessionId} Live progress UX: Bot posts "🤔 Working…" immediately, then edits the same message as the agent works: 🔧 Running `Write`... (3 tools so far) → final answer Edits debounced to 800ms (Slack rate-limits chat.update at ~1/sec). Slack signature verification: HMAC-SHA256(v0:<ts>:<body>) compared timing- safe to X-Slack-Signature. 5-min replay window. Wrong sig → 401. Mounted into the main ComputerAgentServer via a new public mount() method, so /slack/* shares the existing HTTP listener + TLS. Boots only when SLACK_BOTS_ENABLED=1. Bots missing config (TOKEN/SIGNING_SECRET/SOURCE) are skipped with a warning, not fatal. When LYZR_PROXY_ENABLED=1 + LYZR_UPSTREAM_* are set, the claudebot's sandboxes are auto-wired with ANTHROPIC_BASE_URL=<in-process proxy>, and gitagent is auto-wired with GITCLAW_MODEL_BASE_URL + OPENAI_API_KEY + model="openai:<lyzr-id>". So a single set of LYZR_* envs makes both bots talk to Lyzr. Verified locally: /slack/health → {"ok":true,"bots":[...],"mongoConnected":true} invalid signature → 401 unknown bot → 404 url_verification → echoes challenge correctly with valid HMAC Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 971d83e commit 7f1bb69

4 files changed

Lines changed: 629 additions & 1 deletion

File tree

examples/computeragent-server.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,15 @@ interface SandboxChatBody {
611611
export class ComputerAgentServer {
612612
private readonly opts: ComputerAgentServerOptions;
613613
private readonly app = new Hono();
614+
615+
/**
616+
* Mount another Hono app under this server's HTTP listener. Used by the
617+
* Slack-bot module to attach /slack/* routes without booting a separate
618+
* port. Call BEFORE `listen()`.
619+
*/
620+
mount(subApp: Hono): void {
621+
this.app.route("/", subApp);
622+
}
614623
private server: ServerType | null = null;
615624
private readonly runs = new Map<string, ActiveRun>();
616625
private readonly broker = new TaskBroker();
@@ -2164,6 +2173,40 @@ if (import.meta.url === `file://${process.argv[1]}`) {
21642173
defaultStateStore,
21652174
sandbox: sandboxCfg,
21662175
});
2176+
2177+
// ── Optional: Slack bots — mount under /slack/{bot}/events.
2178+
//
2179+
// Two bots exposed when SLACK_BOTS_ENABLED=1:
2180+
// /slack/claudebot/events → claude-agent-sdk + (optional) LYZR proxy
2181+
// /slack/gitagent/events → gitagent direct
2182+
//
2183+
// Each bot maps a Slack thread to a warm /sandboxes instance with
2184+
// autoSave → S3 so threads can be resumed days later. Bots missing env
2185+
// config (TOKEN/SIGNING_SECRET/SOURCE) are skipped at boot, not fatal.
2186+
let slackBotNames: string[] = [];
2187+
if (process.env.SLACK_BOTS_ENABLED === "1") {
2188+
if (!process.env.MONGO_URL) {
2189+
console.error("[slack-bot] SLACK_BOTS_ENABLED=1 but MONGO_URL not set — Slack thread map needs mongo; skipping");
2190+
} else {
2191+
const [{ createSlackBotsApp, botsFromEnv }] = await Promise.all([
2192+
import("./slack-bot.ts"),
2193+
]);
2194+
const bots = botsFromEnv();
2195+
if (bots.length === 0) {
2196+
console.error("[slack-bot] SLACK_BOTS_ENABLED=1 but no bot has all of TOKEN+SIGNING_SECRET+SOURCE; skipping");
2197+
} else {
2198+
const caBase = `http://${process.env.HOST ?? "127.0.0.1"}:${Number(process.env.PORT ?? 8787)}`;
2199+
const slackApp = createSlackBotsApp({
2200+
caBase,
2201+
mongoUrl: process.env.MONGO_URL,
2202+
mongoDb: process.env.MONGO_DATABASE ?? "computeragent-test",
2203+
bots,
2204+
});
2205+
server.mount(slackApp);
2206+
slackBotNames = bots.map((b) => b.name);
2207+
}
2208+
}
2209+
}
21672210
// Graceful shutdown — kill the proxy too when the main server stops.
21682211
for (const sig of ["SIGINT", "SIGTERM"] as const) {
21692212
process.on(sig, () => {
@@ -2180,6 +2223,13 @@ if (import.meta.url === `file://${process.argv[1]}`) {
21802223
console.log(`Anthropic↔OpenAI proxy listening on http://127.0.0.1:${lyzrProxyHandle.port}`);
21812224
console.log(` → use envs.ANTHROPIC_BASE_URL=http://127.0.0.1:${lyzrProxyHandle.port} on /run for claude-agent-sdk + deepagents`);
21822225
}
2226+
if (slackBotNames.length > 0) {
2227+
console.log(`Slack bots active: ${slackBotNames.join(", ")}`);
2228+
for (const b of slackBotNames) {
2229+
console.log(` POST /slack/${b}/events (set as Slack Request URL for the ${b} app)`);
2230+
}
2231+
console.log(` GET /slack/health (mongo connectivity + bot list)`);
2232+
}
21832233
console.log("");
21842234
console.log("Endpoints:");
21852235
console.log(" GET /health runtimes + default + active count");

examples/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"@computeragent/llm-proxy-openai": "workspace:*",
3232
"hono": "^4.6.0",
3333
"@hono/node-server": "^1.13.0",
34-
"tar-stream": "^3.1.7"
34+
"tar-stream": "^3.1.7",
35+
"mongodb": "^6.10.0"
3536
},
3637
"devDependencies": {
3738
"@types/tar-stream": "^3.1.3"

0 commit comments

Comments
 (0)