Agent2Agent(A2A)v1.0.1 双端插件,用于 DeepSeek Harness — English · 中文
@hanphone/dsh-a2a 是一个独立开源的 A2A 插件,把 DeepSeek Harness profile 变成多面手 A2A 一等公民:能同时对外发布多个入站 A2A server,每个绑定一个自己的 agent preset、拥有独立端点 / AgentCard / 派生技能 / 鉴权;也能同时接入多个出站 A2A server,每个独立 preset,远端技能映射为模型工具。所有 server 实例的创建、启停、编辑、删除全部在 GUI 完成——无需改任何配置文件。
架构与设计决策:docs/architecture.md。 跟随 DeepSeek Harness 版本更新:docs/harness-compatibility.md。
SendMessage、SendStreamingMessage、GetTask、ListTasks、CancelTask、GetExtendedAgentCard、SubscribeToTask;SSE 流式带补发帧;官方 TASK_STATE_* / ROLE_* 枚举与 AgentCard 结构(含 supportedInterfaces、capabilities、完整错误码表)。/a2a/<id>)、AgentCard 路由、鉴权 env、派生技能。ptc、standard、minimal、…);选择器只列 roster 真实预设(与应用内选择器一致)并默认选中部署默认。入站任务在按该 preset 组装的会话中执行(标准 agentPresets resolve+mount 路径)。agentPresets.standingKeyFor + ctx.skills.list),纯自动派生——”preset 确定、技能确定,一切皆插件”。无需手写技能表单;skills 服务缺失时兜底内置 chat。远端用 metadata.skill 调技能,由该 preset 会话经其 tool-skill 装载执行。a2a__<name>__<skill> 模型工具。a2a 存储域(默认 json 后端,可按部署切 SQLite);服务端生成 id 跨重启存活,每个任务记录来源入站实例。session(每个 contextId 一个 DSH 会话)与 subagent(委托 ctx.subagents,工具调用过程流式回传)。a2a/inbound-task waterfall,策略插件可否决或审计。contextId)对应一个活跃 DSH 会话(a2a-<contextId>);GUI 按 contextId 聚合会话(状态、任务数、流式、首次/最近活动),可取消该会话的全部活跃任务或关闭其活跃会话——关闭是内存级释放,同一 contextId 再来任务时自动重开新会话。会话数据全在内存(与对端注册表一致);没有 agent 循环时表格仍从任务记录降级展示。.env/凭据库(0o700);记录只保留自动生成的变量名,token 明文绝不进 a2a 域或 AgentCard。运行时读取分层(凭据 → 进程环境),外部 export 同名变量仍兼容。Config 只承载宿主级默认值(baseUrl、subagentProvider、defaultTimeoutMs)。dsh plugin --profile web add @hanphone/dsh-a2a
任意 profile 名均可(web、自定义 profile、headless 等):
dsh plugin --profile <name> add @hanphone/dsh-a2a
cd dsh-a2a
pnpm build
npm pack
dsh plugin --profile <name> add <path-to>/hanphone-dsh-a2a-<version>.tgz
dsh plugin --profile web add @hanphone/dsh-a2a。pnpm dsh web 或对应 profile 启动命令)。每个入站 server 监听在 profile 的 webServer 上:
# 所建实例的 AgentCard(确切 id 见 GUI)
curl http://127.0.0.1:3080/a2a/<id>/agent-card.json
向实例发任务(其宣告的 chat 技能):
curl -X POST http://127.0.0.1:3080/a2a/<id> \
-H "content-type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"SendMessage","params":{"message":{"role":"user","parts":[{"text":"hello"}],"metadata":{"skill":"chat"}}}}'
浏览器端在设置中注册 A2A 连接 页,分三个 Tab。无需改文件即可:
所有面板流量走 profile webServer 上的仅回环 /a2a/api 路由——远程对端永远无法驱动它。
GUI 覆盖日常实例管理。插件 Config 只有宿主级默认值,如需覆盖可经 profile 用户 patch 层($DSH_HOME/profiles/<name>/cordis.patch.yml)设置:
- id: a2a
config:
baseUrl: http://127.0.0.1:<port> # 省略则从 webServer 地址派生
subagentProvider: in-process
defaultTimeoutMs: 60000 # 出站连接默认超时
实例不经 patch 配置——它们在 GUI 中创建并持久化于 a2a 域(inbound_servers / outbound_servers 表)。
base 类 profile 全部挂载:webServer(@deepseek-ai/dsh-host-webserver)、存储栈(@deepseek-ai/dsh-storage + @deepseek-ai/dsh-storage-domain)、工具注册表(@deepseek-ai/dsh-tools)、agent 循环(@deepseek-ai/dsh-agent + @deepseek-ai/dsh-agent-loop)、agent presets(@deepseek-ai/dsh-agent-presets;preset 选择器与 preset 化会话组装需要它)。subagent 执行器还需要 @deepseek-ai/dsh-subagent。
任务与实例存储位于 a2a 存储域。base 组合默认走 json 后端;要切 SQLite,在同一 patch 层路由域并加后端:
- id: storage-domain
config:
backend: json
routes:
a2a: sqlite
- insert:
- id: storage-sqlite
name: '@deepseek-ai/dsh-storage-sqlite'
config:
path: /absolute/path/to/a2a.sqlite
/a2a 聊天命令与面板对应(GUI 的文字备用路线):
a2a status | presets | peers |
inbound list|create|remove|enable|disable |
outbound list|create|remove|enable|disable|refresh |
tasks | task get|cancel <id> |
sessions | session cancel|close <contextId> | help
InboundServerManager 拥有每个实例:每实例 = preset 化会话池 + A2AServer + 路由。实例持久化于 inbound_servers 表,各自服务独立端点与 AgentCard。任务流经 a2a/inbound-task → 执行器 → 任务存储,SSE 帧推送给订阅者。每实例的会话注册表观察 SSE 生命周期钩子;facade 把这些活跃观测与共享任务存储折叠成按 contextId 的会话视图。OutboundServerManager 拥有每个连接:每实例一个带独立 agent 存储的 OutboundAgentRegistry,实例持久化于 outbound_servers 表。A2AClient 发现 AgentCard,每个技能注册为一个工具。settings.section)经仅回环 /a2a/api 路由读写;host 端喂给它入站/出站 server 视图与 preset 名单(/a2a/api/presets)。完整设计见 docs/architecture.md。
src/
api.ts # 回环面板 API (/a2a/api、/a2a/api/presets)
index.ts # Cordis 插件入口 (apply)
protocol.ts # A2A v1.0.1 协议常量与类型
jsonrpc.ts # JSON-RPC 帧
servers/ # 多实例管理器
inbound-manager.ts # 入站 server 实例(CRUD、路由、生命周期)
outbound-manager.ts # 出站连接实例(CRUD、工具)
server/ # 单实例内部件:store、card、a2a-server、
# routes、executors、inbound-registry、session-registry
outbound/ # 出站内部件:A2AClient、registry、tools
client/ # 浏览器半区:设置面板 (React)
service.ts # ctx.a2a 服务 facade
commands.ts # /a2a 聊天命令
tests/
unit/ # protocol、framing、card、store、registry、server、
# client、api、inbound-registry、identity
composition/ # 在真实 Cordis Context 上以 stub 宿主服务跑 apply()
integration/ # 真 agent loop + JSONL 持久化 + mock LLM
types/ # 对真实 DSH 类型做编译期镜像检查
scripts/
e2e-a2a.mjs # 双实例 A2A hermetic 冒烟
docs/ # 公开手册 + 架构/兼容性/测试
cordis.patch.yml # bundle patch(挂载插件;实例由 GUI 管理)
公开手册按当前行为展开:
深入文档:架构、 Harness 兼容性、 测试。
插件通过项目引用对 harness 源码图做类型检查;需要已构建宿主聚合的 harness checkout。
pnpm typecheck # host + client + 编译期镜像检查
pnpm test # vitest run(单元 + 组合 + 真实持久化)
pnpm build # tsc + tsdown → lib/index.js(host)+ lib/client.js(浏览器)
pnpm test:e2e # 对已构建产物的双实例 A2A hermetic 冒烟
pnpm test:all # typecheck + test + build + e2e
stub 测试看不见宿主运行时契约漂移。分层、各层能抓什么、e2e 流程见 docs/testing.zh.md。
本项目受 ryubyte/dsh-a2a 启发并与其并行开发——那是 DeepSeek Harness 上更早的 A2A 插件。其设计——双端范围、AgentCard 宣告、设置面板模式——为本文实现指明了方向。我们的协议层、任务存储与执行器接缝均为独立实现;GUI 管理模型则直接承袭了 ryubyte 的连接面板。
MIT