Imported from bbuugg/ai-page-assist (
AGENTS.md). Install upstream withnpx skills add bbuugg/ai-page-assist. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Project Overview
AI Page Assist is a Chrome MV3 extension. It embeds an AI assistant (Anthropic / OpenAI-compatible / Ollama) in a side-panel iframe. The AI has access to a set of browser tools for inspecting and manipulating the current page.
Commands
npm run build # Production build → plugin/
npm run dev # Watch mode (incremental rebuild)
No test runner or linter is configured.
To load the extension in Chrome: open chrome://extensions/ → Enable Developer Mode → Load unpacked → select the project root (where manifest.json lives).
Architecture
This is a Chrome Extension MV3. The extension has four independent entry points compiled by Vite into plugin/:
| Entry | Output | Role |
|---|---|---|
src/background/background.ts |
plugin/background.js |
Service Worker — handles chrome.debugger API to extract element HTML/CSS via CDP |
src/content/content.ts |
plugin/content.js |
Content script — DOM highlight on hover/click, creates a hidden <iframe> that hosts the React UI |
src/content/content.css |
plugin/content.css |
Injected into host page — only the .ai-extension-highlight-element outline style |
overlay.html + src/overlay/ |
plugin/overlay.html + plugin/assets/* |
React 18 + Tailwind app rendered inside the iframe (side panel) |
preview.html + src/preview/ |
plugin/preview.html + plugin/assets/* |
React app for HTML preview — independent Chrome tab, left code editor + right iframe render |
Communication flow
Host page
└── content.ts ──postMessage──▶ overlay iframe (React App)
◀──postMessage──
│
└── chrome.runtime.sendMessage ──▶ background.ts
◀── (response)
content → overlay message types: ELEMENT_DATA, LOADING, ERROR, SYSTEM_MSG, EDITING_CHANGED, SELECTING_CHANGED, TOOL_*_RESULT
overlay → content command types: TOGGLE_SELECT, TOGGLE_EDIT, CAPTURE_FULL, HIGHLIGHT_NODE, CLOSE, TOOL_GET_ELEMENT_HTML, TOOL_GET_ELEMENT_CSS, TOOL_GET_FULL_PAGE_HTML, TOOL_HIGHLIGHT_ELEMENT, TOOL_EXECUTE_JS, TOOL_SCREENSHOT
content → background: inspectElement (x, y) → { html, css, backendNodeId } via CDP; screenshot → { dataUrl } via captureVisibleTab
Key build constraints
base: './'invite.config.tsis required so asset URLs indist/overlay.htmlare relative (Chrome extensions cannot use absolute/paths).background.jsandcontent.jsmust land atdist/root — enforced viaentryFileNamesin rollup output options.content.cssoutput name is forced viaassetFileNames(source entry iscontent-style, output renamed tocontent.css).manifest.jsonweb_accessible_resourcesmust includedist/overlay.htmlanddist/assets/*for the iframe src to be loadable from host pages.
React overlay state (App.tsx)
All UI state lives in App.tsx: elementData, messages, isSelecting, isEditing, showPreview, showSettings. Child components (Toolbar, HtmlPreview, ChatPanel, SettingsPanel) are purely presentational and communicate upward via props/callbacks.
Custom Tailwind utilities (.glass, .scrollbar-thin) are defined in src/overlay/index.css and are available to all overlay components.
AI / Codex integration
src/lib/storage.ts— read/write{ apiKey, baseURL, model }tochrome.storage.localsrc/lib/tools.ts— tool schemas (TOOL_DEFINITIONS) passed to Codex +executeTool()which sends typed postMessages to content.ts and awaits*_RESULTresponsessrc/lib/Codex.ts— wraps@anthropic-ai/sdkstreams; runs an agentic loop (calls → tool results → next call) until no moretool_useblocks; firesStreamCallbacksfor incremental UI updatessrc/overlay/components/SettingsPanel.tsx— UI to configureapiKey,baseURL,model; persisted viastorage.tssrc/overlay/components/ChatPanel.tsx— maintainsMessageParam[]history for the SDK; callsrunConversationTurn()on send; streams tokens directly into the last assistant message viaappendToLastAssistant()
The manifest.json requires storage permission for chrome.storage.local access.
Development Specifications
Build & verify
- After every non-trivial change run
npm run buildand confirm exit code 0 before declaring done. - No test runner or linter is configured — build success is the only automated check.
AI tool system
- All tools exposed to the AI are defined in
src/lib/tools/(definitions/for individual tool files,registry.tsforALL_TOOLS,index.tsforTOOL_DEFINITIONS+executeTool()). - Adding a new tool requires: a definition file in
definitions/, registration inregistry.ts, and a handler branch inexecuteTool()inindex.ts. - The special
ask_usertool pauses the agentic loop and waits for a user reply via aPromiseresolved inChatPanel.tsx. It must NOT be handled byexecuteTool()— it is intercepted inanthropic.ts/openai.tsbefore the normal tool dispatch.
AI provider loop
src/lib/ai/anthropic.tsandsrc/lib/ai/openai.tsboth run an agenticwhile (continueLoop)loop.continueLoopis set totrueonly when tool calls are present in the response. Plain text replies (end_turnwith no tools) stop the loop naturally.ask_usertool: after receiving the user answer the loop must continue (continueLoop = true,breakinner tool loop) so the AI sees the answer and responds. Do NOTreturnearly after resolvingask_user.StreamCallbacks.onAskUseris optional; if absent,ask_userresolves with an empty string.
ChatPanel streaming state
streamBufRefaccumulates the current streaming token buffer.streamIdRefis set toDate.now()when the first token of a new assistant message arrives (null= no message started yet).- Before resuming after
ask_user, reset both refs to''/nulland callsetIsThinking(true)so the next AI response renders as a fresh message.
Ollama / OpenAI-compatible providers
- Ollama requests are proxied through the background service worker (
streamViaBackground) to avoid CORS issues from the extension origin. - If Ollama returns 403, the fix is to set the environment variable
OLLAMA_ORIGINS=*and restart Ollama. This hint is shown automatically in the chat error message.
AI Tabs bar
background.tstracks AI-opened tabs per session insessionAiTabs: Map<string, number[]>.- After
open_tab/close_tab/onRemoved/resetTabGroup, background pushes{ type: 'AI_TABS_UPDATE', tabs: [{id, title, url}] }viachrome.runtime.sendMessage. App.tsxlistens forAI_TABS_UPDATEand maintainsaiTabsstate; passesonCloseAiTab/onCloseAllAiTabsto ChatPanel.- Closing a tab calls
chrome.tabs.remove(tabId).catch(() => {})directly from the overlay — no AI tool call needed. aiTabsstate is cleared on new/switched session.
Agents
src/lib/agents/index.ts—Agentinterface,BUILTIN_AGENTS(10 built-ins),loadCustomAgents/saveCustomAgents(chrome.storage keycustomAgents),getAllAgents,buildAgentSystemPrompt.activeAgentId: string | nulllives in per-session Zustand state (store.ts);customAgentsin shared store state.buildAgentSystemPrompt(agent)appends tool hints + page context hint (for agents with page tools) + navigation rules.PAGE_CONTEXT_TOOLSset inagents/index.tsdetermines which agents get page context injected in their message.runConversationTurnaccepts optional 5th paramextraSystemPrompt?: string; bothrunAnthropicTurnandrunOpenAITurnappend it toSYSTEM_PROMPT.- ChatPanel detects
@in textarea input to show a mention picker popover (keyboard navigable); selecting an agent callsselectMention(agent)which strips the@queryand setsactiveAgentId. - Active agent shown as a chip above the textarea; ✕ button clears
activeAgentId. - Each
handleSendcomputesextraSystemPromptfrombuildAgentSystemPrompt(activeAgent)+ language hint. SkillsPanel.tsx— overlay panel for browsing/activating/deleting agents and creating custom ones.- ⚡ button in the ChatPanel bottom toolbar toggles the Agents panel.
HTML Preview Page
preview.html+src/preview/— standalone Chrome tab opened viachrome.runtime.getURL('preview.html').- Left pane: editable
<textarea>with HTML source. Right pane:<iframe srcDoc>live render. - Communication: side panel writes HTML to
chrome.storage.localkeypreviewHtmlviasavePreviewHtml(); preview page listens withchrome.storage.onChanged. - AI streaming:
onTokenthrottles (500 ms) extraction of HTML code fences and pushes to preview page only if it is already open (chrome.tabs.querycheck). - Code blocks in assistant messages: a "发送到预览" button is injected via DOM into every
<pre><code class="language-html">element after render. - Toolbar "预览" button: extracts the first HTML code fence from the last assistant message and opens/focuses the preview tab.
savePreviewHtml/loadPreviewHtmllive insrc/lib/storage.ts.
Key conventions
- All
chrome.storageaccess goes throughsrc/lib/storage.ts. - Do not add error handling for scenarios that cannot happen; trust framework guarantees.
- Keep solutions minimal — no premature abstractions, no extra configurability unless asked.
- MCP servers are loaded/saved via
loadMcpServers/saveMcpServersinstorage.ts; disabled tools vialoadDisabledTools/saveDisabledTools. - Custom agents loaded/saved via
loadCustomAgents/saveCustomAgentsinsrc/lib/agents/index.ts. - Build output is
plugin/(notdist/). Load unpacked from project root (wheremanifest.jsonlives). - Desensitization:
createDesensitizer()handles reversible encode/decode for AI layer;desensitize()is for irreversible display masking of tool results. Assistant messages are stored decoded (real text), not with placeholders. - Language hint:
navigator.languageis appended toextraSystemPromptinChatPanel.tsxso the AI responds in the user's browser language.