Skip to content

Commit 7e6b485

Browse files
committed
fix: clean up Slack UX — typing indicator, remove noise, fix empty response
- Add 'is thinking...' typing indicator via assistant.threads.setStatus (auto-clears when bot replies; refreshes to 'is working...' on tool events) - Remove session share link, session idle, and tool completion messages from Slack threads (moved to server-side console.log for debugging) - Fix 'I received your message but didn't have a response' — silently skip empty responses instead of posting a confusing fallback message, log raw response data for debugging Signed-off-by: xuezhaojun <xuezhaokeepgoing@gmail.com>
1 parent 60a703f commit 7e6b485

1 file changed

Lines changed: 50 additions & 15 deletions

File tree

opencode-slack-plugin/src/index.ts

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,25 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
298298
})
299299
}
300300

301+
// ------------------------------------------------------------------
302+
// Set typing indicator ("is thinking...") in Slack thread
303+
// ------------------------------------------------------------------
304+
// Uses assistant.threads.setStatus which shows "<Bot Name> is thinking..."
305+
// in the thread. The status auto-clears when the bot posts a message.
306+
// Falls back silently if the API is unavailable (e.g., missing scope).
307+
308+
async function setTypingStatus(channel: string, thread: string, status = "is thinking...") {
309+
try {
310+
await web.apiCall("assistant.threads.setStatus", {
311+
channel_id: channel,
312+
thread_ts: thread,
313+
status,
314+
})
315+
} catch {
316+
// Best-effort — don't fail the message flow if status API is unavailable
317+
}
318+
}
319+
301320
// ------------------------------------------------------------------
302321
// Get or create an OpenCode session for a Slack thread
303322
// ------------------------------------------------------------------
@@ -346,11 +365,12 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
346365
evictOldestSession()
347366
}
348367

349-
// Share session link in thread
368+
// Log session share link (not posted to Slack — too low-level for users).
369+
// Visible in OpenCode server logs: look for "[slack-plugin] Session share:"
350370
try {
351371
const shareResult = await client.session.share({ path: { id: result.data.id } })
352372
if (!shareResult.error && shareResult.data?.share?.url) {
353-
await postToThread(channel, thread, `Session: ${shareResult.data.share.url}`)
373+
console.log(`${LOG_PREFIX} Session share: ${shareResult.data.share.url} (thread: ${key})`)
354374
}
355375
} catch {
356376
// Share is optional
@@ -484,6 +504,9 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
484504

485505
session.lastActive = Date.now()
486506

507+
// Show typing indicator while processing
508+
setTypingStatus(channel, thread)
509+
487510
// Enqueue the prompt to serialize concurrent messages in the same session.
488511
// OpenCode silently drops prompt() calls on a busy session (the message is
489512
// written to DB but runLoop never starts), so we must serialize here.
@@ -499,13 +522,24 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
499522
return
500523
}
501524

502-
const responseText =
503-
(result.data as any).parts
504-
?.filter((p: any) => p.type === "text")
505-
.map((p: any) => ("text" in p ? p.text : ""))
506-
.join("\n") || "I received your message but didn't have a response."
525+
// Extract text from response parts. The response shape is
526+
// { info: AssistantMessage, parts: Part[] } where Part can be
527+
// { type: "text", text: string } or tool/step parts.
528+
const data = result.data as any
529+
const parts = data?.parts ?? []
530+
const textParts = parts
531+
.filter((p: any) => p.type === "text" && p.text)
532+
.map((p: any) => p.text)
533+
534+
if (textParts.length === 0) {
535+
// No text in response — log the raw data for debugging but don't
536+
// show a confusing fallback message to the user. The session.idle
537+
// event or subsequent messages will follow.
538+
console.warn(`${LOG_PREFIX} Empty text response for session ${session.sessionId}:`, JSON.stringify(data).slice(0, 500))
539+
return
540+
}
507541

508-
await postToThread(channel, thread, responseText)
542+
await postToThread(channel, thread, textParts.join("\n"))
509543
})
510544
}
511545

@@ -650,27 +684,28 @@ const slack = async (input: PluginInput): Promise<Hooks> => {
650684
event: async ({ event }) => {
651685
const evt = event as any
652686

653-
// Forward tool completion updates to the relevant Slack thread
687+
// Log tool completion events (not posted to Slack — too noisy for users).
688+
// Visible in OpenCode server logs: look for "[slack-plugin] Tool:"
654689
if (evt.type === "message.part.updated") {
655690
const part = evt.properties?.part
656691
if (part?.type === "tool" && part.state?.status === "completed" && part.sessionID) {
657692
const session = findSessionByOpenCodeId(part.sessionID)
658693
if (session) {
659-
const toolMsg = `*${part.tool}* — ${part.state.title || "completed"}`
660-
postToThread(session.channel, session.thread, toolMsg).catch(() => {})
694+
console.log(`${LOG_PREFIX} Tool: ${part.tool}${part.state.title || "completed"} (session: ${session.sessionId})`)
695+
// Refresh typing status so the user knows the bot is still working
696+
setTypingStatus(session.channel, session.thread, "is working...")
661697
}
662698
}
663699
}
664700

665-
// Notify Slack thread when the session finishes processing (idle).
666-
// This gives users a clear signal that the bot is done and ready
667-
// for the next message, which is especially useful for long tasks.
701+
// Log session idle events (not posted to Slack).
702+
// Visible in OpenCode server logs: look for "[slack-plugin] Session idle:"
668703
if (evt.type === "session.idle") {
669704
const sessionId = evt.properties?.sessionID ?? evt.properties?.id
670705
if (sessionId) {
671706
const session = findSessionByOpenCodeId(sessionId)
672707
if (session) {
673-
postToThread(session.channel, session.thread, "_Session idle — ready for next message._").catch(() => {})
708+
console.log(`${LOG_PREFIX} Session idle: ${session.sessionId}`)
674709
}
675710
}
676711
}

0 commit comments

Comments
 (0)