fix(ai): tighter agent timeline — thread the rail, fold MCP approval into its run-node - #61
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refines the LevelCode AI webview’s agent activity timeline so MCP activity reads as a single, continuous “thread” (rail continuity + one-row-per-approved-MCP-call), and adds CSS/JS invariants to prevent regressions.
Changes:
- Extend the timeline rail across
#log’s flexgapbetween consecutive top-level timeline rows. - Fold an approved MCP tool call’s run-node into the approval row (so it renders as one timeline row instead of two).
- Add regression tests for the rail-gap offset sync and the MCP folding handshake; add a new design doc for chat sessions.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| extensions/levelcode-ai/media/chat.html | Adds the rail-bridging CSS rule; introduces mcpMergePending and folds approved MCP run-nodes into the approval row. |
| extensions/levelcode-ai/agent.js | Tags MCP tool run-node posts with kind: 'mcp' so the webview can detect and fold them. |
| extensions/levelcode-ai/test/webviewCss.test.js | Adds invariants to keep the rail gap/offset in sync and ensure the MCP fold wiring remains intact. |
| docs/levelcode-chat-sessions-design.md | Adds a proposed design document for persistent chat sessions (not referenced in the PR description). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1
to
+5
| # Chat Sessions — design | ||
|
|
||
| **Status:** proposed · **Scope:** `extensions/levelcode-ai` (chat + agent) · **Depends on:** nothing new; reuses the M5 compaction machinery | ||
|
|
||
| The problem, in one sentence: **"New Chat" is destructive.** `conversation` and |
…into its run-node
Two timeline-rendering fixes, most visible on an MCP run:
1. Thread the rail across #log's 12px flex-gap between consecutive top-level
rows (#log > .tl + .tl > .tl-rail::before { top: -12px }). The per-row rail
left a 12px void between EVERY row, so a run read as disconnected stubs
('cut in the middle'); it now reads as one connected line, breaking only at
narration (intended). Scoped to #log's direct children so grouped rows keep
their own rail.
2. Fold an approved MCP tool call into ONE row instead of an 'Approved · server
· tool' chip PLUS a separate '🔌 server · tool' node. agent.js tags the
run-node kind:'mcp'; addMcpApproval hands its row to that node only when
approved; addAgentLine folds it in. Allow-listed calls were already one row;
skipped calls keep their chip.
Two new webviewCss invariants (rail-bridge == #log gap; the fold handshake).
Full extension suite: 27 suites, 0 failures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ndemianc
force-pushed
the
fix/agent-timeline-rail-and-mcp-fold
branch
from
July 29, 2026 01:15
d72508f to
54ccaa5
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
extensions/levelcode-ai/media/chat.html:1876
- The MCP fold can incorrectly consume an
agentToolrow ifmcpMergePendingpoints at a detached element (e.g. the user triggers “New chat” / the host posts{type:'reset'}after approval but before the run-node arrives). In that caseaddAgentLinereturns early after mutating a node that’s no longer in#log, so the tool row never renders. Guard the fold withlog.contains(mcpMergePending)so a stale ref falls through to normal rendering and gets cleared.
if (kind === 'mcp' && mcpMergePending){
mcpMergePending.className = 'tl tl-tool';
mcpMergePending.innerHTML = rowHtml;
mcpMergePending = null;
scrollIfStuck();
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two changes that make the agent activity timeline read as one clean thread — most visible on an MCP run, which is what prompted this.
1. Thread the rail across the row gap
The timeline rail is drawn per-row (
.tl-rail::beforespans one row), but#logstacks rows with agap: 12pxflex gap — so there was 12px of dead space between every row and the rail rendered as disconnected stubs ("cut in the middle"). Now a row that directly follows another timeline row extends its rail up to meet it:A run of tool / approval / group nodes reads as one connected line (GitHub / Cursor / Claude style); a narration bubble in between still breaks it (intended). Scoped to
#log's direct children, so rows nested inside a collapsed group keep their own contiguous rail.2. Fold an approved MCP call into one row
A manually-approved MCP tool call rendered two rows — an
Approved · server · toolchip and a separate🔌 server · toolnode. The run-node now folds into the approval chip so each call is one row (Copilot-style, one-row-per-action):agent.jstags the run-nodekind: 'mcp'.addMcpApprovalhands its row to the incoming node only when approved.addAgentLinefolds akind==='mcp'node into that pending row, and clears the window otherwise.Edge cases preserved: allow-listed calls were already one row (no card); skipped calls keep their
Skippedchip (nothing ran); only the approved / always-allowed pair folds. The folded row shows🔌 server · tool(matching allow-listed calls) rather than the approval verb — the persistent allow-list effect lives in settings.Tests
webviewCssstatic invariants: the rail-bridge offset stays equal to#log's gap (no drift), and the MCP fold's three-part handshake stays wired end to end (agent.js→addMcpApproval→addAgentLine).agent.js+ the chat.html webview<script>); full extension suite 27 suites, 0 failures.🤖 Generated with Claude Code