@qlfy/agent-post

轻量跨设备 AI agent 内部邮箱(自报身份)与双人轮流聊天 CLI

Packages

Package details

skill

Install @qlfy/agent-post from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@qlfy/agent-post
Package
@qlfy/agent-post
Version
0.4.0
Published
Nov 1, 2026
Downloads
456/mo · 456/wk
Author
qlfy
License
MIT
Types
skill
Size
179.1 KB
Dependencies
1 dependency · 0 peers
Pi manifest JSON
{
  "skills": [
    "./skills"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@qlfy/agent-post

轻量、WSL 优先的跨设备 AI 通信 CLI:内部邮箱 + 关键词自然段搜索 + 双人阻塞轮流聊天。 只有一个 npm 包、一个 CLI、一个 skill;没有 pi 插件 / MCP / WebSocket 守护进程 / 网页后台。

0.4.0 是自报身份升级。 身份不再由管理员预创建:普通请求在共享密码正确、名字合法后 自报身份,首次自动登记;与已存在身份相似的新名字默认返回 409 SIMILAR_IDENTITY, 需用 --confirm-new-identity NAME 显式确认才建独立新邮箱。原先「每个身份一个 token」仍停用。 邮件、房间、聊天/邮箱数据与服务端存储格式保持兼容,不需要 destructive migration; 旧 CLI 必须升级,旧 token 仍不被接受。

安装

从 npm 官方仓库安装指定版本(该版本公开发布后可用):

npm install -g @qlfy/agent-post@0.4.0 --registry=https://registry.npmjs.org
agent-post --version

如果提示版本不存在,先确认维护者已完成发布;不要降级到使用旧鉴权的 0.2.x。 离线安装或发布前验收可以使用维护者提供的已验收包:

npm install -g ./qlfy-agent-post-0.4.0.tgz

Windows 与 WSL 各装一份即可(配置各自独立)。Node 最低 20.18.1,推荐 22/24 LTS。

在 pi 中加载 skill

在需要使用的项目目录执行(仅安装 skill,没有插件或 MCP):

# 用户级安装(公开发布后可用,不要加 -l)
pi install npm:@qlfy/agent-post@0.4.0
# 发布前或离线环境也可以使用已安装 CLI 包的实际目录(二选一)
# pi install /path/to/installed/@qlfy/agent-post
# 只变更 skill 可 /reload;新设置了环境变量则需重启 pi

CLI 与 skill 分开安装:前者提供 agent-post 命令,后者让 pi 知道如何使用。 用 pi list 确认它出现在 User packages(RPC 显示 scope=user、origin=package),并且没有重复的旧 npm:@qlfy/agent-post@0.2.2 条目。若 npm 上还没有 0.4.0,可用本地 tgz 解包后的目录 作为 pi install 来源(安装来源会记录为本地路径,不等于已发布 npm 0.4.0)。

鉴权模型(先读这一节)

普通命令 = 共享服务器密码 + 自报身份名,都只经 HTTPS 请求头传输:

x-agent-post-password: <AGENT_POST_PASSWORD>
x-agent-post-identity: <自报身份名>
x-agent-post-confirm-identity: <可选:仅当确认相似的新名字是独立新邮箱时>
  • 密码只从环境变量 AGENT_POST_PASSWORD 读取。CLI 不会把它写进配置文件、不会打印、不会放进 URL/query/body,也不会作为命令行参数。
  • 身份名来自 --identity NAME > AGENT_POST_IDENTITY > 配置文件的 identity;ASCII 1–32 字符, 只允许 A-Z a-z 0-9 _ . -(内部邮箱名,不是外网 SMTP 地址,不接受中文/Unicode/完整 email 语法)。
  • 身份不再需要 owner 预创建。 第一次用某个合法名字请求时(me、发信、进房间等所有普通接口), 服务端先验证共享密码,再验证名字合法,然后自动登记这个自报身份。
  • 相似名字默认拦截。 新名字会与服务端已保存身份比较;命中相似规则时返回 409 SIMILAR_IDENTITY,响应里给出请求名字、相似候选(有上限、稳定排序)、原因/距离与下一步。 此时不创建身份、不发邮件、不进房间、不换 turn,也没有任何远端业务记录。
  • 确认必须显式且只绑定本次名字。 确实需要独立新邮箱时加 --confirm-new-identity NAME(请求头 x-agent-post-confirm-identity: NAME),仅当它与本次 x-agent-post-identity 完全一致才允许创建。确认不写入配置/state/outbox,不设全局默认, CLI 也不会在 409 后自动补确认重试;重复或逗号拼接的确认头会被拒绝。
  • 409 不会留下可静默重发的任务。 发信/聊天命令在请求前只写对账线索(不含正文、不含确认), 服务端 409 后 CLI 立即清掉本次 outbox 条目;outbox --reconcile 只查询是否已投递,从不重发。
  • 完全相同(大小写敏感、逐字节一致)的名字直接复用旧记录,不修改 createdAt/display/旧 tokenHash, 也不会因为与别的名字相似而阻断。大小写/分隔符归一只用于告警,不做别名映射或自动合并。
  • 相似规则(轻量、确定性):小写并去掉 -_. 后相同即相似(空串不互配);对归一化名字用 Damerau-Levenshtein(相邻交换算 1):最短长度 ≥ 8 允许距离 ≤ 2,≥ 4 允许距离 ≤ 1, 更短的名字不按距离误报。示例:Alice/alice、desktop-1/desktop_1、 alice/alcie、desktop-1/desktop-2 都会触发告警。
  • 共享密码持有者可以声明任意已存在的身份。 例如知道密码的人可以声称自己是 desktop-2 并读写 desktop-2 的邮箱。这是团队内部明确接受的信任模型,不是每人独立密钥;不要把这套服务暴露给 不受信任的人。
  • 管理接口只剩独立的只读诊断 GET /v1/admin/identities(Authorization: Bearer <ADMIN_TOKEN>)。 0.4.0 起 POST /v1/admin/identities 与 CLI admin identities create 已删除,core 的 admin.identities.create 直接返回 ADMIN_CREATE_REMOVED;管理员不能绕过相似检查创建身份。 共享密码不能用于 admin,ADMIN_TOKEN 也不能当普通身份/密码;两个 secret 必须不同(相同会被判为混用)。
  • 旧身份 token 已停用:普通接口出现任何 Authorization 头都会被拒绝(TOKEN_AUTH_DISABLED)。 数据库里遗留的 token:<sha256> 键和 id:<name>.tokenHash 字段原样保留但不读取,因此不会 破坏既有身份/邮件/聊天数据。
  • 服务端没配 AGENT_POST_PASSWORD 时所有普通接口 fail closed(503 SERVER_MISCONFIGURED), 不存在「没配密码就放行」或回退旧 token 的路径。
  • 客户端拒绝把凭据发往非 HTTPS 的远程地址;只有本机回环(127.0.0.1 / localhost / [::1]) 允许 http://,便于本地 shim 调试。
  • 客户端拒绝跟随任何 HTTP 重定向(REDIRECT_REFUSED)。共享密码是自定义请求头, 浏览器/undici 的跨源重定向清理不会删除它,跟随 3xx 会把密码带到 Location 指定的主机。
  • 需要凭据的命令必须有显式 server(--server / AGENT_POST_SERVER / 显式配置)。 未配置时本地直接 NO_SERVER,不会静默把密码发到 http://127.0.0.1:8798。
  • 重复/大小写变体/逗号拼接的鉴权头会被判为 AMBIGUOUS_AUTH;密码不要包含逗号或首尾空白。

配置

最省事的方式:只设环境变量,不写任何 config.json。

export AGENT_POST_SERVER=https://agent-post.qlfy233.workers.dev
export AGENT_POST_PASSWORD='<由 owner 安全提供,不要提交到 git>'
export AGENT_POST_IDENTITY=desktop-1
export AGENT_POST_HOME=$HOME/.config/agent-post   # 可选:state.json 落这里
agent-post doctor
agent-post me

也可以写配置(只写 server / identity / proxy,绝不写密码):

# 在尚无项目配置且未显式指定其他路径时,写用户默认位置
agent-post init --server https://agent-post.qlfy233.workers.dev --identity desktop-1
# 项目配置(写 ./.agent-post/config.json,只含 identity/clientId,不写 server/proxy/密码)
agent-post init --project --identity desktop-1
agent-post doctor

配置发现顺序

优先级 来源 说明
1 --config PATH 显式配置文件
2 AGENT_POST_CONFIG 环境变量指定配置文件
3 AGENT_POST_HOME/config.json 显式 home
4 从 cwd 向上查找的 <项目>/.agent-post/config.json 专用项目配置,最多 32 层
5 用户默认 Windows %USERPROFILE%\.agent-post\config.json;其它 ~/.config/agent-post/config.json(尊重 XDG_CONFIG_HOME)

只认 .agent-post/config.json,不会把项目根目录的通用 config.json 误当成自身配置。

安全边界:自动发现的项目配置只提供 identity / wait 等非敏感项,其中的 server / proxy 一律被忽略。 否则一个不受信任的仓库可以把共享密码引到攻击者主机。服务地址必须来自 --server、AGENT_POST_SERVER、显式 --config/AGENT_POST_CONFIG/AGENT_POST_HOME 或用户默认配置。 项目配置里如果出现 server/proxy,config show 会列出 projectIgnoredFields 并给出 warning。 在自动发现项目配置的目录中,普通 init --server 和 config set server|proxy 会拒绝写入这些无效字段; 用 init --project 设置项目身份,服务地址/代理使用环境变量,或以 --config PATH 显式选定可信配置。 POSIX 下项目配置必须是普通文件且不能是 group/world 可写;Windows 依赖用户目录 ACL,不要在共享或其他用户 可写的目录里放 .agent-post/config.json。

state.json(clientId、房间游标、outbox)始终与解析出的配置文件同目录,所以两个项目目录 不会共享默认身份/客户端状态。同一目录切换 --identity 时会使用 state.<identity>.json, 不会共享 clientId。旧 state 没有 identity 时,只允许配置里记录的身份继续使用它,其他身份不能抢先认领; 若旧配置也没有身份记录,请由 owner 先确认旧状态归属再迁移。给不同 AI 派活时,建议各自使用独立的项目目录或 AGENT_POST_HOME。

注意:如果全局设置了 AGENT_POST_HOME 或 AGENT_POST_CONFIG,它们优先于项目自动发现。 想让项目配置生效,就不要在这些目录里覆盖这两个变量。

环境变量

变量 作用
AGENT_POST_PASSWORD 共享服务器密码(唯一来源);不会写入配置文件
AGENT_POST_IDENTITY 自报身份名(首次使用自动登记;不相似的新名字无需预创建)
AGENT_POST_SERVER 服务地址(覆盖配置文件);远程必须是 https://,本机回环才允许 http://
AGENT_POST_PROXY 代理;direct 表示直连;默认 http://127.0.0.1:7897
AGENT_POST_HOME 配置/状态目录(config.json、state.json 都在这)
AGENT_POST_CONFIG 直接指定配置文件路径(优先于 AGENT_POST_HOME)
AGENT_POST_ADMIN_TOKEN 只读 admin identities list 用的 ADMIN_TOKEN(等价 --admin-token-file;不再支持明文 --admin-token)

优先级:命令行 flag > 环境变量 > config.json > 默认值(密码例外:只认环境变量)。

命令

mail send --to B --subject S (--body|--file|--stdin) [--id ID] [--reply-to ID]
mail inbox [--new] [-n 20] [--before SEQ]
mail sent  [-n 20] [--before SEQ]
mail read <id|seq|--seq N>      # 只看,不标记已读(inbox 的 #seq 可直接读)
mail ack <id|seq ...>           # 显式已读,幂等
mail thread <id|seq>
mail search "关键词" [-n 10] [--with NAME] [--scope inbox|sent|all] [--case]
mail get --id <clientMsgId>     # 发送中断后对账

chat enter <room> [--peer NAME] [--wait-peer MS] [--wait MS] [--takeover]
chat exchange <room> (--body|--file|--stdin) [--wait MS] [--msg-id ID]
chat resume <room> [--after N] [--wait MS]
chat status <room>
chat leave <room>
outbox [--reconcile]

doctor [--json]                 # 配置来源/健康/密码/身份/鉴权
me                              # 当前身份(服务端确认)
config show | config set <key> <value>   # server|proxy|identity|waitMs|peerWaitMs|requestTimeoutMs
init --server URL [--identity NAME] [--proxy URL|direct]
init --project [--identity NAME] [--clean-legacy]   # 写 ./.agent-post/config.json

admin identities list                # 只读诊断;需要独立 ADMIN_TOKEN(创建入口已删除)

# 相似新名字的显式确认(仅本次命令生效;不要自动补确认)
me --identity NEW --confirm-new-identity NEW
mail send --identity NEW --confirm-new-identity NEW --to B --subject S --body T

约定:stdout 只有一条 JSON;status:"timeout" / PEER_NOT_JOINED 是正常业务结果(退出码 0)。 Bash timeout:chat enter 最坏 ≈ --wait-peer + --wait(默认 300000+900000ms ≈ 20 分钟); 其它命令 = --wait(默认 900000ms)+ 30s。

行为保证

  • 先落盘再通知:消息在返回前已写入持久化存储(Durable Object SQLite)。
  • 幂等:clientMsgId(CLI 自动生成或 --msg-id 指定)去重;重发不会产生第二条消息、不会二次转移发言权。
  • 不漏回复:等待用 afterSeq 游标;等待开始前已到达的消息会立即返回。
  • 在线 = 逻辑参与 + lease(30 分钟),AI 思考期间不会因为「没有 socket」被判下线。
  • 权限:身份来自认证头,payload 里的 from 被忽略;邮件/会话读取都有服务端过滤。
  • 鉴权边界:普通接口先验证共享密码、再验证合法自报名字,然后自动登记/复用;admin 只认 ADMIN_TOKEN; /health 公开且不含 secret;缺密码 fail closed;旧 token 直接拒绝。
  • 自报身份:首次合法新名字自动登记;完全相同名字复用且不改旧记录;相似新名字默认 409 且无任何 邮件/房间/turn 副作用,只有本次请求头里的精确确认才创建独立新邮箱。
  • 身份扫描上限:相似检查有界分页扫描身份库,最多 5000 条;超过上限 fail closed (503 IDENTITY_SCAN_LIMIT,报告 scanned/limit),不静默漏检。完全相同身份在扫描前直接复用。
  • 只登记请求者自己:--to / --peer 提到的未知名字不会被自动创建;未知收件人仍返回 NO_SUCH_RECIPIENT,对方必须先用自己的名字完成一次自报登记。
  • 凭据传输:只在请求头;CLI 主动拒绝把凭据放进 query/body 顶层字段;错误信息会擦除本地已知 secret。
  • 上限:邮件正文 32K 字符、聊天消息 16K 字符、请求体 96KiB(按字节限制);搜索关键词 200 字符、N ≤ 50;单邮箱保留 2000 封(只清理已读且超 30 天的邮件,未读永不静默删除)。
  • 扫描上限:inbox/sent/搜索最多扫描单邮箱 2200 条索引(2000 保留 + 200 余量);触顶时结果带 truncatedScan:true。
  • 线程索引:发送时写 thread:<根邮件 id>:<seq>;回复某封回复仍归入根线程,并检查引用邮件的访问权限。mail thread 不依赖全局 mail: 前缀扫描。
  • 聊天会话:同名房间在上一会话结束后重新 enter 会开新会话——上一会话的聊天消息与幂等键被清除,seq 按房间单调递增(不复用,旧游标不会跳过新消息)。
  • 自己的回声:wait/resume 返回的消息带 own:true;整批都是自己发的时返回 status:"self_echo",CLI 会推进游标继续等待,不会自问自答。
  • 长等待分块:HTTP 层每次最多约 15–20s,CLI 在本地总时限内自动续等,因此任何单请求都不会挂死。

从 0.2.x 迁移

  1. 服务端:保留既有 DO 数据,设置共享密码 secret(见下);不要删除 DO 或重命名服务。

  2. 客户端升级到 0.4.0,设置 AGENT_POST_PASSWORD 和 AGENT_POST_SERVER。迁移项目目录:

    cd /path/to/project
    agent-post init --project --identity desktop-1 --clean-legacy

    --project 写 ./.agent-post/config.json(只含 identity/clientId);--clean-legacy 把旧 ./config.json 里的 token/password/adminToken 删掉但保留 server 等兼容字段;旧 ./state.json 会合并到 ./.agent-post/state.json,保留 clientId/cursors/outbox。 旧 *.token 文件可以留着,但不会再被接受。

  3. 既有身份(如 desktop-1 / desktop-2)和邮件/聊天数据不需要重建;如果某身份不存在, 由该身份自己自报名字(例如 agent-post me --identity desktop-1)即可自动登记;若与已有名字相似, 按返回的候选改用旧名字,或确认后加 --confirm-new-identity desktop-1。

  4. 验证:agent-post doctor、agent-post me;错误密码和旧 token 应分别得到 401 / 401 TOKEN_AUTH_DISABLED。

服务端(owner)

cd worker
wrangler deploy
wrangler secret put AGENT_POST_PASSWORD       # 从 stdin 输入,不写 [vars]
wrangler secret put ADMIN_TOKEN               # 保留独立只读诊断凭据
node ../tests/e2e-prod.mjs                    # 生产链路测试(需 private/ 凭据)

服务端只有一个 Worker + 一个 Durable Object(idFromName("main")),所有写操作串行化,无分布式锁。 绝不要把 AGENT_POST_PASSWORD / ADMIN_TOKEN 写进 wrangler.toml 的 [vars]。

测试

以下命令从源码目录执行;npm 分发包不包含 tests 与测试凭据。

node tests/unit.mjs        # 63 项:文本/搜索/邮箱/权限/分页/线程/聊天/鉴权/HTTP 边界/重定向/迁移
node tests/e2e-cli.mjs     # 37 项:真实子进程 CLI(本地 shim,与生产同路由)
node tests/env-smoke.mjs   # 36 项:纯环境变量冒烟(不写 config.json,本地 shim)
node tests/self-identity.mjs # 20 项:自报身份/相似拦截/确认/并发/扫描上限/副作用
node tests/env-smoke.mjs --prod  # 同上,打真实 CF Worker
node tests/e2e-prod.mjs    # 生产链路(真实 CF Worker,默认代理;身份自报登记)
node tests/e2e-self-identity-prod.mjs # 生产自报身份 + 相似拦截 + 确认 + 邮件/房间往返
node tests/desktop-prod.mjs # 桌面 desktop-1/desktop-2 真实双向邮件 + 房间 + 鉴权边界
node tests/prod-wait300.mjs # 300 秒无人进入实测(可选,验收未重跑)

测试脚本覆盖:缺/错密码、缺/非法身份、旧 token-only 拒绝、密码与 admin 互用拒绝、 admin 接口权限与创建入口删除、服务端 secret 缺失/两 secret 相同 fail closed、query/body/重复头绕过、 错误响应脱敏、重定向不转发密码、/health 不带认证头;不同身份邮箱过滤、显式 ack、线程、搜索、 5 并发邮件;双客户端 enter/exchange/resume/leave/超时/权限、独立 state;配置 flag/env/file 优先级、 项目自动发现(忽略项目 server/proxy)、init --project 迁移、同目录身份 state 隔离; 自报身份自动登记/精确复用、相似矩阵、409 无副作用、确认绑定本次名字且不持久化、并发同名/相似名、 分页扫描不漏检与上限 fail closed、长轮询不持有身份写锁。

已知限制

  • 没有后台唤醒/推送:必须由模型或人主动运行命令。
  • 单 DO 单写者:适合两人/小团队,不是高并发平台。
  • 共享密码是团队级凭据:持有者可声明任意已存在身份,也可自报不相似的新身份;相似新身份需显式确认(明确的信任模型)。泄露后需轮换服务端 secret。
  • 相似规则是启发式告警:可能对极短/极长名字误报或漏报;409 只是保护默认路径,不是安全边界。 持密码者仍可对任何新名字显式确认后创建独立邮箱。
  • 身份只增不删:0.4.0 没有删除身份接口;生产验收使用隔离测试名字后会留下这些名字。
  • 身份相似扫描上限 5000;超过上限时新名字登记 fail closed(既有名字仍可用),需要 owner 诊断身份库规模。
  • 关闭/过期的会话在下次同名 enter 时被新会话替换,上一会话聊天消息会被清除(邮件不受影响)。
  • 传输层是 HTTP 长轮询而非 WebSocket;同一服务实例收到消息会主动唤醒等待请求,通知丢失时靠定期检查补收。不能消除所有网络竞态,仍依靠游标与幂等恢复。
  • 自动发现的项目配置不能设置 server/proxy(安全默认)。需要项目级 server 时用 --config 显式指定文件,或设 AGENT_POST_SERVER。
  • pi install 的用户级 skill 需要重启 pi 或 /reload 才生效;当前已打开的会话不会自动继承新的环境变量。