Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 21 additions & 25 deletions cli/src/slack/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,34 +518,30 @@ export async function runSlackBridge(opts: BridgeOptions): Promise<void> {
recentRelays.set(decision.slug, relays);
pasteTmuxPrompt(decision.slug, prompt); // tmux session name == slug

// Post a tiny bot-authored anchor so the working pill has something to
// attach to immediately. Without this the channel looks dead for the
// 10-20s between the human typing and the agent's first text post (which
// is the natural pill anchor for the existing flow). Slack's
// assistant.threads.setStatus only accepts thread roots authored by the
// app itself, so we cannot pin the pill to the human's own message
// (that would 400 with invalid_thread_ts).
// Light the working pill immediately so the channel shows activity
// within seconds of the relay, instead of looking dead until the
// agent's first text post (10-20s later). Slack's
// assistant.threads.setStatus only accepts thread roots authored by
// the app itself, so the human's own message ts is not a valid
// anchor. Instead, reuse the most recent app-authored anchor for the
// slug (the previous turn's text post). No new bot message is
// posted — the pill rides on the existing prior reply. The agent's
// first text post in this turn will then become the new anchor via
// the existing post loop, moving the pill onto it.
//
// The eyes emoji is chosen for minimal noise: a single character that
// reads as "received, on it" and renders cleanly next to the working
// pill ("is working…"). The agent's first real text post will become
// the new thread root via the existing post loop, draining any tools
// accumulated in the meantime under this anchor and moving the pill
// onto the narrative text.
// If there is no prior anchor (very first interaction with the
// agent), skip: the pill will appear the natural way on the first
// reply. That happens once per agent lifetime, so the one-time
// delay is acceptable to avoid a noise-only bot post.
if (event.channel) {
try {
const ts = await client.post(event.channel, "👀");
if (ts) {
writeThreadRoot(decision.slug, ts);
try {
await client.setStatus(event.channel, ts, WORKING_LABEL);
setActivePill(decision.slug, { channelId: event.channel, threadTs: ts, label: WORKING_LABEL, lastSetMs: Date.now() });
} catch (e) {
console.error(`setStatus on relay anchor for ${decision.slug} failed:`, e);
}
const prevAnchor = readThreadRoot(decision.slug);
if (prevAnchor) {
try {
await client.setStatus(event.channel, prevAnchor, WORKING_LABEL);
setActivePill(decision.slug, { channelId: event.channel, threadTs: prevAnchor, label: WORKING_LABEL, lastSetMs: Date.now() });
} catch (e) {
console.error(`setStatus on prior anchor for ${decision.slug} failed:`, e);
}
} catch (e) {
console.error(`relay anchor post for ${decision.slug} failed:`, e);
}
}
});
Expand Down