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.
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user