Skip to content

Commit a902ce9

Browse files
dcramercodex
andauthored
fix(slack): Restore first-thread status and trim short continuations (#206)
Restore the Slack thread delivery regressions from the streaming overhaul and tighten the contracts around them. For the first non-DM thread message we were only resolving assistant thread context from raw `thread_ts`, which meant status updates did not attach until after the first reply already existed. This changes non-DM resolution to use `thread_ts ?? ts`, keeps `message.im` strict about explicit `thread_ts`, and adds regression coverage for the first-message channel thread path. This also drops the `[Continued below]` marker when a reply only spans two Slack posts. That marker was only helpful for longer continuations and added noise in ordinary short thread replies. While touching these contracts, the PR updates the local Slack skill/docs to synthesize the Slack assistant-thread APIs we rely on, rewrites the eval authoring pattern around maintainer-readable describe blocks, case names, and `rubric({ contract, pass, allow, fail })` criteria, and imports a root `policies/` directory with explicit `AGENTS.md` references so repo-wide defaults can live in small policy docs instead of growing the root agent instructions. --------- Co-authored-by: GPT-5 Codex <noreply@openai.com>
1 parent 28c9701 commit a902ce9

27 files changed

Lines changed: 1138 additions & 367 deletions

.agents/skills/slack-development/SKILL.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ Implement Slack-facing behavior with predictable formatting, inbound routing, an
99

1010
Determine which category applies before writing code:
1111

12-
| Category | Typical request | Primary reference |
13-
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14-
| Output formatting | "Fix markdown", "why does Slack render this weirdly?" | `${CLAUDE_SKILL_ROOT}/references/slack-output-formatting.md` |
15-
| Slack event payloads | "What does Slack send?", "why did raw event parsing fail?" | `${CLAUDE_SKILL_ROOT}/references/slack-inbound-message-formats.md` |
16-
| Chat SDK payload contract | "What fields do handlers actually receive?", "which fields are reliable in `onSubscribedMessage`?" | `${CLAUDE_SKILL_ROOT}/references/chat-sdk-payload-contract.md` |
17-
| Thread routing | "Passive detector skips thread replies", "reply/no-reply logic is wrong" | `${CLAUDE_SKILL_ROOT}/references/slack-thread-routing.md` |
18-
| Assistant-thread APIs | "Why does `assistant.threads.setStatus` fail?", "should this DM have assistant status/title?", "does Chat tab DM count as an assistant thread?" | Read Slack docs for `assistant_thread_started`, `assistant_thread_context_changed`, `message.im`, and `assistant.threads.*` first, then load `${CLAUDE_SKILL_ROOT}/references/chat-sdk-payload-contract.md` |
19-
| Long-running behavior | "No feedback while it runs", "show progress", "stream output" | `${CLAUDE_SKILL_ROOT}/references/chat-sdk-patterns.md` |
20-
| Multiple categories | Change touches formatting, routing, and/or runtime UX | Read only the needed references above |
12+
| Category | Typical request | Primary reference |
13+
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14+
| Output formatting | "Fix markdown", "why does Slack render this weirdly?" | `${CLAUDE_SKILL_ROOT}/references/slack-output-formatting.md` |
15+
| Slack event payloads | "What does Slack send?", "why did raw event parsing fail?" | `${CLAUDE_SKILL_ROOT}/references/slack-inbound-message-formats.md` |
16+
| Chat SDK payload contract | "What fields do handlers actually receive?", "which fields are reliable in `onSubscribedMessage`?" | `${CLAUDE_SKILL_ROOT}/references/chat-sdk-payload-contract.md` |
17+
| Thread routing | "Passive detector skips thread replies", "reply/no-reply logic is wrong" | `${CLAUDE_SKILL_ROOT}/references/slack-thread-routing.md` |
18+
| Assistant-thread APIs | "Why does `assistant.threads.setStatus` fail?", "should this DM have assistant status/title?", "does Chat tab DM count as an assistant thread?" | Read Slack docs for `assistant_thread_started`, `assistant_thread_context_changed`, `message.im`, and `assistant.threads.*` first, then load `${CLAUDE_SKILL_ROOT}/references/assistant-thread-apis.md` and `${CLAUDE_SKILL_ROOT}/references/chat-sdk-payload-contract.md` |
19+
| Long-running behavior | "No feedback while it runs", "show progress", "stream output" | `${CLAUDE_SKILL_ROOT}/references/chat-sdk-patterns.md` |
20+
| Multiple categories | Change touches formatting, routing, and/or runtime UX | Read only the needed references above |
2121

2222
If the request is ambiguous, ask one focused question and continue after clarification.
2323

@@ -27,7 +27,7 @@ Use the selected reference files as the implementation guide. Keep SKILL.md high
2727

2828
Slack assistant-thread guardrails:
2929

30-
1. Use Slack's current inbound event payload as the source of truth for assistant-thread API calls. For `message.im`, require the live `channel` and explicit `thread_ts`. For lifecycle events, use `assistant_thread.channel_id` and `assistant_thread.thread_ts`.
30+
1. Use Slack's current inbound event payload as the source of truth for assistant-thread API calls. For non-DM message events, use the live `channel` plus `thread_ts ?? ts`. For `message.im`, require the live `channel` and explicit `thread_ts`. For lifecycle events, use `assistant_thread.channel_id` and `assistant_thread.thread_ts`.
3131
2. Do not invent assistant-thread identifiers from persisted state unless Slack's docs explicitly require it.
3232
3. Separate reply continuity from assistant-thread API eligibility. A stored root timestamp can be valid for reply threading without being valid for `assistant.threads.*`.
3333
4. Treat `assistant_thread_started` and `assistant_thread_context_changed` differently. Context changes can refresh prompts/context, but should not clobber a conversation-specific thread title back to a generic default.
@@ -51,7 +51,7 @@ Use this checklist:
5151
- Rendering: message examples render correctly in Slack (`mrkdwn` expectations, escapes, mentions/links).
5252
- Inbound formats: routing uses documented Chat SDK payload fields first; raw Slack parsing only when necessary.
5353
- Thread routing: explicit bot mention paths bypass passive no-reply classification.
54-
- Assistant threads: `assistant.threads.*` calls use the live inbound assistant-thread context; `message.im` must carry explicit `thread_ts`, and runtime code does not synthesize assistant roots for status/title updates.
54+
- Assistant threads: `assistant.threads.*` calls use the live inbound assistant-thread context; non-DM message events may use `thread_ts ?? ts`, `message.im` must carry explicit `thread_ts`, and runtime code does not synthesize DM assistant roots for status/title updates.
5555
- Accessibility: block messages include an adequate top-level fallback `text` strategy.
5656
- Latency UX: user sees immediate feedback for long-running tasks.
5757
- Streaming/progress: behavior is observable during tool/model execution, not only at completion.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Assistant-Thread APIs
2+
3+
## Scope
4+
5+
Canonical local synthesis of the Slack and Chat SDK APIs this repository
6+
relies on for Slack assistant-thread lifecycle, status, and title behavior.
7+
8+
Use this file before changing:
9+
10+
- assistant-thread status/title behavior
11+
- DM versus channel-thread Slack context handling
12+
- assistant lifecycle event handling
13+
- Slack skill guidance that refers to `assistant.threads.*`
14+
15+
This file intentionally separates:
16+
17+
1. vendor-documented API facts
18+
2. repository policy layered on top
19+
3. concrete implementation seams in this repo
20+
21+
## External API Surfaces We Rely On
22+
23+
### Slack Events API
24+
25+
1. `app_mention`
26+
27+
- Used for explicit mentions in channels.
28+
- Carries `channel`, `ts`, and optional `thread_ts`.
29+
- Messages in direct messages are not delivered through `app_mention`.
30+
31+
2. `message.im`
32+
33+
- Used for DM traffic to the bot.
34+
- Carries `channel`, `ts`, and may omit `thread_ts` on the first DM message.
35+
- Slack AI/app-thread continuation in DMs depends on `thread_ts` when present.
36+
37+
3. `assistant_thread_started`
38+
39+
- Carries `assistant_thread.channel_id`, `assistant_thread.thread_ts`, and optional `assistant_thread.context.channel_id`.
40+
- Used to initialize suggested prompts and thread-title state for Slack assistant threads.
41+
42+
4. `assistant_thread_context_changed`
43+
44+
- Carries the same `assistant_thread.*` shape as the start event.
45+
- Used to refresh context without resetting an established conversation title.
46+
47+
### Slack Web API
48+
49+
1. `assistant.threads.setStatus`
50+
51+
- Requires `channel_id` and `thread_ts`.
52+
- Slack clears status automatically when a reply is posted.
53+
- Sending an empty `status` clears the indicator explicitly.
54+
- Slack currently accepts either `chat:write` or `assistant:write` for this method. This changed in March 2026 and may change again, so re-check the live docs before changing scope guidance.
55+
56+
2. `assistant.threads.setTitle`
57+
58+
- Requires `channel_id`, `thread_ts`, and `title`.
59+
- Still documented as an assistant-thread/title API, primarily for app-thread history in DMs.
60+
- `assistant:write` remains the documented scope for this method.
61+
62+
3. `assistant.threads.setSuggestedPrompts`
63+
64+
- Requires `channel_id`, `thread_ts`, and prompt payloads.
65+
- Used from the lifecycle path when Slack starts or refreshes an assistant thread.
66+
67+
### Chat SDK Slack Adapter
68+
69+
1. `setAssistantStatus(channelId, threadTs, status, loadingMessages?)`
70+
71+
- Thin adapter wrapper around Slack `assistant.threads.setStatus`.
72+
73+
2. `setAssistantTitle(channelId, threadTs, title)`
74+
75+
- Thin adapter wrapper around Slack `assistant.threads.setTitle`.
76+
77+
3. `startTyping(threadId, status?)`
78+
79+
- Available in the adapter, but this repository does not use it as the baseline visible Slack progress surface.
80+
81+
## Repository Policy
82+
83+
1. Assistant status is the primary in-flight progress surface. Visible reply text is finalized before posting.
84+
2. For non-DM message events, the live assistant-thread key is `channel + (thread_ts ?? ts)`.
85+
3. For `message.im`, assistant-thread status/title updates require an explicit live `thread_ts`. Do not synthesize DM assistant roots from generic `ts` or persisted state.
86+
4. For lifecycle events, use `assistant_thread.channel_id + assistant_thread.thread_ts`.
87+
5. Reply continuity and assistant-thread API eligibility are separate concerns. A persisted thread root can be valid for reply threading without being valid for `assistant.threads.*`.
88+
6. Conversation-specific thread titles are DM-only in the normal reply path and come from the earliest human message the runtime actually knows about for that thread.
89+
7. `assistant_thread_context_changed` may refresh prompts/context, but must not clobber an already established conversation title.
90+
91+
## Implementation Map In This Repo
92+
93+
1. Live assistant-thread context selection:
94+
95+
- `packages/junior/src/chat/runtime/thread-context.ts`
96+
- `getAssistantThreadContext()`
97+
98+
2. Status sending and token binding:
99+
100+
- `packages/junior/src/chat/slack/assistant-thread/status-send.ts`
101+
- `packages/junior/src/chat/slack/assistant-thread/status.ts`
102+
- `packages/junior/src/chat/slack/assistant-thread/status-scheduler.ts`
103+
104+
3. Lifecycle event handling:
105+
106+
- `packages/junior/src/chat/slack/assistant-thread/lifecycle.ts`
107+
- `packages/junior/src/chat/runtime/slack-runtime.ts`
108+
109+
4. DM title generation and permission handling:
110+
111+
- `packages/junior/src/chat/slack/assistant-thread/title.ts`
112+
113+
5. Current delivery contract:
114+
115+
- `specs/slack-agent-delivery-spec.md`
116+
117+
## Audit Checklist
118+
119+
When changing code or guidance in this area, verify all of the following:
120+
121+
1. `app_mention` handling does not treat channel traffic like `message.im`.
122+
2. Channel-thread first replies use the live `ts` when `thread_ts` is absent.
123+
3. DM status/title updates are skipped when the current `message.im` lacks explicit `thread_ts`.
124+
4. Lifecycle events use `assistant_thread.channel_id` and `assistant_thread.thread_ts`.
125+
5. `assistant.threads.setStatus` calls never receive adapter-scoped `slack:<channel>` IDs.
126+
6. Title generation stays best effort and does not block visible reply delivery.
127+
7. Skill/docs wording does not hardcode stale scope assumptions for `setStatus`.
128+
129+
## Sources
130+
131+
- Slack `app_mention`: https://docs.slack.dev/reference/events/app_mention/
132+
- Slack `message.im`: https://docs.slack.dev/reference/events/message.im
133+
- Slack `assistant_thread_started`: https://docs.slack.dev/reference/events/assistant_thread_started
134+
- Slack `assistant_thread_context_changed`: https://docs.slack.dev/reference/events/assistant_thread_context_changed/
135+
- Slack `assistant.threads.setStatus`: https://docs.slack.dev/reference/methods/assistant.threads.setStatus
136+
- Slack `assistant.threads.setTitle`: https://docs.slack.dev/reference/methods/assistant.threads.setTitle
137+
- Slack status scope update: https://docs.slack.dev/changelog/2026/03/05/set-status-scope-update/
138+
- Slack `chat:write` scope: https://docs.slack.dev/reference/scopes/chat.write/
139+
- Slack AI app guidance: https://docs.slack.dev/ai/developing-ai-apps
140+
- Chat SDK Slack adapter: https://chat-sdk.dev/adapters/slack

.agents/skills/slack-development/references/chat-sdk-patterns.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ Implementation patterns for responsive Slack UX in Chat SDK based bots.
66

77
## Long-running interaction pattern
88

9+
Repository-specific policy:
10+
911
1. Start with immediate feedback:
1012

11-
- call `thread.startTyping("Working...")` when appropriate
12-
- or post a short acknowledgement message when typing is insufficient
13+
- prefer assistant status as the primary in-flight progress surface
14+
- use `thread.startTyping(...)` only when the current surface genuinely supports it and it does not conflict with the repository delivery contract
1315

14-
2. Stream final output:
16+
2. Keep visible reply text finalized:
1517

16-
- use AI SDK streaming (`streamText` or equivalent)
17-
- pass `textStream` to `thread.post(textStream)` for incremental Slack updates
18+
- do not treat incremental `thread.post(textStream)` output as the default correctness path in this repository
19+
- deliver the visible Slack reply after planning, chunking, and formatting are finalized
1820

19-
3. Emit progress transitions for multi-step/tool-heavy work:
21+
3. Emit progress transitions for multi-step/tool-heavy work through assistant status:
2022

2123
- "searching sources"
2224
- "fetching details"
@@ -28,14 +30,14 @@ Implementation patterns for responsive Slack UX in Chat SDK based bots.
2830

2931
1. Avoids "silent" multi-minute runs.
3032
2. Improves user trust during tool execution.
31-
3. Produces better perceived latency without sacrificing completeness.
33+
3. Keeps Slack-visible text aligned with the finalized reply contract.
3234

3335
## Slack adapter capabilities to use
3436

35-
1. `thread.startTyping(...)` for typing/status feedback.
36-
2. Native stream support through `thread.post(asyncIterable)` on Slack.
37-
3. `thread.postEphemeral(user, message, { fallbackToDM })` for messages visible only to one user.
38-
4. Optional Assistants API status features when `assistant:write` and assistant events are configured.
37+
1. `thread.startTyping(...)` for typing/status feedback when the current surface and contract allow it.
38+
2. `thread.postEphemeral(user, message, { fallbackToDM })` for messages visible only to one user.
39+
3. Optional Assistants API status features when the current Slack status/title scopes and assistant events are configured. Slack's status scope rules have changed recently, so verify the current docs before assuming `assistant:write` is required.
40+
4. Native stream support through `thread.post(asyncIterable)` exists, but in this repository it is not the baseline delivery contract for Slack replies.
3941

4042
## Ephemeral messages
4143

@@ -66,11 +68,13 @@ Note: Ephemeral messages are only available when you have a thread handle (e.g.
6668

6769
1. Keep webhook processing asynchronous with `waitUntil` in the webhook route.
6870
2. Keep external search behavior aligned with AI Gateway-native tools.
69-
3. Prefer one streaming response per user turn over many separate short messages.
71+
3. Prefer one finalized response plan per user turn over many separate short messages.
7072

7173
## Sources
7274

7375
- Chat SDK streaming guide: https://chat-sdk.dev/docs/streaming
7476
- Chat SDK Slack adapter guide: https://chat-sdk.dev/docs/adapters/slack
7577
- Hono webhook pattern in Chat SDK guide: https://chat-sdk.dev/docs/guides/slack-hono
78+
- Slack `assistant.threads.setStatus` docs: https://docs.slack.dev/reference/methods/assistant.threads.setStatus
79+
- Slack status scope update: https://docs.slack.dev/changelog/2026/03/05/set-status-scope-update/
7680
- AI Gateway web search capabilities: https://vercel.com/docs/ai-gateway/capabilities/web-search

0 commit comments

Comments
 (0)