Skip to content

feat(web): open called skills in file viewer - #6553

Open
djfbryant wants to merge 5 commits into
pingdotgg:mainfrom
djfbryant:agent/clickable-skill-references
Open

feat(web): open called skills in file viewer#6553
djfbryant wants to merge 5 commits into
pingdotgg:mainfrom
djfbryant:agent/clickable-skill-references

Conversation

@djfbryant

@djfbryant djfbryant commented Aug 14, 2026

Copy link
Copy Markdown

Problem

Resolved skill invocations in submitted chat messages look like chips, but users cannot open them to inspect the instructions the agent used.

Solution

  • Make resolved skill invocations in submitted user messages clickable.
  • Open the current skill file read-only in the existing right-side file viewer.
  • Persist the resolved invocation name and path with new messages.
  • Resolve legacy messages against the current enabled provider skills.
  • Support relative skill documents and images while confining access to the skill directory.
  • Use signed asset URLs so local and remote environments behave consistently.
  • Keep assistant messages, code blocks, unresolved skills, and disabled skills inert.

User impact

Users can inspect an invoked skill directly from the conversation without leaving T3 Code. Linked documents remain within the same read-only file-viewer experience.

Supported surfaces

  • Web
  • Desktop
  • Local and remote environments
  • Providers that expose skill metadata

Mobile is intentionally out of scope for this change.

Verification

  • 143 focused tests passed across shared parsing, orchestration, secure skill-file access, assets, persistence, chat rendering, and right-panel behavior.
  • Contracts, shared, web, and server typechecks passed.
  • Independent specification and code-standards reviews passed after addressing their findings.

Screenshots

Before/after screenshots still need to be added before this draft is marked ready for review.


Implemented with GPT-5.6 in the Codex harness.

Note

Open called skill files in the right panel file viewer from chat message skill chips

  • Adds end-to-end skill file viewing: clicking a $skill chip in a user message opens the skill's files in the right panel file viewer, with skill SKILL.md tabs labelled $<name> instead of the filename.
  • Introduces server-side skill resolution at dispatch time via collectSubmittedSkillNames and normalizeDispatchCommand, storing resolved name/path pairs as resolvedSkills on thread messages through the projection pipeline and DB (resolved_skills_json column, migration 041).
  • Adds a skills.readFile WebSocket RPC that reads files from a resolved skill directory with path-traversal and symlink containment checks, size truncation at 1 MiB, and binary-file rejection.
  • Adds skill-file asset resource type for serving skill-scoped images with Cache-Control: no-store and exact-file claim validation.
  • Extends the right panel store with openSkillFile, a skill-file: surface ID namespace, a storage version bump (v11→v12), and migration of existing persisted skill tabs; skill-backed tabs survive workspace-unavailable reconciliation.
  • Risk: storage version increment triggers migration for all existing users, rewriting any persisted file tabs that carry skill metadata.

Macroscope summarized 05088b2.


Note

Medium Risk
Touches filesystem access, signed asset tokens, and dispatch normalization; mitigations include directory containment, server-only path resolution, and migration 041 before relying on stored skills.

Overview
Clickable skill chips in chat open the invoked skill read-only in the right-panel file viewer (SKILL.md, linked docs, and images).

Server-side, turn dispatch now derives and stores resolvedSkills (name + path) on user messages—clients cannot inject paths—and projects them via migration 041 (resolved_skills_json). skills.readFile and skill-file signed asset URLs serve content only inside the resolved skill directory (traversal/symlink checks, no-store for skill assets). Legacy messages without stored resolution fall back to re-parsing $skill tokens against the thread’s current enabled provider skills.

Web UI adds durable skill-file:* tabs (separate from workspace file tabs), markdown rendering with in-skill navigation, and updated collectSubmittedSkillNames so skill detection ignores code fences when recording invocations.

Reviewed by Cursor Bugbot for commit 05088b2. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4b4bc971-a47e-462d-90e8-2800380d3256

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 14, 2026
Comment thread packages/shared/src/composerInlineTokens.ts Outdated
Comment thread apps/server/src/skill/SkillFileAccess.ts Outdated
Comment thread apps/web/src/rightPanelStore.ts
Comment thread apps/web/src/rightPanelStore.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Effect service conventions review of the new skill file-read path. Three error-modeling issues in the changed scope; everything else (namespace imports, contract schemas, RPC wiring, projection changes) looks consistent with repo conventions.

Posted via Macroscope — Effect Service Conventions

Comment thread packages/contracts/src/skill.ts
Comment thread apps/server/src/skill/SkillFileAccess.ts Outdated
Comment thread apps/server/src/skill/SkillFileAccess.ts
Comment thread apps/server/src/ws.ts
Comment thread packages/shared/src/composerInlineTokens.ts Outdated
@djfbryant
djfbryant force-pushed the agent/clickable-skill-references branch from 9e43366 to 38bb1e1 Compare August 14, 2026 07:58
Comment thread apps/server/src/orchestration/Normalizer.ts Outdated
Comment thread apps/server/src/skill/SkillFileAccess.ts
Comment thread apps/server/src/orchestration/Normalizer.ts Outdated
Comment thread packages/contracts/src/orchestration.ts
@djfbryant
djfbryant force-pushed the agent/clickable-skill-references branch from 38bb1e1 to b5aff1f Compare August 14, 2026 08:03
Comment thread apps/server/src/ws.ts Outdated
Legacy chips in archived threads could not find a provider because the
fallback used an active-only shell query. Skill file reads now follow
WorkspaceFileSystem instead of O_NOFOLLOW and inode checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
Effect.gen(function* () {
const [threadRow, latestTurnRow, sessionRow] = yield* Effect.all([
getActiveThreadRowById({ threadId }).pipe(
(options?.includeArchived === true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium Layers/ProjectionSnapshotQuery.ts:2361

getThreadShellById(threadId, { includeArchived: true }) always returns latestTurn: null for archived threads, even when latest_turn_id points to a turn. The selected getStoredThreadRowById path is paired with getLatestTurnRowByThread, whose threads.archived_at IS NULL filter excludes that row; use an archived-inclusive latest-turn query on this path.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts around line 2361:

`getThreadShellById(threadId, { includeArchived: true })` always returns `latestTurn: null` for archived threads, even when `latest_turn_id` points to a turn. The selected `getStoredThreadRowById` path is paired with `getLatestTurnRowByThread`, whose `threads.archived_at IS NULL` filter excludes that row; use an archived-inclusive latest-turn query on this path.

@djfbryant
djfbryant marked this pull request as ready for review August 14, 2026 22:22

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 05088b2. Configure here.

Comment thread apps/server/src/ws.ts
cause,
}),
),
));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Frozen skills still resolve later

Medium Severity

skills.readFile and assets.createUrl fall back to live provider lookup whenever the requested name is missing from resolvedSkills. New messages always persist that list, including [], specifically so unresolved or disabled invocations stay frozen. A later enablement still serves the skill if the original text mentioned it, which also diverges from the chips the timeline shows.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 05088b2. Configure here.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

UI consistency review found layout and focus-state issues in the new skill preview surfaces. Details inline.

Posted via Macroscope — UI Consistency

Comment on lines +1176 to +1187
<div className="surface-subheader gap-2 px-3" data-surface-subheader>
<div className="min-w-0 flex-1 py-1.5">
<div className="truncate text-xs font-medium text-foreground">
{relativePath === "SKILL.md" ? `$${props.skill.name}` : relativePath}
</div>
<div
className="truncate font-mono text-[10px] text-muted-foreground"
title={props.skill.path}
>
{props.skill.path}
</div>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.surface-subheader is a fixed-height row (h-10 min-h-10), and in inline preview mode [data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] forces it to 28px. Two stacked lines (text-xs + text-[10px]) plus py-1.5 is ~43px, so this header overflows the row and, in inline mode, bleeds past its 12px bottom margin into the file content. Every other .surface-subheader in the app is a single line of controls. Smallest fix: keep the header to one line (skill/file name) and expose the absolute skill path via the existing title attribute or a tooltip instead of a second line; the same applies to the image variant's header.

Posted via Macroscope — UI Consistency

type="button"
className={cn(
CHAT_INLINE_CHIP_CLASS_NAME,
"cursor-pointer border-fuchsia-500/25 bg-fuchsia-500/12 text-fuchsia-700 hover:bg-fuchsia-500/20 dark:text-fuchsia-300",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The clickable chip is a focusable control but has no focus-visible treatment, unlike the other interactive chips (.chat-markdown a.chat-markdown-file-link:focus-visible ring in index.css, and COMPOSER_INLINE_CHIP_DISMISS_BUTTON_CLASS_NAME's focus-visible:ring-1 focus-visible:ring-ring). Consider adding the same ring so keyboard focus matches the chip contract.

Suggested change
"cursor-pointer border-fuchsia-500/25 bg-fuchsia-500/12 text-fuchsia-700 hover:bg-fuchsia-500/20 dark:text-fuchsia-300",
"cursor-pointer border-fuchsia-500/25 bg-fuchsia-500/12 text-fuchsia-700 hover:bg-fuchsia-500/20 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring dark:text-fuchsia-300",

Posted via Macroscope — UI Consistency

Comment on lines +1270 to +1278
<div className="surface-subheader min-w-0 px-3 py-1.5" data-surface-subheader>
<div className="truncate text-xs font-medium text-foreground">{relativePath}</div>
<div
className="truncate font-mono text-[10px] text-muted-foreground"
title={props.skill.path}
>
{props.skill.path}
</div>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

.surface-subheader is flex ... items-center, so these two children become row items instead of the stacked title/path used by SkillTextFilePreviewPanel, and without min-w-0 on a flex child the truncate on the absolute path cannot shrink. Consider wrapping them in the same min-w-0 flex-1 column the text variant uses.

Suggested change
<div className="surface-subheader min-w-0 px-3 py-1.5" data-surface-subheader>
<div className="truncate text-xs font-medium text-foreground">{relativePath}</div>
<div
className="truncate font-mono text-[10px] text-muted-foreground"
title={props.skill.path}
>
{props.skill.path}
</div>
</div>
<div className="surface-subheader gap-2 px-3" data-surface-subheader>
<div className="min-w-0 flex-1 py-1.5">
<div className="truncate text-xs font-medium text-foreground">{relativePath}</div>
<div
className="truncate font-mono text-[10px] text-muted-foreground"
title={props.skill.path}
>
{props.skill.path}
</div>
</div>
</div>

Posted via Macroscope — UI Consistency

@macroscopeapp

macroscopeapp Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a new user-facing feature (clickable skill references opening in file viewer) with a database migration, new RPC endpoint, new file access layer, and significant UI changes across multiple components. New features of this scope require human review.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant