Skip to content

feat(peek): rebuild the session journey in Slack (canvas + Block Kit fallback)#159

Merged
harry-harish merged 9 commits into
mainfrom
feat/connector-session-journey
Jul 10, 2026
Merged

feat(peek): rebuild the session journey in Slack (canvas + Block Kit fallback)#159
harry-harish merged 9 commits into
mainfrom
feat/connector-session-journey

Conversation

@harry-harish

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

Copy link
Copy Markdown
Member

What

@peek rebuild the journey → the connector reconstructs the failing session's journey (the ordered path of actions → the failure, plus DOM/network context and the narrative) and renders it as a rich, shared Slack canvas (Block Kit fallback), posting a clickable permalink into the thread.

This is the connector's shared-debugging thesis in one surface: today "what failed?" returns a one-line text answer; this puts the whole path in front of the team where they already talk.

Key de-risk

peek already assembles the journey server-sidebuildCausalChain(...) returns { timeline, narrative, error, ... }. This is primarily a rendering feature, not new data assembly. It reuses the exact get_user_action_before_error data path.

How (mirrors the shipped share_session pattern)

  1. New render_session_journey(sessionId, errorId?) MCP tool (@peekdev/mcp) — a dedicated read tool that reuses buildCausalChain and auto-selects the session's latest console error when errorId is omitted. Dedicated (not get_user_action_before_error) so the connector can intercept only journey results for canvas rendering.
  2. SurfaceAdapter.renderJourney? (connector-core) — the runtime intercepts an approved render_session_journey result at the callTool boundary (interceptCallTool — the same seam share_session uses) and returns a short confirmation (the canvas link) to the brain, keeping the full timeline out of LLM context.
  3. connector-slack renderJourneyCausalChain → canvas markdownconversations.canvases.create({ channel_id }) (channel-linked) → files.info → post a clickable <permalink|Session journey>. On any canvas failure → Block Kit fallback (truncated timeline + narrative, capped at Slack's 50-block limit). Never posts a bare/dead canvas_id.

Egress / honesty

A journey canvas sends more derived session content (timeline, selectors, error tables) to Slack than a one-line text answer — a real widening of what leaves the local-first store, but not a raw bundle upload (that's share_session). The explicit @peek rebuild the journey trigger is the consent (user-initiated); no separate card in v1. THREATMODEL egress row #10 + a dedicated section document it plainly, including the share_session-vs-journey comparison and residual limits.

Review

Built subagent-driven (4 tasks, per-task spec+quality review). Whole-branch review (opus) verified 7 cross-task invariants: real inline read-path interception (not a dead branch), brain gets confirmation not raw JSON, canvas is channel-linked + clickable + never-dead-id, read tool has no consent/audit coupling, no behavior change to other tools, contract consistency, changeset + THREATMODEL. Verdict: ready. Two review Minors fixed pre-PR (9259ba2): null-status net rows rendering literal "null"; guard error.level.

Testing

  • peek-mcp: 464 pass (new render-session-journey.test.ts — CausalChain shape, auto-select, no-error/no-DB paths; tool-count snapshots updated).
  • connector-core: 124 pass (intercept → renderJourney → confirmation-not-JSON; degrade without adapter; passthrough).
  • connector-slack: 99 pass (canvas path, all fallback paths, never-dead-id, truncation caps, type guard, null-status regression).
  • pnpm -r typecheck clean; build OK.

Maintainer (out of code)

Add canvases:write (+ existing chat:write) to the Slack app and reinstall to make the canvas path live; without it, it degrades to the Block Kit fallback.

Changeset

One @peekdev/mcp minor (render_session_journey). connector-core/connector-slack are private:true → no changeset.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added the render_session_journey tool to reconstruct a session’s timeline, narrative, errors, actions, DOM changes, and network failures.
    • Automatically uses the latest console error when no error is specified.
    • Renders journey summaries in Slack canvases, with a Block Kit fallback when canvases are unavailable.
    • Added graceful handling for missing sessions, errors, or unsupported rendering surfaces.
  • Documentation

    • Documented the journey rendering data flow, Slack delivery, consent, and security considerations.

harry-harish and others added 8 commits July 10, 2026 10:56
…in for journey rendering

New read tool `render_session_journey({ sessionId, errorId? })` reuses the exact same
data path as `get_user_action_before_error` (getDb → eventsFor → userActionsBeforeError →
extractDomMutationsInWindow → getNetworkErrorsInWindow → buildCausalChain) and returns the
full CausalChain JSON. When errorId is omitted the session's latest console error is selected
automatically. When the session has no console errors a clear text message is returned (no
throw). Tool count bumped 20→21 in PEEK_MCP_TOOLS, stdio-smoke and server.test.ts.

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

Add optional SurfaceAdapter.renderJourney?(conversationId, journey): Promise<string>
and extend interceptCallTool to intercept render_session_journey results — valid
CausalChain JSON (has timeline + narrative) is handed to the adapter; its confirmation
string is returned to the brain instead of the full timeline, keeping LLM context lean.
Adapters without renderJourney degrade to a brief text note (no throw). share_session
handling is unchanged. All 122 tests pass; strict TS build clean.

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

Fix 1: remove unused feedbackText variable and tautological assertion from
test (a) of the render_session_journey suite — the deferred approach was
replaced by (a-confirm); (a) now cleanly asserts only that adapter.renderJourney
was called with (conversationId, journey).

Fix 2: add test (e) — plain-text render_session_journey result (e.g.
"no console errors found") passes through interceptCallTool unchanged,
adapter.renderJourney NOT called, no throw. Driven through the real
SdkBrain+interceptCallTool path.

Fix 3: add test (f) — no active conversationId at interception time:
interceptCallTool called directly (outside runLoop, #activeConversationId
undefined) with a valid CausalChain JSON result → returns the brief degrade
note, renderJourney NOT called, no throw.

Production code (runtime.ts, surface.ts) byte-for-byte unchanged.
124 tests pass (was 122).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: harry-harish <22562634+harry-harish@users.noreply.github.com>
… Block Kit fallback

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

- Replace canvases.create with conversations.canvases.create({ channel_id, title,
  document_content }) so the canvas is channel-linked and visible to members
- Fetch clickable permalink via files.info({ file: canvas_id }) after canvas creation
- Post mrkdwn link <permalink|Session journey> instead of bare canvas ID
- Add console.warn for canvas unavailable + files.info failure (observable errors)
- Fix isJourneyCausalChain to check typeof error.message === 'string'
- Fix misleading MAX_BK_TIMELINE_ENTRIES comment (1 block/entry × 12 + ~4 fixed)
- Update tests: renamed helper to conversationsCanvasesCreate + filesInfo mock,
  assert channel_id arg + files.info call + mrkdwn link in text; new test for
  canvas_id:undefined → Block Kit fallback; fallback tests use new mock structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Harish Kumar <22562634+harry-harish@users.noreply.github.com>
…k when no permalink

Signed-off-by: Harish Kumar <22562634+harry-harish@users.noreply.github.com>
Add attack-surface #10 and a full egress section for the
render_session_journey canvas render path: user-initiated @peek mention
as consent, derived CausalChain (not raw bundle) to a channel-linked
Slack canvas (canvases:write) with Block Kit fallback, honest residual
limits (Slack retention, channel visibility, capture-time masking).
Verify @peekdev/mcp minor changeset render-session-journey-tool.md
is correct; no connector changeset needed (private packages).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Harish Kumar <22562634+harry-harish@users.noreply.github.com>
Whole-branch review minors, fixed pre-PR:
- N1: a net-level failure serializes as JSON status:null (peek-mcp emits
  number|null); the fallback table rendered the literal "null" instead of
  errorText. Use `!= null` and widen the local type to number|null.
- N2: isJourneyCausalChain now validates error.level (dereferenced via
  .toUpperCase() by both renderers), not just error.message, keeping
  renderJourney total on malformed input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Harish Kumar <22562634+harry-harish@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 10, 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: 37 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: ea6cbb34-5521-4968-a62a-9425148b0dc3

📥 Commits

Reviewing files that changed from the base of the PR and between 9259ba2 and 8f66aac.

📒 Files selected for processing (9)
  • packages/connector-core/src/runtime.test.ts
  • packages/connector-core/src/runtime.ts
  • packages/connector-slack/src/journey.test.ts
  • packages/connector-slack/src/journey.ts
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts
  • packages/peek-mcp/src/mcp/queries.ts
  • packages/peek-mcp/src/mcp/server.ts
  • packages/peek-mcp/test/render-session-journey.test.ts
📝 Walkthrough

Walkthrough

Changes

Adds render_session_journey to the MCP server, reconstructs session CausalChain data, intercepts results in connector runtime, and renders journeys in Slack canvases with Block Kit fallback support. Tests cover selection, formatting, interception, degradation, and delivery paths.

Session journey rendering

Layer / File(s) Summary
MCP journey tool
.changeset/render-session-journey-tool.md, packages/peek-mcp/src/mcp/server.ts, packages/peek-mcp/test/*
Registers the new tool, selects explicit or latest console errors, builds causal chains, handles missing data, and updates tool-list expectations.
Runtime journey interception
packages/connector-core/src/surface.ts, packages/connector-core/src/runtime.ts, packages/connector-core/src/runtime.test.ts
Routes valid journey results to surface adapters, returns renderer confirmations, preserves unrelated results, and degrades safely when rendering is unavailable.
Slack journey formatting
packages/connector-slack/src/journey.ts, packages/connector-slack/src/journey.test.ts
Adds journey validation plus Markdown and Block Kit rendering with emoji mapping, stack traces, network failures, and truncation limits.
Slack journey delivery
packages/connector-slack/src/slack-adapter.ts, packages/connector-slack/src/slack-adapter.test.ts, docs/peek/THREATMODEL.md
Creates canvases, resolves permalinks, posts thread confirmations, falls back to Block Kit, and documents the new egress path.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPServer
  participant ConnectorRuntime
  participant SlackAdapter
  participant SlackCanvas
  participant SlackThread
  MCPServer->>ConnectorRuntime: return CausalChain tool result
  ConnectorRuntime->>SlackAdapter: renderJourney(conversationId, journey)
  SlackAdapter->>SlackCanvas: create journey markdown canvas
  SlackCanvas-->>SlackAdapter: canvas id and permalink
  SlackAdapter->>SlackThread: post clickable canvas confirmation
  SlackAdapter-->>ConnectorRuntime: return confirmation text
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% 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 accurately summarizes the main change: rebuilding and rendering session journeys in Slack with a canvas path and Block Kit fallback.
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 feat/connector-session-journey

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: 6

🤖 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-core/src/runtime.ts`:
- Around line 236-250: Strengthen `#postProcessRenderJourney` so malformed journey
payloads cannot cause adapter.renderJourney to throw. Wrap the
adapter.renderJourney(conversationId, parsed) call in try/catch, and return the
existing “Session journey is ready, but this surface cannot render it.” fallback
when rendering fails, preserving the no-throw and no-data-leak behavior
documented for this method.

In `@packages/connector-slack/src/journey.ts`:
- Around line 279-294: Strengthen isJourneyCausalChain so malformed timeline and
networkErrors entries cannot reach journeyMarkdown or journeyBlocks and crash
cap(). Either deep-validate every entry’s required fields, including
timeline.summary and networkErrors.url, or make cap() safely handle non-string
values while preserving valid rendering; ensure renderJourney remains total for
malformed input.
- Around line 222-227: Reserve space for the uppercase error-level prefix when
truncating the headline in the header construction near `headline` and
`blocks.push`. Compute the prefix first, cap the error message to the remaining
characters so the combined `${prefix}: ${headline}` stays within Slack’s
150-character limit, and use that bounded value in the header.

In `@packages/connector-slack/src/slack-adapter.ts`:
- Around line 226-253: Guard both result-delivery chat.postMessage calls in
renderJourney: wrap the canvas confirmation post and the Block Kit fallback post
in try/catch, log delivery failures, and return a clean fallback/status string
instead of propagating Slack errors. Preserve the existing canvas-to-summary
degradation flow and ensure failures in the final fallback also resolve without
throwing.

In `@packages/peek-mcp/src/mcp/server.ts`:
- Around line 599-608: Replace the `getConsoleErrors` call in the session error
auto-selection branch with a dedicated descending query that orders console
errors by timestamp and ID, limits to one row, and selects the true latest error
before building the journey. Preserve the no-error response behavior, and add a
regression test covering sessions with more than 200 console errors.

In `@packages/peek-mcp/test/render-session-journey.test.ts`:
- Around line 229-243: Update the test case in “render_session_journey: unknown
session / no DB” to assert the specific not-found response rather than only
checking that the unknown session ID appears. Use an assertion that
distinguishes the handler’s unknown-session message from the
empty-session/no-console-errors response.
🪄 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: 07126359-31ab-43af-9bd8-226733ee300e

📥 Commits

Reviewing files that changed from the base of the PR and between 8317cd5 and 9259ba2.

📒 Files selected for processing (13)
  • .changeset/render-session-journey-tool.md
  • docs/peek/THREATMODEL.md
  • packages/connector-core/src/runtime.test.ts
  • packages/connector-core/src/runtime.ts
  • packages/connector-core/src/surface.ts
  • packages/connector-slack/src/journey.test.ts
  • packages/connector-slack/src/journey.ts
  • packages/connector-slack/src/slack-adapter.test.ts
  • packages/connector-slack/src/slack-adapter.ts
  • packages/peek-mcp/src/mcp/server.ts
  • packages/peek-mcp/test/render-session-journey.test.ts
  • packages/peek-mcp/test/server.test.ts
  • packages/peek-mcp/test/stdio-smoke.test.ts

Comment thread packages/connector-core/src/runtime.ts
Comment thread packages/connector-slack/src/journey.ts Outdated
Comment thread packages/connector-slack/src/journey.ts
Comment thread packages/connector-slack/src/slack-adapter.ts Outdated
Comment thread packages/peek-mcp/src/mcp/server.ts Outdated
Comment thread packages/peek-mcp/test/render-session-journey.test.ts
…, total renderers, header budget

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

Copy link
Copy Markdown
Member Author

Thanks — all 6 addressed in 8f66aac (with tests):

  1. runtime.ts#postProcessRenderJourney now wraps adapter.renderJourney in try/catch and returns the existing degrade note on failure (no throw, no data leak — the note doesn't echo the payload). Regression test added.
  2. journey.ts isJourneyCausalChain — now deep-validates: timeline entries must have string kind/summary + numeric relMs/ts, and networkErrors must be an array of rows with string method/url. Malformed payloads are rejected before any cap() call, keeping renderJourney total. Tests added for non-string summary, non-array networkErrors, and missing url.
  3. journey.ts journeyBlocks header — reserves the level-prefix budget: cap(error.message, 150 - prefix.length - 1) (the -1 accounts for cap()'s trailing "…"), so the combined LEVEL: message header is exactly ≤150 chars. Test asserts <= 150 on a 300-char message.
  4. slack-adapter.ts renderJourney — both delivery chat.postMessage calls are now guarded: a failed canvas-confirmation post logs and falls through to the Block Kit fallback; a failed fallback post logs and returns a clean status string. renderJourney no longer propagates Slack errors. Tests cover both failure paths.
  5. server.ts auto-select — replaced getConsoleErrors({limit:200}) + last-element with a dedicated getLatestConsoleError query (ORDER BY ts_ms DESC, id DESC LIMIT 1), so the true latest error is selected even with >200 console errors. Removed the now-unneeded NonNullable cast. Regression test seeds 205 errors and asserts the latest is anchored.
  6. render-session-journey.test.ts — the unknown-session test now asserts the specific message (has no console errors to anchor a journey) + that it's plain text, not just the id substring.

One note on #6: peek-mcp has no session-existence concept — an unknown sessionId and an existing-but-empty session intentionally return the same "no console errors to anchor a journey" message, consistent with how every other tool in server.ts treats unknown sessions. So the fix tightens the assertion to the real shared message rather than introducing a distinct not-found path that would diverge from the rest of the tool surface.

All suites green: peek-mcp 465, connector-core 125, connector-slack 108; pnpm -r typecheck and the connector-slack build both clean.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr diff 159 --repo Cubenest/rrweb-stack > /tmp/pr159.diff
wc -l /tmp/pr159.diff
echo "---commit list---"
gh pr view 159 --repo Cubenest/rrweb-stack --json commits --jq '.commits[].oid' | tail -5

Length of output: 610


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== runtime.ts postProcessRenderJourney ==="
rg -n "postProcessRenderJourney" -A30 packages/connector-core/src/runtime.ts | head -80

echo "=== journey.ts isJourneyCausalChain ==="
rg -n "isJourneyCausalChain" -A40 packages/connector-slack/src/journey.ts | head -80

echo "=== journey.ts journeyBlocks header cap ==="
rg -n "cap\(error.message" -B5 -A5 packages/connector-slack/src/journey.ts

echo "=== slack-adapter.ts renderJourney ==="
rg -n "renderJourney" -A80 packages/connector-slack/src/slack-adapter.ts | head -150

echo "=== server.ts getLatestConsoleError ==="
rg -n "getLatestConsoleError|render_session_journey" -B3 -A40 packages/peek-mcp/src/mcp/server.ts | head -150

Length of output: 20888


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== isTimelineEntry / isNetworkError ==="
rg -n "function isTimelineEntry|function isNetworkError" -A15 packages/connector-slack/src/journey.ts

echo "=== getLatestConsoleError query ==="
rg -n "getLatestConsoleError" -B3 -A20 packages/peek-mcp/src/mcp/queries.ts

Length of output: 2584


All six fixes check out against 8f66aac:

  1. runtime.ts#postProcessRenderJourney (Line 245-252) now wraps adapter.renderJourney in try/catch and returns a generic degrade note on failure, never echoing parsed/text. Confirmed.
  2. journey.ts isJourneyCausalChain — now deep-validates timeline via isTimelineEntry (kind/summary as string, relMs/ts as number) and networkErrors via isNetworkError (method/url as string) before returning true. Confirmed.
  3. journey.ts journeyBlocks headercap(error.message, Math.max(0, 150 - prefix.length - 1)) correctly reserves the level-prefix budget plus the cap() ellipsis char, guaranteeing ${prefix}${headline} stays ≤150. Confirmed.
  4. slack-adapter.ts renderJourney — both the canvas-confirmation chat.postMessage and the Block Kit fallback chat.postMessage are now in try/catch with console.warn + graceful fallthrough/return, so no Slack error propagates out of renderJourney. Confirmed.
  5. server.ts / queries.ts — new getLatestConsoleError does a direct ORDER BY ts_ms DESC, id DESC LIMIT 1 query, correctly avoiding the >200-row truncation issue from the old getConsoleErrors({limit:200}) approach. Confirmed.
  6. render-session-journey.test.ts — noted; the shared "no console errors to anchor" message for unknown vs. empty sessions is consistent with the rest of the tool surface, that's a reasonable design choice, not a bug.

Nice work — all fixes are solid, well-guarded, and consistent with the existing "never throw into the tool loop" pattern used elsewhere in runtime.ts. No further concerns from me on this round.

ヽ(•‿•)ノ

[resolve]
[approve]

@harry-harish harry-harish merged commit 99d7e1e into main Jul 10, 2026
8 checks passed
@harry-harish harry-harish deleted the feat/connector-session-journey branch July 10, 2026 07:21
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