Files
page-agent/packages/extension/src/agent/MultiPageAgent.ts
T

103 lines
3.0 KiB
TypeScript
Raw Normal View History

2026-03-06 17:39:09 +08:00
import { type AgentConfig, PageAgentCore } from '@page-agent/core'
import { RemotePageController } from './RemotePageController'
import { TabsController } from './TabsController'
2026-01-28 13:15:34 +08:00
import SYSTEM_PROMPT from './system_prompt.md?raw'
import { createTabTools } from './tabTools'
2026-02-02 19:29:30 +08:00
/** Detect user language from browser settings */
function detectLanguage(): 'en-US' | 'zh-CN' {
const lang = navigator.language || navigator.languages?.[0] || 'en-US'
return lang.startsWith('zh') ? 'zh-CN' : 'en-US'
}
2026-03-27 20:18:13 +08:00
interface MultiPageAgentConfig extends AgentConfig {
includeInitialTab?: boolean
experimentalIncludeAllTabs?: boolean
}
2026-01-29 19:21:57 +08:00
/**
* MultiPageAgent
* - use with extension
* - can be used from a side panel or a content script
*/
export class MultiPageAgent extends PageAgentCore {
2026-03-27 20:18:13 +08:00
constructor(config: MultiPageAgentConfig) {
// multi page controller
const tabsController = new TabsController()
const pageController = new RemotePageController(tabsController)
const customTools = createTabTools(tabsController)
2026-02-02 19:29:30 +08:00
// system prompt - auto-detect language if not specified
const language = config.language ?? detectLanguage()
const targetLanguage = language === 'zh-CN' ? '中文' : 'English'
const systemPrompt = SYSTEM_PROMPT.replace(
/Default working language: \*\*.*?\*\*/,
`Default working language: **${targetLanguage}**`
)
const includeInitialTab = config.includeInitialTab ?? true
2026-03-27 20:18:13 +08:00
const experimentalIncludeAllTabs = config.experimentalIncludeAllTabs ?? false
2026-01-29 19:21:57 +08:00
/**
* When the agent is in side-panel and user closed the side-panel.
* There is no chance for isAgentRunning to be set false.
* (unload event doesn't work well in side panel.)
* (I'm trying not to use long-lived connection because the lifecycle of a sw is hard to predict.)
* This heartbeat mechanism acts as a backup.
*/
let heartBeatInterval: null | number = null
super({
...config,
pageController: pageController as any,
customTools: customTools,
customSystemPrompt: systemPrompt,
onBeforeTask: async (agent) => {
2026-03-27 20:18:13 +08:00
await tabsController.init(agent.task, { includeInitialTab, experimentalIncludeAllTabs })
2026-01-29 19:21:57 +08:00
heartBeatInterval = window.setInterval(() => {
chrome.storage.local.set({
agentHeartbeat: Date.now(),
})
}, 1_000)
await chrome.storage.local.set({
isAgentRunning: true,
})
},
onAfterTask: async () => {
2026-01-29 19:21:57 +08:00
if (heartBeatInterval) {
window.clearInterval(heartBeatInterval)
heartBeatInterval = null
}
await chrome.storage.local.set({
isAgentRunning: false,
})
},
onBeforeStep: async (agent) => {
if (!tabsController.currentTabId) return
// make sure the current tab is loaded before the step starts
await tabsController.waitUntilTabLoaded(tabsController.currentTabId!)
},
onDispose: () => {
2026-01-29 19:21:57 +08:00
if (heartBeatInterval) {
window.clearInterval(heartBeatInterval)
heartBeatInterval = null
}
chrome.storage.local.set({
isAgentRunning: false,
})
tabsController.dispose()
},
})
}
}