Files
Agent Board 262e66b02e feat: purge legacy agent hosts and land vault Chrome extension path
Retire ACP/Hermes/OpenCode surfaces and rename hermes_tools to
mnote_agent_tools so Page AI stays on Pi Lab only. Add Chrome vault
extension + extension token route, pre-release purge design, and soft-retire
legacy smokes for the small-group production cut.
2026-07-25 14:25:37 +08:00

172 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { normalizeBaseUrl, vaultFetch } from "./lib/api.js";
import { loadConfig, loadPersistent } from "./lib/storage.js";
const $ = (id) => document.getElementById(id);
function setStatus(text, kind) {
const el = $("status");
el.textContent = text || "";
el.className = kind || "";
}
function setWorkspaceHint(rootUri, rootPath, source) {
const el = $("workspaceHint");
if (!el) return;
if (!rootUri) {
el.textContent = "";
return;
}
const path = rootPath || rootUri.replace(/^file:\/\//, "");
const src =
source === "default"
? "账号默认工作区"
: source === "override"
? "手动覆盖"
: "已保存";
el.innerHTML = `密码箱写入:<code>${path}</code>${src}`;
}
/**
* 必须在用户点击手势内调用:MV3 从 service worker 弹权限常被静默拒绝,
* 外网 baseUrl(含非标端口 frp)没有 host 权限时 fetch 就是 Failed to fetch。
* 同时申请 http(s)://*/*否则外站 Cookie 读不到登录态无法保存
* @param {string} baseUrl
*/
async function requestBaseUrlPermission(baseUrl) {
const origin = new URL(normalizeBaseUrl(baseUrl)).origin;
// Broad first so cookie capture works on every login site after connect.
const broad = ["http://*/*", "https://*/*", `${origin}/*`];
try {
const haveBroad = await chrome.permissions.contains({ origins: broad });
if (haveBroad) return true;
const ok = await chrome.permissions.request({ origins: broad });
if (ok) return true;
} catch {
/* fall through to narrow */
}
const origins = [`${origin}/*`];
const have = await chrome.permissions.contains({ origins });
if (have) return true;
try {
return await chrome.permissions.request({ origins });
} catch (e) {
throw new Error(
`无法申请访问权限 ${origin}${e?.message || e}(请在扩展详情里允许该站点;登录态还需「所有网站」)`
);
}
}
function formatFetchError(e, baseUrl) {
const msg = e?.message || String(e);
if (/Failed to fetch|NetworkError|Load failed/i.test(msg)) {
return (
`无法访问 ${baseUrl || "baseUrl"}${msg}。` +
`常见原因:① 未点允许扩展访问该站点 ② 外网隧道未通 ③ 证书不受信任。` +
`请重新加载扩展后再点「测试连接」,并允许弹窗中的站点权限。`
);
}
return msg;
}
async function hydrate() {
const p = await loadPersistent();
if (p.baseUrl) $("baseUrl").value = p.baseUrl;
// 默认不展示手填;仅当用户曾覆盖时回填并展开
if (p.rootUri) {
$("rootUri").value = p.rootUri;
const adv = $("advRoot");
if (adv && p.rootUri) {
// 不自动展开;连接后 hint 会显示实际路径
}
}
if (p.email) $("email").value = p.email;
const cfg = await loadConfig();
if (cfg.connected) {
setStatus(
`已连接 · ${cfg.email || cfg.userId || ""} · token 有效`,
"ok"
);
setWorkspaceHint(cfg.rootUri || p.rootUri, p.rootPath, "saved");
} else if (cfg.baseUrl) {
setStatus("已保存配置,但 token 缺失(浏览器重启后需重新登录)", "err");
setWorkspaceHint(p.rootUri, p.rootPath, "saved");
}
}
$("test").onclick = async () => {
setStatus("测试中…");
let baseUrl = "";
try {
baseUrl = normalizeBaseUrl($("baseUrl").value);
const ok = await requestBaseUrlPermission(baseUrl);
if (!ok) {
setStatus(
`已取消访问权限:需要允许扩展访问 ${new URL(baseUrl).origin}`,
"err"
);
return;
}
// whoami 不依赖 /api/health(外网入口可能未挂 health
const data = await vaultFetch(baseUrl, "/api/auth/whoami", {
method: "GET",
credentials: true,
});
setStatus(
`可达 · ${baseUrl} · whoami ok=${Boolean(data?.ok ?? true)}`,
"ok"
);
} catch (e) {
setStatus(formatFetchError(e, baseUrl || $("baseUrl").value), "err");
}
};
$("connect").onclick = async () => {
setStatus("登录中…");
setWorkspaceHint("", "");
const password = $("password").value;
let baseUrl = "";
try {
baseUrl = normalizeBaseUrl($("baseUrl").value);
// 关键:在点击手势内申请 host 权限,再交给 background 登录
const ok = await requestBaseUrlPermission(baseUrl);
if (!ok) {
setStatus(
`已取消访问权限:需要允许扩展访问 ${new URL(baseUrl).origin}(含端口)`,
"err"
);
return;
}
const res = await chrome.runtime.sendMessage({
type: "CONNECT",
baseUrl,
// 留空 → background 调 default workspace
rootUri: ($("rootUri").value || "").trim(),
email: $("email").value,
password,
});
$("password").value = "";
if (!res?.ok) {
setStatus(res?.error || "连接失败", "err");
return;
}
const r = res.result || {};
if (r.rootUri) $("rootUri").value = r.rootUri;
setStatus(
`已连接 · ${r.email || r.userId || ""} · 过期 ${r.expiresAt || "—"}`,
"ok"
);
setWorkspaceHint(r.rootUri, r.rootPath, r.workspaceSource || "default");
} catch (e) {
$("password").value = "";
setStatus(formatFetchError(e, baseUrl || $("baseUrl").value), "err");
}
};
$("disconnect").onclick = async () => {
await chrome.runtime.sendMessage({ type: "DISCONNECT" });
setStatus("已断开", "err");
setWorkspaceHint("", "");
};
hydrate();