feat(ext): add includeInitialTab option; change main world API

This commit is contained in:
Simon
2026-02-04 19:22:06 +08:00
parent 71ca554108
commit 9bd4a47d35
9 changed files with 250 additions and 222 deletions

View File

@@ -17,7 +17,7 @@ function detectLanguage(): 'en-US' | 'zh-CN' {
* - can be used from a side panel or a content script
*/
export class MultiPageAgent extends PageAgentCore {
constructor(config: Omit<PageAgentConfig, 'pageController'>) {
constructor(config: Omit<PageAgentConfig, 'pageController'> & { includeInitialTab?: boolean }) {
// multi page controller
const tabsController = new TabsController()
const pageController = new RemotePageController(tabsController)
@@ -31,6 +31,9 @@ export class MultiPageAgent extends PageAgentCore {
`Default working language: **${targetLanguage}**`
)
// include initial tab for controlling
const includeInitialTab = config.includeInitialTab ?? true
/**
* When the agent is in side-panel and user closed the side-panel.
* There is no chance for isAgentRunning to be set false.
@@ -47,7 +50,7 @@ export class MultiPageAgent extends PageAgentCore {
customSystemPrompt: systemPrompt,
onBeforeTask: async (agent) => {
await tabsController.init(agent.task)
await tabsController.init(agent.task, includeInitialTab)
heartBeatInterval = window.setInterval(() => {
chrome.storage.local.set({

View File

@@ -12,7 +12,7 @@ export class TabsController extends EventTarget {
private task: string = ''
private windowId: number | null = null
async init(task: string) {
async init(task: string, includeInitialTab: boolean = true) {
this.task = task
this.tabs = []
this.currentTabId = null
@@ -26,17 +26,19 @@ export class TabsController extends EventTarget {
})
this.initialTabId = result.tabId
this.currentTabId = result.tabId
this.tabs.push({
id: result.tabId,
isInitial: true,
})
if (!this.initialTabId) {
throw new Error('Failed to get initial tab ID')
}
if (includeInitialTab) {
this.currentTabId = this.initialTabId
this.tabs.push({
id: result.tabId,
isInitial: true,
})
}
await this.updateCurrentTabId(this.currentTabId)
const tabChangeHandler = (message: any): void => {
@@ -230,6 +232,10 @@ export class TabsController extends EventTarget {
`| ${tab.id} | ${url} | ${title} | ${this.currentTabId === tab.id ? '✅' : ''} |`
)
}
if (!this.tabs.length) {
summaries.push('\nNo tabs available. Open a tab if needed.')
}
return summaries.join('\n')
}

View File

@@ -69,11 +69,11 @@ async function exposeAgentToPage() {
}
try {
const { task, llmConfig } = payload
const { task, config } = payload
// create when used
multiPageAgent = new MultiPageAgent(llmConfig)
multiPageAgent = new MultiPageAgent(config)
// events

View File

@@ -1,19 +1,24 @@
import type { AgentActivity, AgentStatus, ExecutionResult, HistoricalEvent } from '@page-agent/core'
import type { LLMConfig } from '@page-agent/llms'
export interface ExecuteHooks {
export type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
export interface ExecuteConfig {
baseURL: string
apiKey: string
model: string
/**
* Whether to include the initial tab (that holds this main world script) in the task.
* @default true
*/
includeInitialTab?: boolean
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
onDispose?: () => void
}
export type Execute = (
task: string,
llmConfig: LLMConfig,
hooks?: ExecuteHooks
) => Promise<ExecutionResult>
export default defineUnlistedScript(() => {
let _lastId = 0
function getId() {
@@ -21,13 +26,13 @@ export default defineUnlistedScript(() => {
return _lastId
}
const execute: Execute = async (task, llmConfig, hooks) => {
const execute: Execute = async (task, config) => {
if (typeof task !== 'string') throw new Error('Task must be a string')
if (task.trim().length === 0) throw new Error('Task cannot be empty')
if (!llmConfig) throw new Error('LLM config is required')
if (!llmConfig.baseURL) throw new Error('LLM config must have a baseURL')
if (!llmConfig.apiKey) throw new Error('LLM config must have an apiKey')
if (!llmConfig.model) throw new Error('LLM config must have a model')
if (!config) throw new Error('Config is required')
if (!config.baseURL) throw new Error('Config must have a baseURL')
if (!config.apiKey) throw new Error('Config must have an apiKey')
if (!config.model) throw new Error('Config must have a model')
const id = getId()
@@ -40,30 +45,31 @@ export default defineUnlistedScript(() => {
// events
if (data.action === 'status_change_event' && hooks?.onStatusChange) {
hooks.onStatusChange(data.payload)
if (data.action === 'status_change_event' && config.onStatusChange) {
config.onStatusChange(data.payload)
return
}
if (data.action === 'activity_event' && hooks?.onActivity) {
hooks.onActivity(data.payload)
if (data.action === 'activity_event' && config.onActivity) {
config.onActivity(data.payload)
return
}
if (data.action === 'history_change_event' && hooks?.onHistoryUpdate) {
hooks.onHistoryUpdate(data.payload)
if (data.action === 'history_change_event' && config.onHistoryUpdate) {
config.onHistoryUpdate(data.payload)
return
}
if (data.action === 'dispose_event' && hooks?.onDispose) {
hooks.onDispose()
if (data.action === 'dispose_event' && config.onDispose) {
config.onDispose()
window.removeEventListener('message', handleMessage)
return
}
// result
if (data.action !== 'execute_result') return
// execute_result
window.removeEventListener('message', handleMessage)
if (data.error) {
@@ -73,6 +79,7 @@ export default defineUnlistedScript(() => {
}
}
// @note will be removed on dispose or result
window.addEventListener('message', handleMessage)
})
@@ -81,7 +88,15 @@ export default defineUnlistedScript(() => {
channel: 'PAGE_AGENT_EXT_REQUEST',
id,
action: 'execute',
payload: { task, llmConfig },
payload: {
task,
config: {
baseURL: config.baseURL,
apiKey: config.apiKey,
model: config.model,
includeInitialTab: config.includeInitialTab,
},
},
},
'*'
)