Repository navigation
Description
pi.agents-anywhere v1.0.3 完全不可用:activate() 第一行即崩溃(pi.storage 不存在)
环境
| 项 | 值 |
|---|---|
| PI-Desktop | 0.15.8(Windows 11) |
| 插件 | pi.agents-anywhere v1.0.3(市场当前最新) |
| 包 | plugins.aiuo.net/packages/pi.agents-anywhere-1.0.3.piplug,shasum 1a11b8ce…115f |
| provenance | mazongYY/pi-desktop-plugins @ 62bb22489f43153360604d1a035008a7fce1290a,console-upload@1.0.0 @ 2026-09-21T10:27:15Z |
现象
- 插件列表显示已启用(
enabled: true,status: ready); - 每次 PI-Desktop 启动都记录一条错误:
[pi.agents-anywhere] Failed to activate: TypeError: Cannot read properties of undefined (reading 'get') at activate (...\pi.agents-anywhere\main.js:329:42) at Object.<anonymous> (...\pi.agents-anywhere\main.js:379:1) - 面板粘贴配置后点保存/连接毫无反应,无报错,状态永远「离线」;
%USERPROFILE%\.pi-desktop\plugins\data\pi.agents-anywhere\从未生成。
结论:插件从未成功运行过。 业务逻辑一行都没执行。
根因
1(致命)pi.storage 不存在 → activate() 立即崩溃
main.js:329:
const storedConfig = (await pi.storage.get("connector_config")) || { ... };宿主 buildApi()(app.asar)暴露的 pi 表面(77 个 API)为
app / themes / plugin / commands / speech / ui / project / workspace / desktop / fs / agent / models / session / usage / services / bus / clipboard / shell / browser / net / audio / keyboard / events——没有 storage。
持久化接口只有:
plugin.getSettings() plugin.setSettings() plugin.getDataPath()
pi.storage 为 undefined → 抛 TypeError。因为异常发生在 activate() 第 3 行,后面所有代码都未执行:
pi.bus.subscribe("anywhere:save_config")未注册 → 面板保存无效pi.bus.subscribe("anywhere:get_status")未注册 → 面板查询无效pi.events.on("session:turnEnded")/pi.events.on("workspace:changed")未注册 → 无时间线同步
这正好解释「点了没反应」。
2(致命)面板调用了不存在的 window.pi.bus.send
views/panel.html:276, 287, 293:
await window.pi?.bus?.send('anywhere:save_config', parsed);
await window.pi?.bus?.send('anywhere:get_status');宿主面板 preload 只暴露 window.pluginBridge(contextBridge.exposeInMainWorld("pluginBridge", bridge)):
{ invoke(channel, payload), send(channel, payload), on(event, handler), getDroppedFilePath(file) }面板侧不存在 window.pi。因使用可选链 ?.,三次调用静默返回 undefined——不报错也不发送,这是「点击毫无反应」的直接原因。
此外宿主把面板 invoke 路由到插件导出的 onPanelInvoke(channel, payload)(plugin-host-process.js 的 case "panel.invoke"),而本插件未导出该函数。
3(致命)未导出宿主生命周期钩子
宿主契约:
module.exports = { onLoad, onUnload };
// globalThis.pi = buildApi();
// pluginModule = await loadPluginModule(entry);
// if (pluginModule?.onLoad) await pluginModule.onLoad();本插件 main.js:379 只有模块底部自执行 activate().catch(...),完全没有 module.exports:
onUnload缺失 → 卸载时不 WebSocket 断连、不清心跳定时器(连接泄漏)onPanelInvoke缺失 → 面板invoke全部失败
4(致命)WebSocket 端点与鉴权方式均错误
main.js:119, 131-140:
const wsUrl = `${serverUrl.replace(/^http/, "ws")...}/connector/v2/rpc`;
await pi.net.websocket.connect({
url: wsUrl,
headers: { "Authorization": `Connector ${id}:${token}` },
});官方协议是两段式(我已从官方 PyPI 包 anywhere-cli 0.1.7.1 的 connector/runtime.py 独立验证):
| 步骤 | 端点 | 鉴权头 |
|---|---|---|
| 1. 换取 accessToken | POST {server_url}/connector/auth |
Authorization: Connector <id>:<token> |
| 2. 建立隧道 | WSS {ws_url}/connector/ws |
Authorization: Bearer <accessToken> |
官方源码:
# 步骤 1
response = await client.post(
urljoin(self.config.server_url + "/", "connector/auth"),
headers={"Authorization": f"Connector {self.config.connector_id}:{self.config.connector_token}"},
)
body = response.json(); access_token = body["accessToken"] # 另有 expiresIn
# 步骤 2
ws_url = _ws_url(self.config.server_url, "/connector/ws")
async with websockets.connect(ws_url, additional_headers={"Authorization": f"Bearer {access_token}"}):插件跳过了 token 交换,直接拿 Connector 凭据连一个不存在的端点 /connector/v2/rpc。即便端点对了,WS 握手用 Connector 而非 Bearer 也会被服务端拒绝关闭。
5(高)消息封装格式错误
main.js:274, 280 用 { jsonrpc: "2.0", ... }。官方封装是 { type: "response" | "notification", ... }(官方源码 jsonrpc 出现 0 次):
await self._send_json({"type": "notification", "method": method, "params": params})
payload = {"id": request_id, "type": "response", "ok": ok}6(高)心跳方法名错误
main.js:287 发 "heartbeat";官方是 connector.heartbeat(_heartbeat_loop,间隔 20s)。
7(高)net.fetch 响应字段名错误
宿主 net.fetch 返回形状(app.asar 验证):
return { status: res.status, headers: headers2, bodyText };没有 body / text。正确取值是 res.bodyText。
8(中)bus.subscribe 缺权限与主题声明
宿主 busSubscribe() 有两道校验:
this.assertPermission(loaded, "bus.subscribe"); // ① 权限
if (!busSubscribeAllowed(loaded.manifest.contributes?.bus?.subscribe, name))
throw apiError("PERMISSION_DENIED", `topic not declared for subscribe: ${name}`); // ② 主题而 manifest.json 只有 permissions: ["ui.view","net.fetch","net.websocket","desktop.control"],contributes 里没有 bus。所以即使缺陷 1 修好,bus 订阅仍会 PERMISSION_DENIED。
9(中)默认域名 api.agents-anywhere.com 无法解析
api.agents-anywhere.com → NXDOMAIN ❌
web.agents-anywhere.com → 115.190.179.255 ✅
agents-anywhere.com → 115.191.44.213 ✅
插件默认填的 https://api.agents-anywhere.com 不存在(main.js:330、panel.html:177,225),用户不手动改就必然连不上。
10(低)activate() 会被触发两次
因缺陷 3(无 onLoad)+ 自执行 activate(),一旦补上 onLoad 就会初始化两次,两个 AnywhereClient 争抢同一 connector;服务端对同一 connector 只允许一个连接 → 表现为无限 403。需加幂等守卫。
11(低)listSessions() 对返回形状过严
main.js:26 的 Array.isArray(result) ? result : [] 遇到 {sessions:[...]} 等形状会静默返回空数组,手机端表现为「项目和会话没同步过来」。
依赖关系
缺陷1 (pi.storage 不存在) ─> activate() 崩溃 ─> bus/事件监听全部未注册
缺陷3 (无生命周期导出) ─> 面板 invoke 全失败 + 卸载不断连
缺陷7 (net.fetch 字段名) ─> 拿不到 accessToken
缺陷4 (端点/鉴权错误) ─> 403 / 连接被拒
缺陷5+6 (封装/心跳名) ─> 对端无法解析
缺陷8 (缺 bus 声明) ─> 即便上面都修好仍 PERMISSION_DENIED
缺陷10 (双实例) ─> 无限 403 循环
叠加缺陷:修任意一个都不足以连通。
已存在但未合并的修复
在 fork mazongYY/pi-desktop-plugins 中已有 PR #1(HanawaBanana,2026-09-23)——「fix(pi.agents-anywhere): 修复无法激活、面板无响应与连接 403」——结论与本次独立审计高度一致,改动 4 文件 +779/−106,并附带了打包好的 pi.agents-anywhere-1.0.4.piplug。
它当前 closed / merged=false,关闭理由为「目标仓库是 fork,改为向原始仓库 vastsa/pi-desktop-plugins 提交」,但本仓库中并不存在对应的修复 PR(仅 #59 是当初新增插件本身),官方 catalog 至今没有 v1.0.4。
即:修复已经写好了,却卡在 PR 流转中,从未进入市场。 建议优先把它捞回来合入并发布 1.0.4。
PR #1 覆盖缺陷 1–7、10、11;未覆盖缺陷 8(它把面板通信整体改走
pluginBridge.invoke+onPanelInvoke,从而绕开 bus)。
建议修复
- 持久化改
pi.plugin.getSettings/setSettings,回退plugin.getDataPath()下 JSON;都失败时返回默认配置而不抛错(保证activate()永不中断)。 - 面板三处
window.pi.bus.send→window.pluginBridge.invoke,并在main.js导出onPanelInvoke(channel, payload)。 - 导出
module.exports = { onLoad, onUnload, onPanelInvoke };onUnload中断连、清定时器。 activate()加幂等守卫。- 协议改为两段式(
/connector/auth→Connector头;/connector/ws→Bearer),封装改{type:...},心跳改connector.heartbeat,并按expiresIn(约 900s)续期。 net.fetch取res.bodyText。- 默认域名改为可用值,或强制要求填手机端下发的地址。
listSessions兼容array/{sessions}/{items}/{data}。
对审核流程的建议
该包 review.decision = "approved"、risk = "medium",但连「能否加载并激活」都没有验证——activate() 第一行就抛 TypeError,plugins/data/<id>/ 从未生成。
建议增加自动冒烟测试:
- 真实宿主加载插件,断言
onLoad无未捕获异常; - 静态扫描插件中的
pi.xxx.yyy调用,与宿主buildApi()白名单比对(仅此一项即可在打包阶段拦下缺陷 1); - 断言面板不引用
window.pi(应为window.pluginBridge)。
复现
- 安装
pi.agents-anywherev1.0.3; - 重启 PI-Desktop;
- 看
%USERPROFILE%\.pi-desktop\logs\app\plugin.log→Failed to activate: TypeError: ... main.js:329:42; - 确认
plugins\data\pi.agents-anywhere\不存在; - 打开「远程助手」面板填配置点保存 → 无任何反应、无报错。
本报告为独立审计:结论来自对宿主 app.asar 的 API 表面提取、插件源码逐行核对,以及官方 anywhere-cli 0.1.7.1 connector 源码交叉验证。
Activity
muzimu217 commentedon Oct 6, 2026
核验结论:该插件不来自本仓库,本仓库无法修复。
plugins/目录与catalog.json中均无pi.agents-anywhere。mazongYY/pi-desktop-plugins@62bb224于 2026-09-21 通过 console-upload 直接发布到 plugins.aiuo.net,源头在作者的 fork。你的根因分析本身质量很高(
pi.storage不存在、面板window.pi不存在、缺module.exports、WebSocket 鉴权协议不符),建议把这些反馈带到上游作者仓库mazongYY/pi-desktop-plugins,或通过 plugins.aiuo.net 控制台联系作者下架/更新 1.0.4。另外当前 #62(市场站 /packages/ 全部返回 HTML)也会阻断该插件的重新下载,需一并留意。vastsa commentedon Oct 6, 2026
复核结论:不是宿主缺陷,且不属于本仓库的范围
证据
pi.storage命名空间。规范docs/spec/07-plugins/03-plugin-api.md列出的命名空间是app / themes / plugin / commands / speech / ui / project / workspace(fs) / agent / models / provider OAuth / session / plugin-owned sessions / usage / session collaboration / clipboard+shell / browser / services / bus / net / desktop control / audio / keyboard;插件自身的持久化数据走pi.plugin.getSettings()与pi.plugin.getDataPath()。在宿主仓库的规范与运行时/SDK 代码里检索pi.storage无任何命中。因此
pi.storage.get(...)在任何宿主版本上都是undefined—— 这是插件按不存在的 API 写的,不是宿主回归。catalog.json里也没有pi.agents-anywhere条目(本仓库目录只收录 25 个插件),所以这里没有可改的源码或条目。正确去处
mazongYY/pi-desktop-plugins@62bb2248…(console-upload@1.0.0)。把这份缺陷清单提到那里最有效——把第一行的pi.storage换成pi.plugin.getSettings()/getDataPath()(并处理其余 11 项)就能让插件真正跑起来。关闭理由:报告中的崩溃根因在插件自身源码(调用了不存在的宿主 API),本仓库没有对应代码或目录条目可改。结论与去处已记录在此。