Skip to content

feat: add current page context to agent system prompt#342

Merged
itayadler merged 3 commits intomainfrom
issue-339
Feb 15, 2026
Merged

feat: add current page context to agent system prompt#342
itayadler merged 3 commits intomainfrom
issue-339

Conversation

@itayadler
Copy link
Copy Markdown
Collaborator

@itayadler itayadler commented Feb 15, 2026

Summary

Closes #339

Adds implicit "current page" context that collects browser/page metadata from the preview iframe and passes it to the AI agent's system prompt. Unlike selected component (which requires user action), this is always present — giving the agent awareness of the user's browsing context.

What's included

  • Client: Collects page URL, viewport dimensions, device pixel ratio, page title, color scheme preference, and scroll position from the preview iframe at prompt-send time. Sent as an ACP resource content block with _meta: { current_page: true } marker.
  • Server: Extracts current_page from content blocks, appends a [Current Page Context] section to LLM user messages, and adds current_page_guidance to the system prompt explaining how to use the context.

Key changes

File Change
libs/client/src/state/Client__Task__Types.res currentPageToContentBlock builder, included in taskToContentBlocks for all task states
apps/frontman_server/lib/frontman_server/tasks/interaction.ex extract_current_page/1, append_current_page_context/2, has_current_page?/1
apps/frontman_server/lib/frontman_server/agents/prompts.ex current_page_guidance/0 system prompt section
apps/frontman_server/lib/frontman_server/agents/root_agent.ex has_current_page field
apps/frontman_server/lib/frontman_server/agents.ex Passes has_current_page to RootAgent.new
apps/frontman_server/lib/frontman_server/tasks.ex has_current_page?/2 delegation

How it works

  1. Client reads from previewFrame.contentWindow / contentDocument at prompt time
  2. Data is sent as ACP resource block: { type: "resource", resource: { _meta: { current_page: true, url, viewport_width, ... } } }
  3. Server extracts into UserMessage.current_page struct
  4. [Current Page Context] appended to user message text before LLM call
  5. System prompt includes guidance on how to use viewport, URL, DPR, etc.

Open with Devin

Collect page metadata (URL, viewport, DPR, title, color scheme, scroll)
from the preview iframe and send as ACP content block with every prompt.
Server extracts this data and appends [Current Page Context] to user
messages, plus adds system prompt guidance when page context is present.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 15, 2026

File Coverage Missing
All files 64%
src/FrontmanNextjs.res.mjs 0% 1-35
src/FrontmanNextjs__CircularBuffer.res.mjs 100%
src/FrontmanNextjs__Config.res.mjs 0% 1-65
src/FrontmanNextjs__Instrumentation.res.mjs 0% 1-26
src/FrontmanNextjs__LogCapture.res.mjs 78% 68-69 86-87 96-100 132-133 140-141 148-149 152-166 188-189 192-193 197-208 214-216 223-238 241-250 257-258 282-285
src/FrontmanNextjs__LogRecordProcessor.res.mjs 85% 21-24 35-36
src/FrontmanNextjs__Middleware.res.mjs 0% 1-139
src/FrontmanNextjs__OpenTelemetry.res.mjs 100%
src/FrontmanNextjs__OpenTelemetry__Bindings.res.mjs 0% 1-12
src/FrontmanNextjs__Sentry.res.mjs 81% 33-40 48-49
src/FrontmanNextjs__Sentry__Bindings.res.mjs 0% 1-2
src/FrontmanNextjs__Server.res.mjs 0% 1-224
src/FrontmanNextjs__SpanProcessor.res.mjs 88% 86-89 92-96 99-100 139-140
src/FrontmanNextjs__ToolRegistry.res.mjs 100%
src/cli/FrontmanNextjs__Cli.res.mjs 0% 1-180
src/cli/FrontmanNextjs__Cli__AutoEdit.res.mjs 69% 35-45 47-59 107-108 177-189 198-217 220 222 238-242 250-254
src/cli/FrontmanNextjs__Cli__Detect.res.mjs 61% 45-46 48-49 54-55 57-58 68-69 81 83 85 87 89 96-97 100-101 104-105 108-109 112 132-136 144-148 162-166 200-201 204-217 224 226 228 230 234-275
src/cli/FrontmanNextjs__Cli__Files.res.mjs 59% 21-22 33-37 42-50 61-92 133-137 141-148 150-159 162-166 180-184 197-204 216-220 224-231 233-242 245-249 263-267 276-283 313-317 327 331 335-337
src/cli/FrontmanNextjs__Cli__Install.res.mjs 73% 12-46 60-66 107-112 127-131 135-139 163-169
src/cli/FrontmanNextjs__Cli__Style.res.mjs 100%
src/cli/FrontmanNextjs__Cli__Templates.res.mjs 87% 194-272 284-286 292-298
src/tools/FrontmanNextjs__Tool__GetLogs.res.mjs 79% 25-52
src/tools/FrontmanNextjs__Tool__GetRoutes.res.mjs 63% 22-90

Minimum allowed coverage is 70%

Generated by 🐒 cobertura-action against 1244c62

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +783 to +791
let (viewportWidth, viewportHeight, dpr, scrollY) = switch previewFrame.contentWindow {
| Some(win) => (
Some(win.innerWidth),
Some(win.innerHeight),
Some(win.devicePixelRatio),
Some(win.scrollY->Float.toInt),
)
| None => (None, None, None, None)
}
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.

🔴 Unprotected cross-origin iframe window property access in currentPageToContentBlock can crash prompt-send

Accessing win.innerWidth, win.innerHeight, win.devicePixelRatio, and win.scrollY at lines 785-788 has no try/catch protection, unlike the getColorScheme helper (line 769-773) which wraps its win.matchMedia call in try/catch.

Root Cause: stale WindowProxy becomes cross-origin after iframe navigation

The contentWindow stored in state is a browser WindowProxy. When the preview iframe navigates from a same-origin page to a cross-origin page:

  1. Initial same-origin load: setPreviewFrame at Client__WebPreview__Body.res:35-37 successfully captures contentWindow: Some(win).
  2. Iframe navigates to a cross-origin URL: the onLoad handler's try/catch at Client__WebPreview__Body.res:34-41 catches the SecurityError from contentDocument access and silently does nothing — crucially, it does not reset contentWindow to None.
  3. The state still holds contentWindow: Some(win), but win is now a WindowProxy pointing at the cross-origin browsing context.
  4. User sends a prompt → taskToContentBlockscurrentPageToContentBlock(previewFrame) → matches Some(win) → accesses win.innerWidththrows SecurityError.

This unhandled exception crashes the prompt-send flow, preventing the user from interacting with the agent.

Impact: Any user whose preview iframe navigates cross-origin (e.g., OAuth redirects, external links) will be unable to send prompts until they reload the page.

Prompt for agents
In libs/client/src/state/Client__Task__Types.res, wrap the window property access block (lines 783-791) in a try/catch, similar to how getColorScheme handles potential SecurityError. When the catch fires, fall back to (None, None, None, None). The code should look like:

  let (viewportWidth, viewportHeight, dpr, scrollY) = switch previewFrame.contentWindow {
  | Some(win) =>
    try {
      (
        Some(win.innerWidth),
        Some(win.innerHeight),
        Some(win.devicePixelRatio),
        Some(win.scrollY->Float.toInt),
      )
    } catch {
    | _ => (None, None, None, None)
    }
  | None => (None, None, None, None)
  }

Also wrap the contentDocument access for title (lines 794-802) in a similar try/catch, and consider resetting contentWindow/contentDocument to None in the WebPreview onLoad catch handler at Client__WebPreview__Body.res:38-41 so stale cross-origin references don't persist in state.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

The current_page field was stored in the DB but never deserialized back
into UserMessage when loading interactions from the database. This caused
append_current_page_context to receive nil instead of the page data,
so the [Current Page Context] section was never appended to LLM messages.
The raw JS function returns null when no chip element is found, but
the ReScript type was option<{..}> which expects undefined for None.
This caused null to be wrapped as Some(null), leading to a TypeError
when calling getAttribute on null. Changed type to Nullable.t and
convert with Nullable.toOption.
@itayadler itayadler merged commit 023e9a4 into main Feb 15, 2026
15 of 16 checks passed
@itayadler itayadler deleted the issue-339 branch February 15, 2026 13:36
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.

Add current page context to agent system prompt

1 participant