Commit Graph

2 Commits

Author SHA1 Message Date
10ff50e899 feat(homeagent): 输出通道改造 —— 通道名 email + 注入通道对齐 + 附件走路径
修的是内核的交付判定与我们对不上的问题。

## 根因

内核判断「这一轮到底有没有交付」**只认工具名前缀** `output_send__*`
(internal/agent/core/process.go 的 isOutputDeliveryTool)。而 `send_mail`
是个普通工具,长得和 read_inbox 没有区别 —— 模型用它发完信,内核不知道
回复已经交付,照常补一句「请根据以上工具结果继续。」。模型把这句话读成
「还要再做一步」,而它手里唯一能做的「一步」往往又是再发一封信。
这个自我强化循环在 QQ 上有过真实事故(单轮 34 次 output_send__qq、514 秒)。

三处改动,缺一不可:

1. **通道名 `homeagent` → `email`**(按投递介质命名,与内核自带的
   qq/webui/cli/acp/a2a 一致),内核据此拼出 `output_send__email`。
   能力位声明必须与实现一致:原先声明了 CapFile 而 handler 不读 `type`,
   于是任何 type=file 都会静默变成一封「把路径当正文」的邮件 ——
   声明一个做不到的能力比不声明更糟(调用方无从发现)。现在 caps=7
   且真的实现 text/file/image。

2. **注入来信时用通道名,不再用插件名**。内核的约定是「用回复该走的通道名
   注入」:webui 传 "webui",clawhubadapter 传它自己的通道名。我们原先两个
   参数都传 `homeagent-mail-bridge` —— 那个名字**不是一个已注册的通道**,
   于是注入来源、模型被告知要用的通道名、实际注册的通道名三者对不上。

3. **提示词补上平台特有的投递指引**(channel.go 的 deliveryHint)。
   共用的 replyInstruction 对人来信说的是「回信不用你自己发,插件会替你转发」
   —— 那句在 dsh/opencode/pi 上完全正确,在 HomeAgent 上却与内核的 persona
   (「必须显式调用 output_send__{通道名}」)相反。实测:模型听我们的、
   返回纯文本、插件兜底转发,邮件确实到了,但内核不知道已交付。
   现在两条口径对齐:优先走通道,兜底 relay 仍然生效且**不会重复发**
   (通道 handler 先 noteExplicitChannelSend,自动 relay 据此让位)。

同时按新纪律在 plg.json 声明 `sdk: "1.2.0"`;工具链据此同步了 go.mod 的
require/replace(它自己写的,含 store 里的绝对路径 —— 换机器跑一次
hmapdev build 会重新同步)。

## 验证(真模型、真网关、真邮件)

进程构建:`hmapdev build` 报「SDK 1.2.0(项目声明 sdk=1.2.0)」、
子进程模式(协议 2);go vet 干净、go test 通过。

部署:经内核自己的 pluginmgr(POST 127.0.0.1:9876/plugins,overwrite=true)
0.1.0 → 0.2.1 → 0.2.2,每次 config_kept=true;重启后 loaded/alive/心跳齐全,
插件日志「注册完成(15 个工具 + 1 个输出通道)」。

行为端到端(两封真邮件):
- 让模型列出通道 → 回信里列出 `email | text / file / image`,与注册的
  caps=7 及描述逐字一致
- 让模型「原样重复标记」→ 内核日志 `executing tool: output_send__email`,
  回流**恰好 1 封**,插件日志「模型已自行回复,跳过自动 relay」
- 让模型建文件并以 type=file 发送 → `cmd_run` → `output_send__email_help`
  → `output_send__email`,附件 chan-file-*.txt(15 字节)原样到达,relay 让位

这两条正是改造的两个动因:内核认得交付(循环被切断),以及附件走
「一个本地路径字符串」而不是 `attachment_ids=[...]` 数组
(opencode 上模型把数组写成 JSON 字符串、连试 6 次放弃整个任务的那个失败模式,
在结构上就不存在了)。
2026-09-12 17:38:32 +08:00
fc33087982 feat: homeagent-mail-bridge —— TrueAgent 接入 AgentMail(MVP)
## 架构

TrueAgent 是单一常驻 agent 事件循环,没有 per-conversation session。
插件像 QQ 插件一样:所有邮件注入同一个事件循环,靠中断消息文本传递 context。
不做 platform_sessions 上报(无 sessions 可映射)。

## 工具

- read_inbox: 收件箱查询(渲染发件人/主题/正文/抄送/附件)
- send_mail: 三维地址发信(支持 reply_to)
- output_send__homeagent 输出通道(agent 可主动发信)

## 自动回信

InjectInputSync(阻塞)等待 agent 处理完毕 → 自动把 FinalText
发回给发件人(reply_to 指向原邮件)。agent 不需要记得调 send_mail。

试过 StageAfterOutput hook,但 before_output 看不到 ToolCalls
(read_inbox 调用在 turn 中间就被清掉了),导致自动回信不触发。
InjectInputSync 更可靠:TrueAgent 保证不重入。

## 基础设施

- 心跳 25 秒
- SSE 长连 + 自动重连(3 秒退避)
- 注册时自动注册密钥(AGENTMAIL_AGENT_KEY 环境变量)
- systemd drop-in 注入 AgentMail 环境变量

## 构建

plugindev build → dist/homeagent_linux_amd64.hmap
安装到 /home/newqqagent/plugins/homeagent-mail-bridge/
homeagent.service drop-in: /etc/systemd/system/homeagent.service.d/agentmail.conf

## 端到端验证

admin → homeagent@/tmp/homeagent.new(自动回信验证)
→ homeagent 读取邮件 + 自动回信「收到确认」→ 25 秒往返完成
2026-09-03 12:57:43 +08:00