dsh-a2a-server 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/CHANGELOG.zh-CN.md +39 -0
  3. package/README.md +122 -29
  4. package/README.zh-CN.md +91 -28
  5. package/cordis.patch.yml +10 -6
  6. package/docker/Dockerfile +5 -0
  7. package/docs/visitor-isolation.en.md +51 -0
  8. package/docs/visitor-isolation.md +75 -0
  9. package/lib/briefing.d.ts +39 -0
  10. package/lib/briefing.d.ts.map +1 -0
  11. package/lib/briefing.js +108 -0
  12. package/lib/briefing.js.map +1 -0
  13. package/lib/config.d.ts +58 -15
  14. package/lib/config.d.ts.map +1 -1
  15. package/lib/config.js +74 -5
  16. package/lib/config.js.map +1 -1
  17. package/lib/contexts.d.ts +9 -4
  18. package/lib/contexts.d.ts.map +1 -1
  19. package/lib/contexts.js +7 -13
  20. package/lib/contexts.js.map +1 -1
  21. package/lib/docker-executor.d.ts +11 -0
  22. package/lib/docker-executor.d.ts.map +1 -0
  23. package/lib/docker-executor.js +115 -0
  24. package/lib/docker-executor.js.map +1 -0
  25. package/lib/index.d.ts +1 -31
  26. package/lib/index.d.ts.map +1 -1
  27. package/lib/index.js +462 -440
  28. package/lib/index.js.map +1 -1
  29. package/lib/isolation.d.ts +21 -0
  30. package/lib/isolation.d.ts.map +1 -0
  31. package/lib/isolation.js +108 -0
  32. package/lib/isolation.js.map +1 -0
  33. package/lib/management.d.ts +5 -0
  34. package/lib/management.d.ts.map +1 -0
  35. package/lib/management.js +75 -0
  36. package/lib/management.js.map +1 -0
  37. package/lib/microsandbox-executor.d.ts +39 -0
  38. package/lib/microsandbox-executor.d.ts.map +1 -0
  39. package/lib/microsandbox-executor.js +316 -0
  40. package/lib/microsandbox-executor.js.map +1 -0
  41. package/lib/microsandbox-runtime.d.ts +28 -0
  42. package/lib/microsandbox-runtime.d.ts.map +1 -0
  43. package/lib/microsandbox-runtime.js +164 -0
  44. package/lib/microsandbox-runtime.js.map +1 -0
  45. package/lib/projection.d.ts +46 -3
  46. package/lib/projection.d.ts.map +1 -1
  47. package/lib/projection.js +19 -8
  48. package/lib/projection.js.map +1 -1
  49. package/lib/router.d.ts +47 -0
  50. package/lib/router.d.ts.map +1 -1
  51. package/lib/router.js +218 -65
  52. package/lib/router.js.map +1 -1
  53. package/lib/sandbox-files.d.ts +16 -0
  54. package/lib/sandbox-files.d.ts.map +1 -0
  55. package/lib/sandbox-files.js +168 -0
  56. package/lib/sandbox-files.js.map +1 -0
  57. package/lib/sandbox-lifecycle.d.ts +48 -0
  58. package/lib/sandbox-lifecycle.d.ts.map +1 -0
  59. package/lib/sandbox-lifecycle.js +125 -0
  60. package/lib/sandbox-lifecycle.js.map +1 -0
  61. package/lib/security.d.ts +4 -2
  62. package/lib/security.d.ts.map +1 -1
  63. package/lib/security.js +4 -2
  64. package/lib/security.js.map +1 -1
  65. package/lib/state.d.ts +37 -0
  66. package/lib/state.d.ts.map +1 -0
  67. package/lib/state.js +146 -0
  68. package/lib/state.js.map +1 -0
  69. package/lib/tasks.d.ts +4 -0
  70. package/lib/tasks.d.ts.map +1 -1
  71. package/lib/tasks.js +18 -0
  72. package/lib/tasks.js.map +1 -1
  73. package/lib/types.d.ts +9 -0
  74. package/lib/types.d.ts.map +1 -1
  75. package/lib/types.js.map +1 -1
  76. package/lib/version.d.ts +1 -1
  77. package/lib/version.js +1 -1
  78. package/lib/visitor-read.d.ts +4 -0
  79. package/lib/visitor-read.d.ts.map +1 -0
  80. package/lib/visitor-read.js +20 -0
  81. package/lib/visitor-read.js.map +1 -0
  82. package/lib/visitor-worker.d.ts +2 -0
  83. package/lib/visitor-worker.d.ts.map +1 -0
  84. package/lib/visitor-worker.js +18 -0
  85. package/lib/visitor-worker.js.map +1 -0
  86. package/lib/visitor-worker.mjs +14686 -0
  87. package/lib/visitor-worker.py +78 -0
  88. package/lib/workspace-read.d.ts +9 -0
  89. package/lib/workspace-read.d.ts.map +1 -0
  90. package/lib/workspace-read.js +152 -0
  91. package/lib/workspace-read.js.map +1 -0
  92. package/package.json +48 -31
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ [English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
4
+
5
+ ## 0.4.0 (2026-11-04)
6
+
7
+ - Exclude nested `node_modules` from Microsandbox public projections without following dependency links or changing host projects. Other links, private imports and Docker mounts remain strictly checked.
8
+
9
+ - Add owner-only local IPC `prepare-stop` to drain tasks and persist guest disks before Portal/CLI terminates the backend. Real-model Relay testing exposed lost recent writes when Windows terminated the process tree without this step.
10
+
11
+ - Add automatically prepared Microsandbox 0.7.11 execution: one VM per authenticated visitor, read-only public projections and independent persistent private disks.
12
+ - Pause after 30 idle seconds and stop after 10 idle minutes; serialize visitor tools, bound resident VMs, preserve files on stop/restart and recover abandoned instances.
13
+ - Keep conversations and credentials outside guest mounts. Migrate old private files outside public roots; reject links and unsafe directory layouts. Only read_file remains exposed to visitors.
14
+ - Verify native Python mutation boundaries, lifecycle, restart persistence, automatic preparation and Portal startup progress. See [isolation guide](docs/visitor-isolation.en.md).
15
+
16
+ ## 0.3.0
17
+
18
+ Requires DeepSeek Harness `>=0.2.1-rc.2 <0.3.0` and speaks A2A 1.0 JSON-RPC.
19
+
20
+ - Restrict visitors to chat and `read_file` within the owner's selected data directory. Block path escapes, links, private plugin state and other tools; separate public prompt context from the owner's runtime context.
21
+ - Move owner RPC and monitoring to authenticated local IPC. Public HTTP accepts visitor credentials only. An empty `trustedPeers` list denies all visitors.
22
+ - Bound concurrent tasks, daily admission, execution time, model calls, outputs and stream buffers. Prevent concurrent tasks in one context from mixing answers; clean up disconnected SSE subscriptions.
23
+ - Persist task results and idle conversation history across eviction and restart within the retention window. Fail interrupted work without replaying it.
24
+ - Add the optional Portal-configured automatic briefing loop: default-on hourly updates, a persistent switch, restart-safe timing, and direct publication to `.hop/handoff.md`.
25
+ - Declare required runtime packages and verify imports without development dependencies. Preserve the guard against malformed Unicode from upstream streaming decoders.
26
+
27
+ Validation: 217 tests in 15 files, TypeScript checking, build and declared-runtime package import check. Models are deterministic test adapters; real-model and cross-network deployment verification remain separate.
28
+
29
+ ### Upgrade
30
+
31
+ Stop the agent, update dsh if needed, then install into the existing profile:
32
+
33
+ ```sh
34
+ dsh plugin --profile YOUR_PROFILE add dsh-a2a-server@0.3.0
35
+ ```
36
+
37
+ Replace `YOUR_PROFILE` with the saved profile name and restart the agent. Upgrade the companion [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent) for the private management endpoint and automatic briefing settings. Do not recreate an existing profile with `--from-default-profile` during an upgrade.
38
+
39
+ Old HTTP owner/monitoring integrations must migrate to local IPC. Review visitor admission and the selected data directory before exposing the service. [Migration details](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.en.md).
@@ -0,0 +1,39 @@
1
+ # 更新日志
2
+
3
+ [English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
4
+
5
+ ## 0.4.0(2026-11-04)
6
+
7
+ - Microsandbox 公共资料副本排除各层级的 `node_modules`,不跟随依赖链接,也不修改宿主项目。其他链接、私有文件导入和 Docker 挂载仍保持严格检查。
8
+
9
+ - 增加仅本机主人可用的 `prepare-stop`,收尾任务并停止沙箱、保留磁盘后,Portal/CLI 才结束后端。真实模型中继测试发现,Windows 直接结束进程树会丢失尚未写盘的近期文件,现已修复正常停止流程。
10
+
11
+ - 自动准备 Microsandbox 0.7.11;每位已认证访客独立 VM,公共资料副本只读、私有磁盘可读写并持久保存。
12
+ - 工具完成后空闲 30 秒暂停、10 分钟停止;同访客工具串行,限制驻留数量,停止/重启保留文件,并回收遗留实例。
13
+ - 聊天记录和凭据不挂入沙箱;旧私有文件迁出公共目录,拒绝链接及不安全目录结构。访客仍只开放 read_file。
14
+ - 增加真实 Python 隔离、生命周期、重启持久化、自动准备与 Portal 启动进度测试,详见[隔离指南](docs/visitor-isolation.md)。
15
+
16
+ ## 0.3.0
17
+
18
+ 要求 DeepSeek Harness `>=0.2.1-rc.2 <0.3.0`,使用 A2A 1.0 JSON-RPC。
19
+
20
+ - 访客可以聊天,并通过 `read_file` 读取主人指定的资料目录。拦截路径越界、链接、插件私有状态及其他工具;公共模型提示与主人运行时上下文分离。
21
+ - 主人 RPC 和监控迁到需要认证的本机 IPC;公网 HTTP 仅接受访客凭据。空白名单 `trustedPeers: []` 拒绝全部访客。
22
+ - 限制并发、每日任务数、执行时限、模型调用、输出和流缓冲;同一 context 的并发请求不会串答,SSE 断开后释放订阅。
23
+ - 在保留期内持久化任务结果与空闲对话,支持驱逐和重启后的恢复。重启中断的任务标记失败,不自动重放。
24
+ - 增加由 Portal 配置的自动简报循环:默认每小时更新、一个持久化开关、重启去重,完成后直接写入 `.hop/handoff.md`。
25
+ - 补齐运行依赖声明,验证脱离开发依赖后可导入。保留上游流式解码异常的 Unicode 防护。
26
+
27
+ 验证:15 个文件共 217 项测试、类型检查、构建和仅使用声明运行依赖的包导入检查。测试使用确定性模型;真实模型与跨网络部署需要单独验收。
28
+
29
+ ### 升级
30
+
31
+ 停止 agent,必要时升级 dsh,然后更新原有 profile 中的插件:
32
+
33
+ ```sh
34
+ dsh plugin --profile YOUR_PROFILE add dsh-a2a-server@0.3.0
35
+ ```
36
+
37
+ 将 `YOUR_PROFILE` 替换为已保存的 profile 名称,安装后重新启动。配套 [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent) 也需要更新,以使用私有管理入口和自动简报开关。升级已有 profile 时不要再次使用 `--from-default-profile`。
38
+
39
+ 原来的 HTTP 主人/监控调用必须迁移至本机 IPC。部署前检查访客许可与资料目录,详见[迁移指南](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.md)。
package/README.md CHANGED
@@ -16,8 +16,8 @@
16
16
  <a href="https://www.npmjs.com/package/dsh-a2a-server"><img src="https://img.shields.io/npm/v/dsh-a2a-server?style=flat&label=npm&color=CB3837" alt="npm version"></a>
17
17
  <a href="https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat" alt="MIT License"></a>
18
18
  <img src="https://img.shields.io/badge/A2A-v1.0%20JSON--RPC-4D6BFE?style=flat" alt="A2A v1.0 JSON-RPC binding">
19
- <img src="https://img.shields.io/badge/DSH-0.1.0--rc.6-4493F8?style=flat" alt="Built against DSH 0.1.0-rc.6">
20
- <img src="https://img.shields.io/badge/tests-167-2EA44F?style=flat" alt="167 tests">
19
+ <img src="https://img.shields.io/badge/DSH-0.2.1--rc.2-4493F8?style=flat" alt="Built against DSH 0.2.1-rc.2">
20
+ <img src="https://img.shields.io/badge/tests-217-2EA44F?style=flat" alt="217 tests">
21
21
  </p>
22
22
 
23
23
  `dsh-a2a-server` makes a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
@@ -30,14 +30,21 @@ The plugin is **inbound only**: it never connects to another agent. There is no
30
30
  client, no peer directory, and no A2A subagent provider. It is a transport
31
31
  adapter over `ctx.agents`, not a capability seam.
32
32
 
33
+ For a local configuration UI and cross-network access, use the companion
34
+ [Talk2MyAgent setup guide](https://github.com/huangjuhua-aigc/Talk2MyAgent/blob/main/README.en.md).
35
+ See the [deployment and migration guide](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.en.md)
36
+ for visitor permissions, automatic briefings, private owner access and Tailscale.
37
+
33
38
  ## Install
34
39
 
35
40
  ```sh
36
- dsh plugin --profile web add dsh-a2a-server
41
+ dsh plugin --profile web add dsh-a2a-server@0.4.0
37
42
  ```
38
43
 
39
44
  `dsh plugin` forwards to pnpm inside the profile directory and appends this
40
45
  bundle to `dsh.profile.bundles`, because the package declares `dsh.bundle`.
46
+ Since DSH v0.2.1-alpha.1, `--profile` must come before any pnpm arguments —
47
+ the forms above already do.
41
48
 
42
49
  To work from a checkout instead, point it at the directory:
43
50
 
@@ -50,13 +57,28 @@ dsh plugin --profile web add ./path/to/dsh-a2a-server
50
57
  | `ctx.agents` | `dsh-base` |
51
58
  | `ctx.credentials` | `dsh-base` |
52
59
  | `ctx.webServer` | **`dsh-web-app`** |
53
- | `ctx.sessionProjections` (optional) | composition; enables `GetTask` after settlement |
60
+ | `ctx.tools` and `ctx.systemPrompt` | required visitor tool and prompt isolation services |
61
+ | `ctx.sessionProjections` (optional) | composition; exposes task lifecycle projections |
54
62
 
55
- The plugin stays PENDING until the three required services exist. `ctx.webServer`
63
+ The plugin stays PENDING until all required services exist. `ctx.webServer`
56
64
  ships in `dsh-web-app` rather than `dsh-base`, so a `headless` profile needs
57
65
  `@deepseek-ai/dsh-host-webserver` mounted before this bundle serves anything.
58
66
  `dsh --profile <name> --dump-config` prints the composed rows.
59
67
 
68
+ ### Upgrading to 0.4.0
69
+
70
+ Version 0.4.0 requires DSH `>=0.2.1-rc.2 <0.3.0`. Stop the agent, install
71
+ `dsh-a2a-server@0.4.0` into its existing profile, then restart it. Owner RPC and
72
+ monitoring now use authenticated local IPC; update the companion Portal as well.
73
+ Visitors retain chat and scoped workspace reads, while other tools are denied.
74
+ An empty `trustedPeers: []` denies all visitors. Task results persist across
75
+ restart within the configured retention window.
76
+
77
+ The current [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent)
78
+ configures default-on hourly briefings with one persistent switch. The plugin
79
+ alone does not enable automatic briefing without its `briefing` configuration.
80
+ Portal automatically prepares Microsandbox for per-visitor isolation. Public data is copied read-only, excluding `node_modules`; private files persist on independent visitor disks. Idle VMs pause after 30 seconds and stop after 10 minutes. Existing standalone configurations keep their selected execution mode; installing the plugin alone does not enable a VM. See the [sandbox guide](docs/visitor-isolation.en.md) and [0.4.0 release notes](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/CHANGELOG.md).
81
+
60
82
  ## Quick start
61
83
 
62
84
  The bundled example composition runs a real model and a listening server.
@@ -103,7 +125,7 @@ curl -s http://127.0.0.1:9922/a2a \
103
125
  | `A2A_PEERS` | `alice` | `name[:token]` list; a token is required for each |
104
126
  | `A2A_PORT` | `9900` | Listening port |
105
127
  | `A2A_SEND_MODE` | `block` | `block` or `immediate` |
106
- | `A2A_WORKSPACE_ROOT` | temp dir | Parent of the per-peer working directories |
128
+ | `A2A_WORKSPACE_ROOT` | temp dir | Shared public data; visitor workspaces live outside this root |
107
129
  | `DEEPSEEK_MODEL` | `deepseek-v4-flash` | Model id requested from the adapter |
108
130
 
109
131
  A one-shot probe runs 67 checks against a live server and exits non-zero on any
@@ -121,13 +143,15 @@ node example/probe.mjs http://127.0.0.1:9922 demo123
121
143
  | `/.well-known/agent-card.json` | GET | Public by default |
122
144
  | `/.well-known/agent.json` | GET | Public by default |
123
145
  | `{basePath}` (default `/a2a`) | POST | Bearer required |
146
+ | `{basePath}/admin/contexts[/<id>]` | GET | Local IPC only + admin token; never registered on public HTTP |
147
+ | `{basePath}/admin/prepare-stop` | POST | Local IPC + admin token; drain tasks and stop sandboxes before terminating DSH |
124
148
 
125
149
  | JSON-RPC method | Status |
126
150
  | --- | --- |
127
151
  | `SendMessage` | Waiting negotiated per request |
128
152
  | `SendStreamingMessage` | SSE |
129
153
  | `GetTask` | Idempotent; answers after settlement |
130
- | `ListTasks` | Cursor-paginated, filterable, resident contexts only |
154
+ | `ListTasks` | Cursor-paginated, filterable, live and retained tasks |
131
155
  | `CancelTask` | Cancels the running turn |
132
156
  | `SubscribeToTask` | Live stream; `-32004` on a terminal task |
133
157
  | `GetExtendedAgentCard` | Extra skills for an authenticated peer; `-32007` when unconfigured |
@@ -179,17 +203,17 @@ another peer answers exactly as an absent one.
179
203
 
180
204
  card:
181
205
  name: dsh-harness
182
- description: Reads code, runs commands, reports findings.
206
+ description: Answers questions using code and text in the authorized directory.
183
207
  public: true
184
208
  skills:
185
209
  - id: general
186
210
  name: general
187
- description: General-purpose task execution.
211
+ description: Chat and scoped file reading.
188
212
  tags: [coding, research]
189
213
  extendedSkills: # authenticated peers only
190
214
  - id: internal
191
215
  name: internal diagnostics
192
- description: Reports harness internals.
216
+ description: Explains project documentation from the authorized directory.
193
217
  tags: [internal]
194
218
  provider:
195
219
  organization: Example Inc.
@@ -226,16 +250,33 @@ another peer answers exactly as an absent one.
226
250
  | `card.skills` | `[]` | Declared skills; falls back to one `general` entry |
227
251
  | `card.extendedSkills` | `[]` | Skills revealed only through `GetExtendedAgentCard` |
228
252
  | `peers` | `{}` | Identity → credential **reference name** |
229
- | `trustedPeers` | all authenticated | Allow-list of identities that may run tasks |
253
+ | `trustedPeers` | all authenticated | Access allow-list for conversation and scoped file reads; `[]` denies everyone |
230
254
  | `rateLimitPerMinute` | `60` | Sliding window per identity |
231
255
  | `maxContextTurns` | `5` | Messages accepted per context before `rejected` |
232
256
  | `sendMode` | `block` | Default when the client sends no `returnImmediately` |
233
- | `blockTimeoutMs` | `60000` | After which a blocking request is declined |
257
+ | `blockTimeoutMs` | `60000` | Maximum blocking wait; returns the current task on timeout so the client can poll |
234
258
  | `contextIdleTtlMs` | `1800000` | Idle time before a context's agent is released |
235
259
  | `maxResidentContexts` | `64` | Ceiling on resident contexts |
236
- | `isolation.workspaceMode` | `per-peer` | `per-peer` or `shared` |
237
- | `isolation.workspaceRoot` | — | Required; parent directory or shared cwd |
238
- | `isolation.peerWorkspaces` | `{}` | Per-identity working-directory override |
260
+ | `maxConcurrentTasks` / `maxConcurrentTasksPerPeer` | `8` / `2` | Atomic task admission; excess requests receive 429 |
261
+ | `maxTasksPerPeerPerDay` | `200` | Persisted task allowance per identity per UTC day |
262
+ | `taskTimeoutMs` | `120000` | Cancels execution when its deadline expires |
263
+ | `maxModelCallsPerTask` / `maxOutputTokens` | `8` / `4096` | Model request and per-request output limits |
264
+ | `maxInputChars` / `maxOutputChars` | `32000` / `64000` | Input/output text limits |
265
+ | `streamHeartbeatMs` / `maxStreamsPerTask` | `15000` / `4` | Heartbeat interval and subscription cap |
266
+ | `maxStreamBufferBytes` | `262144` | Disconnect a stream whose buffered frame exceeds the cap |
267
+ | `storage.directory` | `~/.dsh-a2a/<deployment-key>` | Private checkpoints; one process per directory |
268
+ | `storage.retentionMs` / `storage.maxContexts` | `86400000` / `256` | Retention after last activity, and maximum stored contexts |
269
+ | `storage.maxContextBytes` | `4000000` | Maximum checkpoint bytes per context |
270
+ | `isolation.workspaceMode` | `per-peer` | Legacy migration field; visitors now have an external private cwd |
271
+ | `isolation.workspaceRoot` | — | Required; shared public read-only data root |
272
+ | `isolation.peerWorkspaces` | `{}` | Legacy per-identity read-root override |
273
+ | `isolation.privateRoot` | deployment-specific home directory | Private workspace parent, outside public data |
274
+ | `isolation.execution` | `read-only` | Select `microsandbox` for per-visitor VMs (Portal default); legacy `docker` also supported |
275
+ | `preamble.files` | `[".hop/handoff.md"]` | Host briefing files (**agent-level**: resolved against the workspace root and shared by every visitor; absolute paths pass through), merged into each context's first message; absent files are skipped |
276
+ | `briefing.prompt` | — | Optional single-update instruction executed as the bounded local owner at startup and hourly; configured by Portal, requires owner administration |
277
+ | `briefing.settingsFile` | — | Absolute path to a JSON `{ "enabled": boolean }` preference; absent file means enabled, observed every 5 seconds. Disabling preserves the current briefing and lets active work finish; restart timing is persisted |
278
+ | `admin.tokenEnv` | — | Credential reference for local owner and monitoring requests; rejected by public RPC |
279
+ | `admin.socketPath` | deployment-specific | Windows named pipe or private Unix socket for owner RPC and monitoring |
239
280
  | `push.enabled` | `false` | Reserved; see [Boundaries](#boundaries) |
240
281
 
241
282
  Configuration is refused at load when `isolation.workspaceRoot` is absent, a peer
@@ -254,7 +295,8 @@ peers:
254
295
 
255
296
  ```yaml
256
297
  # ~/.dsh/.credentials.yaml
257
- A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
298
+ refs:
299
+ A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
258
300
  ```
259
301
 
260
302
  `ctx.credentials` resolves the reference per request across four layers — the
@@ -270,11 +312,28 @@ identities.
270
312
  | --- | --- | --- |
271
313
  | Model context | One peer's conversation cannot enter another's model request | Separate `contextId` → separate Session → separate log |
272
314
  | Protocol access | A peer cannot read, continue, or cancel another's context or task | Ownership by authenticated identity |
273
- | Tooling | A peer's agent cannot use tools to read another peer's session | `workspaceMode: per-peer` |
274
-
275
- `per-peer` (the default) derives each identity's `cwd` from `workspaceRoot`.
276
- `shared` places every peer in one directory, which suits peers collaborating on a
277
- single repository; under it, files written by one peer are readable by another.
315
+ | Tooling | Only the scoped `read_file` is available; no writes, shell, network or cross-session tools | Inherited-tool masking, scoped reader, and execution guard |
316
+ | Prompt context | Public agents have a dedicated system prompt and suppress optional host runtime context | Scoped complete prompt and context suppression |
317
+
318
+ Visitor working directories now live outside `workspaceRoot`. The selected root
319
+ contains public data; `read_file` defaults to it and accepts `root: "workspace"`
320
+ for the current visitor's files. Portal automatically prepares Microsandbox:
321
+ one VM per visitor, read-only public projections and persistent private disks.
322
+ Idle VMs pause after 30 seconds and stop after 10 minutes, retaining files.
323
+ Backend conversation storage is not mounted. See [setup and limits](docs/visitor-isolation.en.md).
324
+
325
+ `read_file({"path":"."})` lists the root's entries. A file path reads UTF-8 text;
326
+ `offset` (1-based) and `limit` page lines or directory entries. Reads are bounded
327
+ to 2 MiB files, 32,000 returned characters and 500 lines/entries per call; directory
328
+ scans cover at most 1,000 entries. Binary/office-document extraction is not yet
329
+ implemented. Canonical path checks refuse escapes; symlinks, junctions, hard
330
+ links and Windows alternate data streams are rejected. No shell is invoked.
331
+ `preamble.files` continues to provide an optional opening briefing.
332
+
333
+ Owner RPC uses the same `/a2a` path on **local IPC**, authenticated with the admin
334
+ token. It retains the standard caretaker preset. The public HTTP listener accepts
335
+ only visitor credentials and never mounts the monitoring API. See the
336
+ [migration and deployment notes](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.en.md).
278
337
 
279
338
  ## Boundaries
280
339
 
@@ -285,13 +344,20 @@ single repository; under it, files written by one peer are readable by another.
285
344
  - No `stateTransitionHistory`, protocol extensions, or card signatures.
286
345
  - Streaming emits committed assistant messages. Per-chunk streaming is not
287
346
  implemented.
288
- - No orphan-task watchdog: a task wedged non-terminal stays that way.
289
- - Cross-session tool denial is not implemented; isolation rests on
290
- `workspaceMode`.
347
+ - Execution deadlines cancel the agent. Admission remains occupied until the
348
+ underlying adapter finishes cancellation; an adapter ignoring AbortSignal
349
+ needs process-level supervision. These limits are not a currency budget.
350
+ - Only rooted `read_file` is enabled for visitors. Other tools require separate
351
+ capability work; changing the sandbox mode does not grant those tools. This
352
+ application-level file reader is not an OS sandbox against a malicious local
353
+ administrator mutating the filesystem.
291
354
  - A token ceiling settles a task as `completed`; the real turn ending travels in
292
355
  `Task.metadata.dsh.stopReason`, which A2A's state enum cannot express.
293
- - Task state survives settlement but not a process restart: session persistence
294
- is not composed, so the projection has no log to cold-fold after a reboot.
356
+ - Task results and idle conversation history survive eviction/restart within
357
+ the retention window. Interrupted tasks become failed and are never replayed.
358
+ Cold restoration creates a new harness session behind the stable contextId.
359
+ - One task may run per context; concurrent submissions receive 409. Message IDs
360
+ are not deduplication keys: proxies must not blindly retry submissions.
295
361
  - `ctx.webServer` provides no TLS. Any non-loopback exposure belongs behind a
296
362
  reverse proxy.
297
363
  - A configuration change restarts the plugin and cancels in-flight tasks.
@@ -312,6 +378,10 @@ src/
312
378
  ├── tasks.ts task slots and the three-stage turn correlation
313
379
  ├── projection.ts the a2aTask fold over the session log
314
380
  ├── security.ts authentication, rate limiting, defanging, redaction
381
+ ├── workspace-read.ts bounded read_file within the authorized data root
382
+ ├── management.ts private owner RPC and monitoring over local IPC
383
+ ├── state.ts durable task and conversation checkpoints
384
+ ├── briefing.ts hourly briefing updates and persistent scheduling
315
385
  ├── config.ts schema plus the cross-field checks that fail at load
316
386
  └── types.ts declaration merges into SessionEventMap / MessageSourceMap
317
387
  ```
@@ -327,17 +397,19 @@ carries the committed output, so the fold serves the answer without reaching
327
397
  back into message history.
328
398
 
329
399
  **Residency is explicit.** HTTP has no connection lifetime, so each `contextId`
330
- maps to an Activation evicted when idle, leaving the durable Session behind.
400
+ maps to an Activation evicted when idle. Private checkpoints retain task results
401
+ and conversation history; cold restoration creates a new harness session.
331
402
 
332
403
  ## Development
333
404
 
334
405
  ```sh
335
406
  pnpm install
336
407
  pnpm typecheck
337
- pnpm test # 167 tests across 10 files
408
+ pnpm test # protocol, lifecycle, security and workspace-read regressions
338
409
  pnpm serve # a listening server
339
410
  pnpm probe # 67 checks against a running server
340
411
  pnpm build # emit lib/
412
+ pnpm test:package # verify imports with only declared runtime packages
341
413
  ```
342
414
 
343
415
  The end-to-end suites boot a real Cordis composition with a real agent loop and
@@ -357,7 +429,10 @@ capability seams this plugin consumes. This project supplies:
357
429
  - Per-peer authentication, isolation, and workspace policy
358
430
 
359
431
  The harness is pre-release and does not promise compatibility across renames or
360
- repackaging, so peer dependencies are pinned exactly to `0.1.0-rc.6`.
432
+ repackaging, so compatibility is declared through `engines.dsh`
433
+ (`>=0.2.1-rc.2 <0.3.0`) plus range-bounded `@deepseek-ai/dsh-*` peer
434
+ dependencies: an install preflight refuses any combination the plugin was not
435
+ built against, and the range admits every 0.2.x patch that follows.
361
436
 
362
437
  ## Community
363
438
 
@@ -373,3 +448,21 @@ If the code has expired, open an issue and we will refresh it.
373
448
  ## License
374
449
 
375
450
  MIT
451
+ ## Public relay integration
452
+
453
+ Talk2MyAgent Portal now supports automatic relay registration, a background Connector, publication controls and visitor invitations. This plugin continues to provide the local A2A service with its existing workspace and briefing rules. Relay is deployed independently. See [implementation and tests](docs/a2a-relay-implementation.zh-CN.md) and [Portal English guide](https://github.com/huangjuhua-aigc/Talk2MyAgent/blob/main/docs/relay.en.md).
454
+
455
+ ### Relay integration test
456
+
457
+ `tests/relay.integration.spec.ts` is skipped unless **both** variables point at local checkouts:
458
+
459
+ - `A2A_RELAY_SOURCE` — the [a2a-relay-server](https://github.com/huangjuhua-aigc/a2a-relay-server) repository root
460
+ - `A2A_CONNECTOR_SOURCE` — Talk2MyAgent's `packages/a2a-connector` directory (run `pnpm install` in Talk2MyAgent first)
461
+
462
+ ```sh
463
+ A2A_RELAY_SOURCE=/path/to/a2a-relay-server \
464
+ A2A_CONNECTOR_SOURCE=/path/to/Talk2MyAgent/packages/a2a-connector \
465
+ pnpm vitest run tests/relay.integration.spec.ts
466
+ ```
467
+
468
+ It uses a stub model, temporary data and dynamic ports; no API key or DSH install is needed.
package/README.zh-CN.md CHANGED
@@ -16,8 +16,8 @@
16
16
  <a href="https://www.npmjs.com/package/dsh-a2a-server"><img src="https://img.shields.io/npm/v/dsh-a2a-server?style=flat&label=npm&color=CB3837" alt="npm 版本"></a>
17
17
  <a href="https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-2EA44F?style=flat" alt="MIT License"></a>
18
18
  <img src="https://img.shields.io/badge/A2A-v1.0%20JSON--RPC-4D6BFE?style=flat" alt="A2A v1.0 JSON-RPC 绑定">
19
- <img src="https://img.shields.io/badge/DSH-0.1.0--rc.6-4493F8?style=flat" alt="基于 DSH 0.1.0-rc.6 构建">
20
- <img src="https://img.shields.io/badge/tests-167-2EA44F?style=flat" alt="167 项测试">
19
+ <img src="https://img.shields.io/badge/DSH-0.2.1--rc.2-4493F8?style=flat" alt="基于 DSH 0.2.1-rc.2 构建">
20
+ <img src="https://img.shields.io/badge/tests-217-2EA44F?style=flat" alt="217 项测试">
21
21
  </p>
22
22
 
23
23
  `dsh-a2a-server` 让 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
@@ -30,12 +30,16 @@ subagent provider。它是 `ctx.agents` 之上的传输适配层,不是能力
30
30
 
31
31
  ## 安装
32
32
 
33
+ 需要本机配置界面或跨网络访问时,先看配套 [Talk2MyAgent 使用说明](https://github.com/huangjuhua-aigc/Talk2MyAgent)。
34
+ 访客读取权限、自动简报、主人 IPC 与 Tailscale 部署详见[部署与迁移指南](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.md)。
35
+
33
36
  ```sh
34
- dsh plugin --profile web add dsh-a2a-server
37
+ dsh plugin --profile web add dsh-a2a-server@0.4.0
35
38
  ```
36
39
 
37
40
  `dsh plugin` 会在 profile 目录里转发给 pnpm,并把这个 bundle 追加进
38
- `dsh.profile.bundles`——因为包中声明了 `dsh.bundle`。
41
+ `dsh.profile.bundles`——因为包中声明了 `dsh.bundle`。自 DSH v0.2.1-alpha.1 起,
42
+ `--profile` 必须放在所有 pnpm 参数之前——上面的写法已经是正确的。
39
43
 
40
44
  若想直接使用本地检出,把路径指过去即可:
41
45
 
@@ -48,12 +52,27 @@ dsh plugin --profile web add ./path/to/dsh-a2a-server
48
52
  | `ctx.agents` | `dsh-base` |
49
53
  | `ctx.credentials` | `dsh-base` |
50
54
  | `ctx.webServer` | **`dsh-web-app`** |
51
- | `ctx.sessionProjections`(可选) | 组合层;启用后 `GetTask` 在任务结算后仍可应答 |
55
+ | `ctx.tools` 和 `ctx.systemPrompt` | 必需的访客工具及提示词隔离服务 |
56
+ | `ctx.sessionProjections`(可选) | 组合层;提供任务生命周期投影 |
52
57
 
53
- 三个必需服务齐备之前,插件保持 PENDING。`ctx.webServer` 由 `dsh-web-app` 提供而不在
58
+ 必需服务齐备之前,插件保持 PENDING。`ctx.webServer` 由 `dsh-web-app` 提供而不在
54
59
  `dsh-base` 中,因此 `headless` profile 需要先挂载 `@deepseek-ai/dsh-host-webserver`。
55
60
  `dsh --profile <name> --dump-config` 可以打印组合出来的配置行。
56
61
 
62
+ ### 升级到 0.4.0
63
+
64
+ 0.4.0 适配 DSH `>=0.2.1-rc.2 <0.3.0`。先停止 agent,将
65
+ `dsh-a2a-server@0.4.0` 安装到原有 profile,再重新启动;配套 Portal 也需更新。
66
+ 主人 RPC 和监控已改为需要认证的本机 IPC,公网 HTTP 不接受主人凭据。
67
+ 访客可聊天并读取授权目录,其他工具关闭;空白名单 `trustedPeers: []` 拒绝所有访客。
68
+ 任务结果与空闲会话在保留期内可跨驱逐和重启恢复。
69
+
70
+ 新版 [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent) 默认开启每小时简报更新,
71
+ 用一个开关控制,完成后直接写入 `.hop/handoff.md`。单独安装协议插件而未配置 `briefing`
72
+ 不会自动执行简报任务。
73
+
74
+ Portal 自动准备 Microsandbox,为每位访客提供独立沙箱。公共资料使用只读副本并排除 `node_modules`,私有文件保存在独立持久磁盘;空闲 30 秒暂停、10 分钟停止。独立 DSH 的旧配置保留原执行模式,安装插件本身不会自动启用 VM。详见[沙箱指南](docs/visitor-isolation.md)和 [0.4.0 发布说明](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/CHANGELOG.zh-CN.md)。
75
+
57
76
  ## 快速开始
58
77
 
59
78
  仓库自带的示例组合会运行真实模型并启动一个监听服务。
@@ -99,7 +118,7 @@ curl -s http://127.0.0.1:9922/a2a \
99
118
  | `A2A_PEERS` | `alice` | `名字[:token]` 列表;每个 peer 都必须有 token |
100
119
  | `A2A_PORT` | `9900` | 监听端口 |
101
120
  | `A2A_SEND_MODE` | `block` | `block` 或 `immediate` |
102
- | `A2A_WORKSPACE_ROOT` | 临时目录 | per-peer 工作目录的父目录 |
121
+ | `A2A_WORKSPACE_ROOT` | 临时目录 | 公共资料目录;访客工作区在此目录之外 |
103
122
  | `DEEPSEEK_MODEL` | `deepseek-v4-flash` | 向适配器请求的模型 id |
104
123
 
105
124
  一次性探针会对运行中的服务执行 67 项检查,任何一项不符即以非零码退出:
@@ -116,13 +135,15 @@ node example/probe.mjs http://127.0.0.1:9922 demo123
116
135
  | `/.well-known/agent-card.json` | GET | 默认公开 |
117
136
  | `/.well-known/agent.json` | GET | 默认公开 |
118
137
  | `{basePath}`(默认 `/a2a`) | POST | 必须携带 Bearer |
138
+ | `{basePath}/admin/contexts[/<id>]` | GET | 仅本机 IPC + admin token,公网 HTTP 不挂载 |
139
+ | `{basePath}/admin/prepare-stop` | POST | 仅本机 IPC + admin token;收尾任务、停止沙箱后确认可结束 DSH |
119
140
 
120
141
  | JSON-RPC 方法 | 状态 |
121
142
  | --- | --- |
122
143
  | `SendMessage` | 是否等待逐请求协商 |
123
144
  | `SendStreamingMessage` | SSE |
124
145
  | `GetTask` | 幂等;结算后仍可应答 |
125
- | `ListTasks` | 游标分页、可过滤;仅覆盖常驻的 context |
146
+ | `ListTasks` | 游标分页、可过滤;包括保留期内的持久化任务 |
126
147
  | `CancelTask` | 取消正在执行的 turn |
127
148
  | `SubscribeToTask` | 续订活任务;终态任务返回 `-32004` |
128
149
  | `GetExtendedAgentCard` | 已认证 peer 可见的额外 skill;未配置时 `-32007` |
@@ -171,12 +192,12 @@ part 是被**删除**而不是做了别名:本服务写出的每一个回包
171
192
 
172
193
  card:
173
194
  name: dsh-harness
174
- description: 读代码、执行命令、给出结论。
195
+ description: 读取授权目录中的代码和资料,回答访客问题。
175
196
  public: true
176
197
  skills:
177
198
  - id: general
178
199
  name: general
179
- description: 通用任务执行。
200
+ description: 对话与授权目录内的文件读取。
180
201
  tags: [coding, research]
181
202
  provider:
182
203
  organization: Example Inc.
@@ -213,16 +234,23 @@ part 是被**删除**而不是做了别名:本服务写出的每一个回包
213
234
  | `card.skills` | `[]` | 声明的 skills;为空时回退到一条 `general` |
214
235
  | `card.extendedSkills` | `[]` | 只对已认证 peer 经 `GetExtendedAgentCard` 公开的 skills |
215
236
  | `peers` | `{}` | 身份 → 凭据**引用名** |
216
- | `trustedPeers` | 全部已认证身份 | 允许执行任务的身份白名单 |
237
+ | `trustedPeers` | 全部已认证身份 | 对话与受限文件读取白名单;`[]` 拒绝所有访客 |
217
238
  | `rateLimitPerMinute` | `60` | 按身份的滑动窗口 |
218
239
  | `maxContextTurns` | `5` | 单个 context 接受的消息数上限,超出后 `rejected` |
219
240
  | `sendMode` | `block` | 客户端未表态时的默认行为 |
220
- | `blockTimeoutMs` | `60000` | 超过后拒绝继续阻塞 |
241
+ | `blockTimeoutMs` | `60000` | 最长阻塞等待;到期返回任务当前状态,客户端可继续查询 |
221
242
  | `contextIdleTtlMs` | `1800000` | context 的 agent 被释放前的空闲时长 |
222
243
  | `maxResidentContexts` | `64` | 常驻 context 数量上限 |
223
- | `isolation.workspaceMode` | `per-peer` | `per-peer` 或 `shared` |
224
- | `isolation.workspaceRoot` | — | 必填;父目录或共享 cwd |
225
- | `isolation.peerWorkspaces` | `{}` | 按身份覆盖工作目录 |
244
+ | `isolation.workspaceMode` | `per-peer` | 旧配置迁移字段;访客均使用外置私有 cwd |
245
+ | `isolation.workspaceRoot` | — | 必填;只放公共资料 |
246
+ | `isolation.peerWorkspaces` | `{}` | 旧版按访客覆盖读取根目录 |
247
+ | `isolation.privateRoot` | 用户主目录下按部署分隔 | 公共资料之外的私有工作区父目录 |
248
+ | `isolation.execution` | `read-only` | 设为 `microsandbox` 使用独立访客 VM(Portal 默认);兼容 `docker` |
249
+ | `preamble.files` | `[".hop/handoff.md"]` | 主人背景简报文件(**agent 级**:相对工作区根目录解析,全体访客共用一份;绝对路径直通),合并进每个 context 的首条访客消息;文件不存在则跳过 |
250
+ | `briefing.prompt` | — | 可选的单次简报更新指令,由 Portal 配置,通过有执行预算的主人 agent 每小时运行 |
251
+ | `briefing.settingsFile` | — | 绝对路径;读取 `{ "enabled": boolean }` 设置,缺省开启,每 5 秒检查 |
252
+ | `admin.tokenEnv` | — | 本机主人 RPC 和监控的凭据引用,公网 RPC 拒绝此凭据 |
253
+ | `admin.socketPath` | 按部署生成 | Windows 命名管道或私有 Unix socket |
226
254
  | `push.enabled` | `false` | 保留字段,见[边界](#边界) |
227
255
 
228
256
  以下情况在加载期即被拒绝:缺少 `isolation.workspaceRoot`;peer 名不匹配
@@ -240,7 +268,8 @@ peers:
240
268
 
241
269
  ```yaml
242
270
  # ~/.dsh/.credentials.yaml
243
- A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
271
+ refs:
272
+ A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
244
273
  ```
245
274
 
246
275
  `ctx.credentials` 在每次请求时跨四层解析该引用——进程环境、托管文档、`<cwd>/.env`、
@@ -254,11 +283,18 @@ A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
254
283
  | --- | --- | --- |
255
284
  | 模型上下文 | 一个 peer 的对话不会进入另一个 peer 的模型请求 | 不同 `contextId` → 不同 Session → 不同 log |
256
285
  | 协议访问 | peer 无法读取、续接或取消他人的 context 与任务 | 按认证身份判定归属 |
257
- | 工具层 | peer 的 agent 无法借工具读取他人的 session | `workspaceMode: per-peer` |
286
+ | 工具层 | 仅提供受控 `read_file`,不开放写入、命令、联网或跨会话工具 | 屏蔽继承工具、专用读取工具、执行校验 |
287
+ | 提示词 | 访客不继承可选的主人个性化提示与运行时上下文 | 独立的公共系统提示词 |
288
+
289
+ 访客 cwd 现在放在 `workspaceRoot` 之外。所选目录只放公共资料,`read_file` 默认读取它,
290
+ 传入 `root: "workspace"` 则读取自己的私有工作区。Portal 自动准备 Microsandbox,每位访客独立 VM,
291
+ 公共资料副本只读、自己的持久化磁盘可写;空闲 30 秒暂停、10 分钟停止,文件保留,不挂后端聊天记录。
292
+ 详见[安装、迁移与隔离边界](docs/visitor-isolation.md)。
258
293
 
259
- `per-peer`(默认)依据 `workspaceRoot` 为每个身份派生独立 `cwd`。`shared` 则把所有 peer
260
- 放进同一个目录,适用于协作维护同一个仓库的场景;此模式下一个 peer 写入的文件对其他 peer
261
- 可读。
294
+ `read_file({"path":"."})` 列出资料目录;文件支持 UTF-8 文本,按 `offset`/`limit`
295
+ 分页。单文件上限 2 MiB,单次最多 32000 字符、500 行/项,目录扫描最多 1000 项。
296
+ 拒绝越界、符号链接/junction、硬链接和 Windows ADS。主人维护能力使用独立本机 IPC,
297
+ 保留可信管理工具,不能从公网入口调用。资源配额的完整配置表见英文 README。
262
298
 
263
299
  ## 边界
264
300
 
@@ -268,12 +304,14 @@ A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
268
304
  `pushNotifications: false`。
269
305
  - 不支持 `stateTransitionHistory`、协议扩展与 Card 签名。
270
306
  - 流式只推送已提交的 assistant 消息,未实现逐 chunk 推送。
271
- - 无孤儿任务看门狗:卡在非终态的任务会一直保持该状态。
272
- - 未实现跨会话工具拒绝;隔离依赖 `workspaceMode`。
307
+ - 有并发准入、任务期限及模型调用预算;超时后等待底层适配器完成取消才释放执行名额。
308
+ 不响应取消的适配器仍需进程级监管,这些额度不是按货币计费的预算。
309
+ - 访客仅能使用有目录边界的 `read_file`;改变沙箱模式不会自动开放其他工具。
310
+ 该工具边界不替代针对恶意本机管理员的操作系统隔离。
273
311
  - 触及 token 上限的任务结算为 `completed`,真实的 turn 结束原因放在
274
312
  `Task.metadata.dsh.stopReason`——A2A 的状态枚举无法表达它。
275
- - 任务状态可以跨结算存活,但无法跨进程重启:未组合 session 持久化,重启后 projection
276
- 没有日志可供冷折叠。
313
+ - 任务结果与空闲会话在保留期内可跨进程重启恢复;中断的任务标记失败,不重放。
314
+ - 每个 context 同时只允许一个任务,并发提交返回 409;messageId 不是提交去重键。
277
315
  - `ctx.webServer` 不提供 TLS。任何非回环地址的暴露都应置于反向代理之后。
278
316
  - 修改配置会重启插件并取消进行中的任务。
279
317
 
@@ -293,6 +331,10 @@ src/
293
331
  ├── tasks.ts 任务槽位与三段式 turn 关联
294
332
  ├── projection.ts 在 session log 上折叠出的 a2aTask 读模型
295
333
  ├── security.ts 认证、限流、注入去势、外发脱敏
334
+ ├── workspace-read.ts 授权目录内受限的 read_file
335
+ ├── management.ts 本机 IPC 主人 RPC 与监控
336
+ ├── state.ts 持久化任务及对话 checkpoint
337
+ ├── briefing.ts 每小时简报更新与持久化调度
296
338
  ├── config.ts schema,以及加载期就会拒绝的跨字段校验
297
339
  └── types.ts 向 SessionEventMap / MessageSourceMap 的声明合并
298
340
  ```
@@ -306,17 +348,18 @@ src/
306
348
  历史。
307
349
 
308
350
  **驻留是显式管理的。** HTTP 没有连接生命周期,因此每个 `contextId` 对应一个 Activation,
309
- 空闲后被驱逐,持久化的 Session 留在原地。
351
+ 空闲后被驱逐。私有 checkpoint 保留任务结果与对话历史,冷恢复时创建新的 harness session。
310
352
 
311
353
  ## 开发
312
354
 
313
355
  ```sh
314
356
  pnpm install
315
357
  pnpm typecheck
316
- pnpm test # 10 个文件共 167 项测试
358
+ pnpm test # 15 个文件共 217 项测试
317
359
  pnpm serve # 启动监听服务
318
360
  pnpm probe # 对运行中的服务执行 67 项检查
319
361
  pnpm build # 产出 lib/
362
+ pnpm test:package # 仅声明运行依赖时验证构建产物导入
320
363
  ```
321
364
 
322
365
  端到端测试会启动真实的 Cordis 组合与真实的 agent loop,并通过 HTTP 驱动它;模型使用
@@ -333,8 +376,10 @@ pnpm build # 产出 lib/
333
376
  - A2A task 与 harness turn 之间的映射
334
377
  - 按 peer 的认证、隔离与工作目录策略
335
378
 
336
- harness 处于 pre-release 阶段,不承诺跨重命名或重新打包的兼容性,因此 peer 依赖精确锁定
337
- 在 `0.1.0-rc.6`。
379
+ harness 处于 pre-release 阶段,不承诺跨重命名或重新打包的兼容性,因此兼容性通过
380
+ `engines.dsh`(`>=0.2.1-rc.2 <0.3.0`)加上按 range 声明的 `@deepseek-ai/dsh-*`
381
+ peer 依赖来表达:安装预检会拒绝任何插件未曾适配过的组合,而这个 range 也放行后续
382
+ 所有的 0.2.x 补丁版本。
338
383
 
339
384
  ## 社区交流
340
385
 
@@ -349,3 +394,21 @@ harness 处于 pre-release 阶段,不承诺跨重命名或重新打包的兼
349
394
  ## 许可
350
395
 
351
396
  MIT
397
+ ## 公网中继集成
398
+
399
+ Talk2MyAgent Portal 已实现自动注册 Relay、后台 Connector、发布开关和访客邀请。插件仍提供本地 A2A 接口,文件权限和公开简报逻辑保持原样。中继服务单独部署;实现状态与测试命令见 [中继实现记录](docs/a2a-relay-implementation.zh-CN.md)。
400
+
401
+ ### 中继集成测试
402
+
403
+ `tests/relay.integration.spec.ts` 需要**同时**设置以下两个变量才会运行,否则跳过:
404
+
405
+ - `A2A_RELAY_SOURCE`:[a2a-relay-server](https://github.com/huangjuhua-aigc/a2a-relay-server) 仓库根目录
406
+ - `A2A_CONNECTOR_SOURCE`:Talk2MyAgent 的 `packages/a2a-connector` 目录(先在 Talk2MyAgent 执行 `pnpm install`)
407
+
408
+ ```sh
409
+ A2A_RELAY_SOURCE=/path/to/a2a-relay-server \
410
+ A2A_CONNECTOR_SOURCE=/path/to/Talk2MyAgent/packages/a2a-connector \
411
+ pnpm vitest run tests/relay.integration.spec.ts
412
+ ```
413
+
414
+ 测试使用桩模型、临时数据和动态端口,无需 API 密钥或安装 DSH。