yunti-browser-runtime 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -9
- package/bin/yunti-browser-runtime.js +6 -0
- package/docs/ACTION_RESULT_COVERAGE.md +41 -0
- package/docs/AGENT_WORKFLOW_CONTRACT.md +108 -0
- package/docs/EXECUTION_PLAN.md +1433 -5
- package/docs/EXTENSION_DISTRIBUTION.md +146 -0
- package/docs/EXTENSION_PERMISSION_STRATEGY.md +167 -0
- package/docs/EXTENSION_STORE_COPY.md +213 -0
- package/docs/INSTALL.md +13 -7
- package/docs/NEXT_MAJOR_PLAN.md +808 -0
- package/docs/PROJECT_STATUS.md +1037 -7
- package/docs/PUBLISHING_BLOCKERS.md +1 -1
- package/docs/RELEASE.md +6 -2
- package/docs/RELEASE_0_2_0_CHECKLIST.md +827 -0
- package/docs/ROADMAP.md +44 -0
- package/docs/SECURITY.md +34 -0
- package/docs/TOOL_GUIDE.md +290 -5
- package/extension/background.js +20 -0
- package/extension/cdp.js +4 -0
- package/extension/content.js +167 -16
- package/extension/dom-observer.js +588 -0
- package/extension/manifest.json +3 -2
- package/extension/popup.js +2 -2
- package/extension/session-manager.js +106 -4
- package/extension/tool-handlers.js +1090 -156
- package/mcp/bridge-hub.js +257 -3
- package/mcp/http-server.js +213 -0
- package/mcp/memory.js +5 -5
- package/mcp/redaction.js +52 -3
- package/mcp/server.js +2 -0
- package/mcp/tools.js +421 -38
- package/package.json +4 -2
- package/scripts/check-action-result-coverage.js +145 -0
- package/scripts/doctor.js +6 -1
- package/scripts/package-extension.js +1 -0
- package/scripts/release-check.js +3 -0
- package/skills/yunti-browser-runtime/SKILL.md +128 -3
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
export function createSessionManager() {
|
|
13
13
|
const sessionsByTab = new Map()
|
|
14
14
|
const pollers = new Map()
|
|
15
|
+
const pendingTabRecovery = new Map()
|
|
16
|
+
const lastInjectionAtByTab = new Map()
|
|
15
17
|
let cachedPlatformMatches = null
|
|
16
18
|
let toolRequestHandler = null
|
|
17
19
|
|
|
@@ -38,6 +40,104 @@ export function createSessionManager() {
|
|
|
38
40
|
}).catch(() => {})
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
async function ensureAllTabsRegistered(options = {}) {
|
|
44
|
+
const settings = await getSettings()
|
|
45
|
+
cachedPlatformMatches = settings.platformMatches
|
|
46
|
+
const tabs = await chrome.tabs.query({})
|
|
47
|
+
const results = await Promise.allSettled(
|
|
48
|
+
tabs.map((tab) => ensureTabRegistered(tab.id, { ...options, tab, settings }))
|
|
49
|
+
)
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
reason: options.reason || "manual",
|
|
53
|
+
checked: tabs.length,
|
|
54
|
+
recovered: results.filter((result) => result.value?.registered || result.value?.injected)
|
|
55
|
+
.length,
|
|
56
|
+
skipped: results.filter((result) => result.value?.skipped).length,
|
|
57
|
+
failed: results.filter((result) => result.status === "rejected" || result.value?.ok === false)
|
|
58
|
+
.length,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function ensureTabRegistered(tabId, options = {}) {
|
|
63
|
+
if (!Number.isFinite(Number(tabId))) {
|
|
64
|
+
return { ok: false, skipped: true, reason: "missing_tab_id" }
|
|
65
|
+
}
|
|
66
|
+
const key = Number(tabId)
|
|
67
|
+
if (pendingTabRecovery.has(key)) return pendingTabRecovery.get(key)
|
|
68
|
+
const promise = recoverTabRegistration(key, options).finally(() => {
|
|
69
|
+
pendingTabRecovery.delete(key)
|
|
70
|
+
})
|
|
71
|
+
pendingTabRecovery.set(key, promise)
|
|
72
|
+
return promise
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function recoverTabRegistration(tabId, options = {}) {
|
|
76
|
+
const settings = options.settings || (await getSettings())
|
|
77
|
+
cachedPlatformMatches = settings.platformMatches
|
|
78
|
+
const tab = options.tab || (await chrome.tabs.get(tabId).catch(() => null))
|
|
79
|
+
if (!isInjectableTab(tab, settings)) {
|
|
80
|
+
return { ok: true, skipped: true, reason: "unsupported_tab" }
|
|
81
|
+
}
|
|
82
|
+
if (sessionsByTab.has(tabId)) {
|
|
83
|
+
await refreshTabRegistration(tabId).catch(() => null)
|
|
84
|
+
return { ok: true, registered: true, reason: "already_registered" }
|
|
85
|
+
}
|
|
86
|
+
const ping = await refreshTabRegistration(tabId).catch((error) => ({
|
|
87
|
+
ok: false,
|
|
88
|
+
error: error?.message || String(error),
|
|
89
|
+
}))
|
|
90
|
+
if (ping?.ok) {
|
|
91
|
+
return { ok: true, registered: true, reason: "content_script_present" }
|
|
92
|
+
}
|
|
93
|
+
const lastInjectionAt = lastInjectionAtByTab.get(tabId) || 0
|
|
94
|
+
if (Date.now() - lastInjectionAt < 5000) {
|
|
95
|
+
return { ok: true, skipped: true, reason: "recently_injected" }
|
|
96
|
+
}
|
|
97
|
+
const injected = await injectContentScripts(tabId).catch((error) => ({
|
|
98
|
+
ok: false,
|
|
99
|
+
error: error?.message || String(error),
|
|
100
|
+
}))
|
|
101
|
+
if (!injected?.ok) {
|
|
102
|
+
return { ok: false, reason: "inject_failed", error: injected?.error || "inject failed" }
|
|
103
|
+
}
|
|
104
|
+
lastInjectionAtByTab.set(tabId, Date.now())
|
|
105
|
+
await delay(50)
|
|
106
|
+
await refreshTabRegistration(tabId).catch(() => null)
|
|
107
|
+
return { ok: true, injected: true, reason: options.reason || "auto_recovery" }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isInjectableTab(tab, settings) {
|
|
111
|
+
if (!tab?.id) return false
|
|
112
|
+
if (tab.discarded) return false
|
|
113
|
+
return isPlatformUrl(tab.url, settings)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function refreshTabRegistration(tabId) {
|
|
117
|
+
return chrome.tabs.sendMessage(tabId, { type: "yunti_refresh_registration" })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function injectContentScripts(tabId) {
|
|
121
|
+
if (!chrome.scripting?.executeScript) {
|
|
122
|
+
return { ok: false, error: "chrome.scripting permission is unavailable" }
|
|
123
|
+
}
|
|
124
|
+
if (chrome.scripting.insertCSS) {
|
|
125
|
+
await chrome.scripting.insertCSS({
|
|
126
|
+
target: { tabId },
|
|
127
|
+
files: ["content.css"],
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
await chrome.scripting.executeScript({
|
|
131
|
+
target: { tabId },
|
|
132
|
+
files: ["dom-observer.js"],
|
|
133
|
+
})
|
|
134
|
+
await chrome.scripting.executeScript({
|
|
135
|
+
target: { tabId },
|
|
136
|
+
files: ["content.js"],
|
|
137
|
+
})
|
|
138
|
+
return { ok: true }
|
|
139
|
+
}
|
|
140
|
+
|
|
41
141
|
async function handleMessage(message, sender) {
|
|
42
142
|
switch (message?.type) {
|
|
43
143
|
case "yunti_content_ready":
|
|
@@ -244,9 +344,7 @@ export function createSessionManager() {
|
|
|
244
344
|
async function refreshActiveTab() {
|
|
245
345
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
|
|
246
346
|
if (!tab?.id) return { ok: false, error: "no active tab" }
|
|
247
|
-
await
|
|
248
|
-
.sendMessage(tab.id, { type: "yunti_refresh_registration" })
|
|
249
|
-
.catch(() => null)
|
|
347
|
+
await ensureTabRegistered(tab.id, { tab, reason: "popup_refresh" }).catch(() => null)
|
|
250
348
|
return getPanelState()
|
|
251
349
|
}
|
|
252
350
|
|
|
@@ -254,6 +352,8 @@ export function createSessionManager() {
|
|
|
254
352
|
sessionsByTab,
|
|
255
353
|
pollers,
|
|
256
354
|
activateTab,
|
|
355
|
+
ensureAllTabsRegistered,
|
|
356
|
+
ensureTabRegistered,
|
|
257
357
|
forgetTab,
|
|
258
358
|
forwardConsoleEvent,
|
|
259
359
|
getPlatformMatches,
|
|
@@ -279,7 +379,7 @@ function normalizePageAuth(auth, source = "unknown") {
|
|
|
279
379
|
return {
|
|
280
380
|
state: "missing_from_content_script",
|
|
281
381
|
loggedIn: false,
|
|
282
|
-
reason: "Content script did not report page state;
|
|
382
|
+
reason: "Content script did not report page state; Yunti will try automatic registration recovery before asking for a page refresh",
|
|
283
383
|
checkedAt: new Date().toISOString(),
|
|
284
384
|
checkedBy: source,
|
|
285
385
|
}
|
|
@@ -300,6 +400,7 @@ function normalizeClientInfo(client) {
|
|
|
300
400
|
const value = client && typeof client === "object" ? client : {}
|
|
301
401
|
return {
|
|
302
402
|
family: normalizeBrowserFamily(value.family || value.userAgent),
|
|
403
|
+
extensionVersion: String(value.extensionVersion || "").slice(0, 80),
|
|
303
404
|
userAgent: String(value.userAgent || "").slice(0, 500),
|
|
304
405
|
platform: String(value.platform || "").slice(0, 120),
|
|
305
406
|
language: String(value.language || "").slice(0, 80),
|
|
@@ -308,6 +409,7 @@ function normalizeClientInfo(client) {
|
|
|
308
409
|
|
|
309
410
|
function getBackgroundClientInfo() {
|
|
310
411
|
return {
|
|
412
|
+
extensionVersion: chrome.runtime?.getManifest?.().version || "",
|
|
311
413
|
userAgent: String(globalThis.navigator?.userAgent || ""),
|
|
312
414
|
platform: String(globalThis.navigator?.platform || ""),
|
|
313
415
|
language: String(globalThis.navigator?.language || ""),
|