问题(⑧):守护进程用 noExtensions:true 起会话,它的邮件工具只给模型在邮件
会话里用;人在 TUI 里敲的 pi 拿不到。结果是平台的建设者自己收不到邮件 ——
一个「邮件驱动」的平台,维护者只能绕到 curl + 密钥直连 Gateway 才能看收件箱。
新增 plugins/pi-mail-bridge/extension/index.ts:把同一套工具(createMailTools)
注册到交互式会话。两者是同一条 AgentMail 身份(agent pi)的两个入口,与 DSH 的
「TUI + 邮箱是同一个 Agent」一致。
密钥解析顺序(交互式 pi 的环境里没有 AGENTMAIL_*):
1. 进程环境
2. AGENTMAIL_ENV_FILE(默认 /etc/agentmail/pi.env)—— 与守护进程同一把密钥,
因此身份一致
3. AGENTMAIL_CONFIG_DIR/agent.key 或 ~/.agentmail/agent.key
(兼容 key 与 key_token 两种字段名;实测本机文件用的是 key_token,
只认 key 会静默读不到)
拿不到密钥时不注册任何工具并明确告知 —— 挂一组永远 401 的工具比没有更糟。
不注册 connect_to_server:它会重写 Gateway 坐标并重新登记密钥,而交互式会话与
守护进程共用同一身份,一次 TUI 对话不该改到守护进程的配置。
为什么不会重复注册(读 SDK 实现确认,并用探针实测):
resource-loader.js 里 noExtensions 为真时只用 cliEnabledExtensions,
settings.json 的 extensions 数组被排除 —— 即 noExtensions:true 只加载
命令行 -e 传入的扩展。
探针:noExtensions=true → 扩展数=0;false → 16 个且含 pi-mail-bridge。
deploy/install.sh 增加幂等的扩展注册步骤(写入 settings.json 的 extensions)。
验证:headless pi 实际调用 read_inbox 返回真实邮件主题;工具清单含
send_mail/read_inbox/read_mail/forward_mail/upload_attachment/download_attachment/
suggest_address/list_contacts/session_participants/read_thread(10 个),
connect_to_server 按设计排除。
75 lines
2.6 KiB
JavaScript
75 lines
2.6 KiB
JavaScript
/**
|
||
* 邮件会话 → DSH 会话 id 的派生约定。
|
||
*
|
||
* 这组函数存在的原因是一个具体的静默失效:插件重启后 `sessionMap` 为空,
|
||
* 人在 WebUI 改档位的 `session_update` 找不到目标会话就被忽略,
|
||
* 运行时仍按旧档位执行 —— 人以为自己收紧了权限。
|
||
*/
|
||
|
||
import { test } from "node:test";
|
||
import assert from "node:assert/strict";
|
||
|
||
import {
|
||
dshSessionIdForMail,
|
||
matchesMailSession,
|
||
pickMailSession,
|
||
} from "../lib/mail-session-id.js";
|
||
|
||
test("首次尝试的 id 是 mail-<邮件会话 id>", () => {
|
||
assert.equal(dshSessionIdForMail("abc-123"), "mail-abc-123");
|
||
});
|
||
|
||
test("匹配首次尝试的 id", () => {
|
||
assert.equal(matchesMailSession("mail-abc", "abc"), true);
|
||
});
|
||
|
||
test("匹配模型降级重试的 -r<i>", () => {
|
||
assert.equal(matchesMailSession("mail-abc-r1", "abc"), true);
|
||
assert.equal(matchesMailSession("mail-abc-r12", "abc"), true);
|
||
});
|
||
|
||
test("不匹配别的会话(前缀相同也不行)", () => {
|
||
assert.equal(
|
||
matchesMailSession("mail-abcdef", "abc"),
|
||
false,
|
||
"mail-abc 的前缀不能吃掉 mail-abcdef",
|
||
);
|
||
assert.equal(matchesMailSession("mail-abd", "abc"), false);
|
||
assert.equal(matchesMailSession("other-abc", "abc"), false);
|
||
assert.equal(matchesMailSession("", "abc"), false);
|
||
});
|
||
|
||
test("不匹配非数字后缀(避免误吞其它会话)", () => {
|
||
assert.equal(matchesMailSession("mail-abc-rx", "abc"), false);
|
||
assert.equal(matchesMailSession("mail-abc-r", "abc"), false);
|
||
assert.equal(matchesMailSession("mail-abc-retry", "abc"), false);
|
||
});
|
||
|
||
test("pickMailSession 优先首次尝试,而不是数组顺序", () => {
|
||
// 数组顺序可能来自 ctx.agents.list(),与尝试顺序无关
|
||
assert.equal(
|
||
pickMailSession(["mail-abc-r2", "mail-abc-r1", "mail-abc"], "abc"),
|
||
"mail-abc",
|
||
);
|
||
});
|
||
|
||
test("pickMailSession 没有首次尝试时取序号最小的重试", () => {
|
||
assert.equal(
|
||
pickMailSession(["mail-abc-r3", "mail-abc-r1", "mail-abc-r2"], "abc"),
|
||
"mail-abc-r1",
|
||
);
|
||
});
|
||
|
||
test("pickMailSession 找不到时返回 undefined(调用方据此跳过)", () => {
|
||
assert.equal(pickMailSession(["other-1", "mail-def"], "abc"), undefined);
|
||
assert.equal(pickMailSession([], "abc"), undefined);
|
||
assert.equal(pickMailSession(undefined, "abc"), undefined);
|
||
});
|
||
|
||
test("接管的平台会话推不出 id:不能被误判成邮件会话", () => {
|
||
// 平台自己生成的 id(如 DSH 界面里开的会话)与邮件会话无关,
|
||
// 匹配函数必须说「不是」,否则会给错误的会话改档位。
|
||
assert.equal(matchesMailSession("session-7f3a91", "abc"), false);
|
||
assert.equal(pickMailSession(["session-7f3a91"], "abc"), undefined);
|
||
});
|