Skip to content

[Bug] pi.agents-anywhere v1.0.3 完全不可用:activate() 第一行即崩溃(pi.storage 不存在),附带 11 项叠加缺陷 #63

Description

@mylastfree

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

现象

  1. 插件列表显示已启用(enabled: true, status: ready);
  2. 每次 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)
    
  3. 面板粘贴配置后点保存/连接毫无反应,无报错,状态永远「离线」;
  4. %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)。


建议修复

  1. 持久化改 pi.plugin.getSettings/setSettings,回退 plugin.getDataPath() 下 JSON;都失败时返回默认配置而不抛错(保证 activate() 永不中断)。
  2. 面板三处 window.pi.bus.send → window.pluginBridge.invoke,并在 main.js 导出 onPanelInvoke(channel, payload)。
  3. 导出 module.exports = { onLoad, onUnload, onPanelInvoke };onUnload 中断连、清定时器。
  4. activate() 加幂等守卫。
  5. 协议改为两段式(/connector/auth → Connector 头;/connector/ws → Bearer),封装改 {type:...},心跳改 connector.heartbeat,并按 expiresIn(约 900s)续期。
  6. net.fetch 取 res.bodyText。
  7. 默认域名改为可用值,或强制要求填手机端下发的地址。
  8. listSessions 兼容 array / {sessions} / {items} / {data}。

对审核流程的建议

该包 review.decision = "approved"、risk = "medium",但连「能否加载并激活」都没有验证——activate() 第一行就抛 TypeError,plugins/data/<id>/ 从未生成。

建议增加自动冒烟测试:

  1. 真实宿主加载插件,断言 onLoad 无未捕获异常;
  2. 静态扫描插件中的 pi.xxx.yyy 调用,与宿主 buildApi() 白名单比对(仅此一项即可在打包阶段拦下缺陷 1);
  3. 断言面板不引用 window.pi(应为 window.pluginBridge)。

复现

  1. 安装 pi.agents-anywhere v1.0.3;
  2. 重启 PI-Desktop;
  3. 看 %USERPROFILE%\.pi-desktop\logs\app\plugin.log → Failed to activate: TypeError: ... main.js:329:42;
  4. 确认 plugins\data\pi.agents-anywhere\ 不存在;
  5. 打开「远程助手」面板填配置点保存 → 无任何反应、无报错。

本报告为独立审计:结论来自对宿主 app.asar 的 API 表面提取、插件源码逐行核对,以及官方 anywhere-cli 0.1.7.1 connector 源码交叉验证。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions