Skip to content

feat(connector-slack): in-Slack UX pass — consent card, assistant affordances, results, errors#150

Merged
harry-harish merged 9 commits into
mainfrom
worktree-feat-connector-inslack-ux
Jul 9, 2026
Merged

feat(connector-slack): in-Slack UX pass — consent card, assistant affordances, results, errors#150
harry-harish merged 9 commits into
mainfrom
worktree-feat-connector-inslack-ux

Conversation

@harry-harish

@harry-harish harry-harish commented Jul 9, 2026

Copy link
Copy Markdown
Member

What

The first in-Slack product-UX pass for the peek Slack connector — how it
feels inside Slack, not the local setup flow. Four areas, no security-model,
consent-model, or daemon change.

1. Assistant affordances

  • Thread-start suggested prompts (4 real use cases: "What just failed?",
    "Show console errors", "What caused it?", "Make a Playwright repro") + a
    greeting, and a thread title from the user's first message.
  • A "thinking…" status during reasoning — set via a direct
    assistant.threads.setStatus at message receipt (the reply is async off the
    runtime turn-chain, so the Bolt handler-scoped helper would auto-clear too
    early). Auto-clears when the reply posts. (Needs chat:write — see the
    connector-slack README.)

2. Human-readable, secret-masked consent card

  • peek-mcp buildElicitMessage now emits a masked, verb-specific sentence
    (e.g. peek wants to Type "m•••m" into #email on your live browser.
    Approve?
    ) instead of run "type".
  • connector-slack consentCard renders both consent paths: the delegated
    (default) path shows that masked sentence in a clean header/context/buttons
    card; the suspend path shows a structured fields card. type text and
    request_user_input prompt are always masked (first/last char, length
    not leaked) — verified no plaintext can reach any block, incl. the raw-JSON
    fallback (only unclassifiable non-Action payloads).

3. Tier-1 result formatting

  • LLM prose renders with structure; Playwright-repro output posts as a fenced
    code block
    . Tier-2 structured cards deliberately deferred (would break the
    load-bearing brain-returns-text seam for little gain at alpha).

4. In-Slack error legibility

  • An optional SurfaceAdapter.postError + errorBlock, and a defensive
    7-mode classifyError in the runtime's turn-loop catch (mcp-down,
    llm-key-rejected, endpoint, not-recording, tool-error, consent-timeout,
    max-turns, unknown fallback) → actionable :warning: messages instead of a
    generic catch-all. Plus an mcp.connect() guard for a clean daemon failure.

How it was built

Subagent-driven: 8 tasks, each TDD with a per-task spec+quality review, then a
whole-branch review (READY TO MERGE). The error-classifier substrings were
grounded against the real thrown messages — which caught the plan's max-turns
string being wrong (real: …tool-use turns).

Testing

Full local CI green: pnpm build && typecheck && lint && test. peek-mcp 448,
connector-core 113, connector-slack 44. Changeset: @peekdev/mcp patch
(user-visible consent text; connectors are private).

Spec + plan: rrweb-stack-private/docs/{specs,plans}/2026-07-09-peek-connector-inslack-ux*.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added clearer, action-specific consent prompts with masked sensitive values.
    • Improved Slack messages with human-friendly action summaries, code-style formatting, and better error cards.
    • Added suggested prompts and thread status updates in Slack for a smoother chat experience.
  • Bug Fixes

    • Errors are now shown in a more readable format instead of a generic message.
    • Startup failures are now surfaced more clearly and stop the process cleanly.
  • Documentation

    • Added setup and usage guidance for the Slack connector.

harry-harish and others added 8 commits July 9, 2026 10:15
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Fire-and-forget postStatus() on the persisted app.client — not the handler-scoped
setStatus — so the "peek is thinking…" indicator survives the async reply chain.
No-op on the /peek slash path (no thread). README adds the chat:write scope note.

Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Adds `classifyError(err)` to connector-core/runtime.ts with 7 kind
branches (mcp-connection-lost, tool-error, llm-key-rejected,
llm-endpoint-error, not-recording, consent-timeout, max-turns) plus an
`unknown` default so a provider swap can never break error handling.

Rewrites the `runLoop` catch to call `adapter.postError?.(cid, classified)`
with postText fallback; removes the now-unused `ERROR_TEXT` const.

Wraps `mcp.connect()` in connector-slack/index.ts with a try/catch that
emits a clear stderr message and exits with code 1 before any Slack thread
exists.

Substring grounding: the brief's max-turns fixtures used 'maxturns'/'max
turns' which do not appear in SdkBrain's real thrown message ("SdkBrain
exceeded N tool-use turns"). Changed both the branch substring to
'tool-use turns' and the test fixture to the real string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
…rkdwn

Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
@harry-harish

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Action performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@harry-harish, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f790c83-9442-483d-bd35-8debcbef2c39

📥 Commits

Reviewing files that changed from the base of the PR and between 1bcfac9 and 2295afe.

📒 Files selected for processing (4)
  • packages/connector-core/src/runtime.test.ts
  • packages/connector-slack/src/blockkit.test.ts
  • packages/connector-slack/src/blockkit.ts
  • packages/connector-slack/src/slack-adapter.ts
📝 Walkthrough

Walkthrough

Adds a classifyError helper and optional SurfaceAdapter.postError hook to connector-core for structured error reporting, rewrites Slack Block Kit rendering with value masking, action humanization, code-block/error-block helpers, updates the Slack adapter with status/prompts/postError wiring and startup error handling, and adds masked, verb-specific consent text to peek-mcp elicitation.

Changes

Legible errors and consent UX

Layer / File(s) Summary
SurfaceAdapter postError contract
packages/connector-core/src/surface.ts
Adds optional postError?(conversationId, err) method to the SurfaceAdapter interface with {kind, headline, hint} payload.
classifyError and runLoop wiring
packages/connector-core/src/runtime.ts, packages/connector-core/src/runtime.test.ts
Adds classifyError to map caught errors to {kind, headline, hint}, removes fixed ERROR_TEXT, and updates runLoop to call adapter.postError or fall back to postText; adds classification and error-legibility tests.
Block Kit masking, humanization, and consent card rewrite
packages/connector-slack/src/blockkit.ts, packages/connector-slack/src/blockkit.test.ts
Adds maskValue, humanizeAction, actionFields, looksLikeCode, codeBlock, resultBlocks, errorBlock; rewrites consentCard to render humanized/masked details, JSON fallback with truncation, or summary text.
Slack adapter postError, status, and prompts wiring
packages/connector-slack/src/slack-adapter.ts, packages/connector-slack/src/slack-adapter.test.ts, packages/connector-slack/src/index.ts, packages/connector-slack/README.md
Adds suggestedPrompts, thread postStatus helper, postConfirmation/postError implementations, thread-title setting on user messages, MCP-connect startup error handling with exit code 1, and README documentation.
Peek-MCP elicitation masking and consent text
packages/peek-mcp/src/mcp/elicitation.ts, packages/peek-mcp/src/mcp/elicitation.test.ts, .changeset/peek-connector-inslack-consent-text.md
Adds maskValue and an Action-typed buildElicitMessage producing verb-specific, masked consent prompts, replacing the generic run-string message; adds a version changeset.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RunLoop
  participant classifyError
  participant Adapter

  RunLoop->>classifyError: err thrown during turn
  classifyError-->>RunLoop: {kind, headline, hint}
  alt adapter has postError
    RunLoop->>Adapter: postError(conversationId, classified)
  else
    RunLoop->>Adapter: postText(conversationId, headline + hint)
  end
Loading
sequenceDiagram
  participant SlackAdapter
  participant BlockKit
  participant SlackAPI

  SlackAdapter->>SlackAdapter: postStatus(conversationId, "peek is thinking…")
  SlackAdapter->>SlackAPI: assistant.threads.setStatus
  SlackAdapter->>BlockKit: errorBlock(headline, hint)
  BlockKit-->>SlackAdapter: warning + context blocks
  SlackAdapter->>SlackAPI: chat.postMessage(blocks, text=headline)
Loading

Possibly related PRs

  • Cubenest/rrweb-stack#140: Updates the same runtime.ts classifyError/postError wiring and blockkit.ts/slack-adapter.ts consent/error rendering.
  • Cubenest/rrweb-stack#143: Consumes buildElicitMessage/maskValue from elicitation.ts to drive elicitConsent for execute_action.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main connector-slack UX changes: consent cards, assistant affordances, results, and errors.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-feat-connector-inslack-ux

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/connector-slack/src/slack-adapter.ts (1)

165-178: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

setTitle failure silently drops the user's message.

await setTitle(m.text.slice(0, 75)) is not wrapped in error handling. If the Slack API call fails (network, permissions), the promise rejects, emit() is never reached, and the user's message is lost. This is inconsistent with postStatus, which swallows errors for the same reason — both are niceties that must never block the turn.

Proposed fix: wrap setTitle in try/catch
       if (!m.text || !m.channel) return;
-      // Thread title is handler-scoped and safe to set synchronously before emit.
-      await setTitle(m.text.slice(0, 75));
+      // Thread title is a nicety; never block the message on it.
+      try {
+        await setTitle(m.text.slice(0, 75));
+      } catch {
+        // Title is a nicety; a failure must never drop the user's message.
+      }
       const cid = m.thread_ts ?? m.ts;
       this.emit(cid, m.channel, cid, m.user ?? 'unknown', m.text);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-slack/src/slack-adapter.ts` around lines 165 - 178, The
message flow in SlackAdapter’s userMessage handler can be blocked by a failing
setTitle call, causing emit() to never run and the user’s message to be lost.
Update the userMessage path in slack-adapter.ts to wrap await setTitle(...) in
try/catch, matching the non-blocking behavior used by postStatus, and ensure any
title update failure is swallowed so the code always continues to compute cid
and call this.emit(...).
🧹 Nitpick comments (1)
packages/connector-core/src/runtime.test.ts (1)

466-510: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting the fallback text content.

The postText fallback test verifies postText is called with the right conversation ID but doesn't check the composed text (${headline}. ${hint}). Asserting the text contains the classified headline/hint would catch regressions in the fallback formatting.

🧪 Suggested addition
     await vi.waitFor(() => expect(adapter.texts).toHaveLength(1));
     expect(adapter.texts[0]?.[0]).toBe('t1');
+    expect(adapter.texts[0]?.[1]).toContain('Something went wrong reaching peek');
+    expect(adapter.texts[0]?.[1]).toContain('Please try again');
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/connector-core/src/runtime.test.ts` around lines 466 - 510, The
fallback branch in ConnectorRuntime’s error handling is only asserting that
postText is called, but not that it uses the composed headline/hint message.
Update the “falls back to postText when the adapter has no postError” test in
runtime.test.ts to assert the posted text includes the classified error content
(the `${headline}. ${hint}` formatting) so regressions in fallback formatting
are caught. Use the existing ConnectorRuntime, FakeAdapter, and postText
assertions to locate the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/connector-slack/src/blockkit.ts`:
- Around line 189-192: Mixed prose plus code is being routed to resultBlocks as
a code block whenever looksLikeCode sees any ``` marker, which causes nested
fencing in Slack. Update resultBlocks in blockkit.ts to detect fenced code
embedded inside surrounding prose and send those cases to textBlocks instead of
codeBlock, using the existing helpers looksLikeCode, codeBlock, and textBlocks
to keep standalone code unchanged.

---

Outside diff comments:
In `@packages/connector-slack/src/slack-adapter.ts`:
- Around line 165-178: The message flow in SlackAdapter’s userMessage handler
can be blocked by a failing setTitle call, causing emit() to never run and the
user’s message to be lost. Update the userMessage path in slack-adapter.ts to
wrap await setTitle(...) in try/catch, matching the non-blocking behavior used
by postStatus, and ensure any title update failure is swallowed so the code
always continues to compute cid and call this.emit(...).

---

Nitpick comments:
In `@packages/connector-core/src/runtime.test.ts`:
- Around line 466-510: The fallback branch in ConnectorRuntime’s error handling
is only asserting that postText is called, but not that it uses the composed
headline/hint message. Update the “falls back to postText when the adapter has
no postError” test in runtime.test.ts to assert the posted text includes the
classified error content (the `${headline}. ${hint}` formatting) so regressions
in fallback formatting are caught. Use the existing ConnectorRuntime,
FakeAdapter, and postText assertions to locate the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 63bdec0b-2d79-4f21-85b8-c9517b70c2cd

📥 Commits

Reviewing files that changed from the base of the PR and between a8d831f and 1bcfac9.

📒 Files selected for processing (12)
  • .changeset/peek-connector-inslack-consent-text.md
  • packages/connector-core/src/runtime.test.ts
  • packages/connector-core/src/runtime.ts
  • packages/connector-core/src/surface.ts
  • packages/connector-slack/README.md
  • packages/connector-slack/src/blockkit.test.ts
  • packages/connector-slack/src/blockkit.ts
  • packages/connector-slack/src/index.ts
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts
  • packages/peek-mcp/src/mcp/elicitation.test.ts
  • packages/peek-mcp/src/mcp/elicitation.ts

Comment thread packages/connector-slack/src/blockkit.ts Outdated
…ssert fallback text (#150)

- resultBlocks: already-fenced text routes to textBlocks (Slack mrkdwn renders
  ``` natively); only bare Playwright code gets wrapped by codeBlock once.
  Prevents nested-fence rendering bug for mixed prose+code LLM replies.
- slack-adapter userMessage: wrap setTitle in try/catch so a title API failure
  never drops the user's message (emit always fires).
- runtime.test: assert postText fallback contains the unknown-kind headline +
  hint substrings, not just the conversation id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
@harry-harish

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@harry-harish harry-harish merged commit 8947f44 into main Jul 9, 2026
7 checks passed
@harry-harish harry-harish deleted the worktree-feat-connector-inslack-ux branch July 9, 2026 05:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant