import { App } from "@slack/bolt";
const app = new App({ token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET });
const BASE = "https://api.usepatchwork.co/v1/loom";
const threads = new Map(); // slack thread_ts → patchwork thread id
function pw(subject) {
return {
"Authorization": `Bearer ${process.env.PATCHWORK_API_KEY}`,
"Patchwork-Subject": subject,
"Content-Type": "application/json",
};
}
app.event("app_mention", async ({ event, say }) => {
const subject = `slack:${event.team}:${event.user}`;
const key = event.thread_ts ?? event.ts;
const headers = pw(subject);
// One Patchwork thread per Slack thread.
let threadId = threads.get(key);
if (!threadId) {
const res = await fetch(`${BASE}/threads`, {
method: "POST", headers,
body: JSON.stringify({ agent: process.env.PATCHWORK_AGENT }),
}).then((r) => r.json());
threadId = res.data.id;
threads.set(key, threadId);
}
const started = await fetch(`${BASE}/threads/${threadId}/messages`, {
method: "POST", headers,
body: JSON.stringify({ content: event.text }),
}).then((r) => r.json());
const reply = await settle(started.data.run_id, headers);
await say({ text: reply, thread_ts: key });
});
async function settle(runId, headers, tries = 60) {
for (let i = 0; i < tries; i++) {
const { data } = await fetch(`${BASE}/runs/${runId}`, { headers })
.then((r) => r.json());
if (data.terminal) {
return data.state === "failed"
? "Something went wrong on my side."
: data.message?.content ?? "(no reply)";
}
await new Promise((r) => setTimeout(r, 1000));
}
return "Still working on that one.";
}