Repository navigation
feat(slack): Agent Sessions lifecycle, native stop and automatic titles (#215) - #303
Conversation
…es (#215) Slack half of vercel/chat 2ce2be00 (#862) plus 8b6d7f3a (#882) and 2cc8cc3f (#897), gated on agent_view: agents.sessions.setStatus/rename, supports_turn_cancellation, start_typing/end_typing session states, stream() signal checks and session_status on stop, agent_session_stopped (abort_turn without the thread lock) and agent_session_title_changed events, and the session_title config applied by the open_dm bridge. Closes #215 Part of #184
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 45 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
# Conflicts: # CHANGELOG.md
|
Merge gate: CI green (Lint & Type Check, test (3.12), test (3.13), CodeQL / Analyze (actions, python)) on e1773ef; local Codex review (gpt-6-astra, xhigh, --base origin/main) on e1773ef: "No actionable regressions found against the specified merge base. Validation passed: 7,882 tests, 24 skipped, lint checks, and type checking"; 5 astra rounds (3 on the initial branch, 2 after merging main, last 3 clean); CodeRabbit rate-limited (no review/inline comments), no gemini comments. origin/main (78787c6) is already an ancestor of HEAD, so no re-merge was needed. #215 stays open for the live agent_view Stop-button check. Merging with --admin (Protect Main requires a code-owner approval). |
Summary
Slack Agent Sessions lifecycle and native Stop, all gated on
agent_view=True(issue #215). Built on #201 (turn cancellation,TurnSignal,end_typing, agent-session events) and #214 (agent_view, open_dm bridge). PR A and PR B from the issue land together (~1.05k LOC including tests and docs, under the split threshold).Upstream commits mapped
2ce2be00feat(slack): Agent Sessions lifecycle and native stop (feat(slack): add Agent Sessions lifecycle and native stop vercel/chat#862, chat@4.39.0), Slack half8b6d7f3afix(slack): clear Agent Session typing status (#882, chat@4.40.0)2cc8cc3ffix(slack): surface custom status text in the Agent messaging experience (#897, chat@4.40.0)d4a1f03arotationsession_status="processing"hook left by [4.41/SL3] Slack: rotate long native streams before expiry #208 (the# SL10:markers are gone)What changed (
src/chat_sdk/adapters/slack/)set_session_status(channel_id, thread_ts, status, *, initiator_user_id=None, title=None)/_rename_agent_sessionviaclient.api_call("agents.sessions.setStatus" | "agents.sessions.rename", json=...).supports_turn_cancellationreturnsagent_view.start_typing: under agent_view,Nonesets the session toprocessingand""toactive(compared with== "", neveror), withinitiator_user_id; failures are logged with no legacy fallback. A custom status still usesassistant.threads.setStatus.end_typing(thread_id, status="active")(agent_view + thread ts only).set_assistant_status: agent_view + empty/whitespace status sets the sessionactive; otherwiseloading_messagesis explicit, then configured, then[status]under agent_view.set_assistant_titlerenames the session under agent_view.stream(): breaks at the top of each chunk iteration whenoptions.signal.aborted, then finalizes throughstop()as usual. Under agent_view: finalstop()getssession_status(options.session_statusor"active"), rotationstop()gets"processing", and the post+edit fallback, first-stop-failure fallback and final-stop-expiry returns callend_typing.agent_session_stoppedruns a task that awaitschat.abort_turn(warns), sets the sessionactive(warns), then callsprocess_agent_session_stoppedwithstreaming_message_ts; no thread lock; pinned, logging done-callback, shieldedwait_until.agent_session_title_changedcallsprocess_agent_session_title_changed.SlackAdapterConfig.session_title(SlackSessionTitle = bool | resolver,Nonedefault =agent_view) +SlackSessionTitleContext; applied by the open_dm bridge task after processing a top-level human DM with text.Tests ported (
index.test.ts, not fidelity-mapped)"routes custom labels and clearing separately on an agent_view DM thread", "uses native $expected without custom text in agent view" (it.each), "does not fall back to a legacy label when native status fails", "surfaces custom status text via the legacy API and clears via the sessions lifecycle", "passes agent loading messages $expected" (it.each), "renames an agent session under agentView", "finishes agent streams with an active session status" (
test_slack_api.py), "keeps the agent session processing while the reply continues" (test_slack_stream_rotation.py), "aborts and activates an agent session when the user stops it", "dispatches agent session title changes", "automatically titles a top-level agent_view DM session", "skips automatic titles when sessionTitle is disabled", "does not retitle an agent_view DM follow-up".Python-specific: legacy mode makes no
agents.sessions.*call (typing, end_typing, status, title; stream stop payload unchanged);""vsNone; an aborted signal mid-stream stops reading and awaitsstop()exactly once; fallback and final-expiryend_typingwith the requested status; the stop event aborts a realChatturn that holds the thread lock (asyncio.Event gates, no sleeps); abort/activation failures still dispatch; title trimming and 80-char cut, sync/async resolvers,None/blank skip, resolver failure logged, bot/empty messages skipped, off without agent_view.Fidelity
index.test.tsis not mapped, so the target report is unchanged andscripts/fidelity_target.jsonis not modified:Delta vs committed report (HEAD): missing 100 -> 100 (+0)Divergences
None added to the non-parity table. Notes (documented in
docs/UPSTREAM_SYNC.md):processMessage(...).then(applyConfiguredSessionTitle)branch is unreachable (the bridge takes every agent_view top-level DM first, same guard), so it is not ported; a code comment citesindex.ts:3537-3557.agents.sessions.setStatus/renamego through_with_token_kwargs(upstreamwithToken, landed in [4.41/SL8b] Slack: Enterprise Grid org-wide installs, authorizations[] routing, retry marker (split from #213) #268 and merged in here), so org-wide installs carry the event'steam_id.slicecounts UTF-16 units).Consumer impact (Slack,
agent_view=Trueonly; nothing changes without it)thread.start_typing()/start_typing()without a status now callsagents.sessions.setStatus(processing) instead ofassistant.threads.setStatus"Typing...";start_typing(tid, "")setsactive. Replies after typing move the session back toactiveviaend_typing.set_assistant_status(..., "")sets the sessionactive;set_assistant_titlecallsagents.sessions.rename.session_statusonchat.stopStream(activeby default) andprocessingon rotation; an aborted turn finalizes the message with what was streamed.agent_session_stopped) now aborts the running turn (thread.signal, cross-process via the state backend), sets the sessionactive, and runson_agent_session_stoppedhandlers.session_title=Falseopts out).Live check pending: the Stop button cancelling a streaming reply and the session returning to
activestill need verification in anagent_viewworkspace.Validation
ruff check/format, audit_test_quality (0 hard failures),
verify_test_fidelity --check-docs,--strictagainst chat@4.31.0 (730 real tests, all matched), pytest7882 passed, 24 skipped(after merging main at 78787c6), pyrefly 0 errors.Review (gpt-6-astra)
_stoptask registered withwait_until: identical to upstream (index.ts:3980-3990,processAgentSessionStoppedis fire-and-forget and registers its own task with the samewaitUntil); kept, with a parity comment. (b) Breaking out ofasync foron abort left a generator input suspended (JSfor awaitcloses it onbreak): fixed, the input is closed before breaking.BaseExceptionGroup([GeneratorExit()])(TaskGroup acrossyield) and skipstop(): fixed with the existingaclose_quietlyhelper, with a test.Part of #215 (stays open until the live
agent_viewStop-button check is recorded)Part of #184
Merge gate
Final HEAD:
e1773ef(includes main at78787c6).Independent review findings (5):
agent_viewStop-button check from [4.41/SL10] Slack Agent Sessions lifecycle + native stop #215's acceptance criteria has not been run. The footer is now "Part of [4.41/SL10] Slack Agent Sessions lifecycle + native stop #215", so merging leaves the issue open for that check. The "pending" notes stay in CHANGELOG.md and docs/UPSTREAM_SYNC.md. The live check itself is still not done." Summarize this \nmore detail"->"Summarize this"). Mutating the code to drop the first-line split now fails it.test_first_stop_failure_fallback_ends_the_agent_session. All deltas stay buffered, thenstop()fails, and the test asserts the post+edit result and onesetStatuswith the requested status. Deleting thatend_typingnow fails it.set_assistant_status("D1", "1.2", "Working...")sends noloading_messages. Removing theand self._agent_viewgate now fails it.{"subtype": "file_share"}added to the skip parametrize. Removing the subtype guard now fails it.Found while merging main (#268, Enterprise Grid): main now has
_with_token_kwargs(upstreamwithToken). Upstream wrapsagents.sessions.setStatus/renameinwithToken(adapter-slack/src/index.ts:4317,4336), so both calls now go through it. Two cases were added to_WITH_TOKEN_CALL_SITESintests/test_slack_enterprise_grid.py; both fail without the fix. The "no per-call helper" note in docs and in this body is updated.gpt-6-astra: 2 rounds in this pass, both clean.
36160c5: no actionable findings.e1773ef(after merging main): no actionable findings. This is the final HEAD.Earlier rounds 1-3 on
f8da06dare recorded above.Bots: CodeRabbit was rate-limited, with no review or inline comments. No gemini comments.
CI: green on
e1773ef: Lint & Type Check, test (3.12), test (3.13), CodeQL / Analyze.Local full validation on
e1773ef:--check-docs: pass.--strictagainst chat@4.31.0: all matched.Delta vs committed report (HEAD): missing 100 -> 100 (+0).