dsh-a2a-server 0.1.1 → 0.3.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 (86) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/CHANGELOG.zh-CN.md +28 -0
  3. package/README.md +151 -54
  4. package/README.zh-CN.md +113 -53
  5. package/cordis.patch.yml +10 -6
  6. package/lib/briefing.d.ts +39 -0
  7. package/lib/briefing.d.ts.map +1 -0
  8. package/lib/briefing.js +108 -0
  9. package/lib/briefing.js.map +1 -0
  10. package/lib/config.d.ts +69 -22
  11. package/lib/config.d.ts.map +1 -1
  12. package/lib/config.js +76 -6
  13. package/lib/config.js.map +1 -1
  14. package/lib/contexts.d.ts +9 -4
  15. package/lib/contexts.d.ts.map +1 -1
  16. package/lib/contexts.js +7 -13
  17. package/lib/contexts.js.map +1 -1
  18. package/lib/index.d.ts +1 -26
  19. package/lib/index.d.ts.map +1 -1
  20. package/lib/index.js +420 -408
  21. package/lib/index.js.map +1 -1
  22. package/lib/management.d.ts +5 -0
  23. package/lib/management.d.ts.map +1 -0
  24. package/lib/management.js +74 -0
  25. package/lib/management.js.map +1 -0
  26. package/lib/projection.d.ts +67 -22
  27. package/lib/projection.d.ts.map +1 -1
  28. package/lib/projection.js +30 -11
  29. package/lib/projection.js.map +1 -1
  30. package/lib/protocol/card.d.ts +25 -7
  31. package/lib/protocol/card.d.ts.map +1 -1
  32. package/lib/protocol/card.js +35 -14
  33. package/lib/protocol/card.js.map +1 -1
  34. package/lib/protocol/index.d.ts +10 -10
  35. package/lib/protocol/index.d.ts.map +1 -1
  36. package/lib/protocol/index.js +8 -8
  37. package/lib/protocol/index.js.map +1 -1
  38. package/lib/protocol/jsonrpc.d.ts +54 -6
  39. package/lib/protocol/jsonrpc.d.ts.map +1 -1
  40. package/lib/protocol/jsonrpc.js +70 -8
  41. package/lib/protocol/jsonrpc.js.map +1 -1
  42. package/lib/protocol/parse.d.ts +79 -0
  43. package/lib/protocol/parse.d.ts.map +1 -0
  44. package/lib/protocol/parse.js +249 -0
  45. package/lib/protocol/parse.js.map +1 -0
  46. package/lib/protocol/sse.d.ts +6 -3
  47. package/lib/protocol/sse.d.ts.map +1 -1
  48. package/lib/protocol/sse.js +4 -2
  49. package/lib/protocol/sse.js.map +1 -1
  50. package/lib/protocol/wire.d.ts +165 -54
  51. package/lib/protocol/wire.d.ts.map +1 -1
  52. package/lib/protocol/wire.js +54 -9
  53. package/lib/protocol/wire.js.map +1 -1
  54. package/lib/router.d.ts +55 -1
  55. package/lib/router.d.ts.map +1 -1
  56. package/lib/router.js +419 -137
  57. package/lib/router.js.map +1 -1
  58. package/lib/security.d.ts +37 -3
  59. package/lib/security.d.ts.map +1 -1
  60. package/lib/security.js +48 -4
  61. package/lib/security.js.map +1 -1
  62. package/lib/state.d.ts +37 -0
  63. package/lib/state.d.ts.map +1 -0
  64. package/lib/state.js +146 -0
  65. package/lib/state.js.map +1 -0
  66. package/lib/tasks.d.ts +4 -0
  67. package/lib/tasks.d.ts.map +1 -1
  68. package/lib/tasks.js +23 -5
  69. package/lib/tasks.js.map +1 -1
  70. package/lib/types.d.ts +12 -3
  71. package/lib/types.d.ts.map +1 -1
  72. package/lib/types.js +1 -1
  73. package/lib/types.js.map +1 -1
  74. package/lib/version.d.ts +20 -0
  75. package/lib/version.d.ts.map +1 -0
  76. package/lib/version.js +20 -0
  77. package/lib/version.js.map +1 -0
  78. package/lib/workspace-read.d.ts +9 -0
  79. package/lib/workspace-read.d.ts.map +1 -0
  80. package/lib/workspace-read.js +152 -0
  81. package/lib/workspace-read.js.map +1 -0
  82. package/package.json +39 -30
  83. package/lib/protocol/normalize.d.ts +0 -72
  84. package/lib/protocol/normalize.d.ts.map +0 -1
  85. package/lib/protocol/normalize.js +0 -278
  86. package/lib/protocol/normalize.js.map +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ [English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
4
+
5
+ ## 0.3.0
6
+
7
+ Requires DeepSeek Harness `>=0.2.1-rc.2 <0.3.0` and speaks A2A 1.0 JSON-RPC.
8
+
9
+ - 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.
10
+ - Move owner RPC and monitoring to authenticated local IPC. Public HTTP accepts visitor credentials only. An empty `trustedPeers` list denies all visitors.
11
+ - 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.
12
+ - Persist task results and idle conversation history across eviction and restart within the retention window. Fail interrupted work without replaying it.
13
+ - 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`.
14
+ - Declare required runtime packages and verify imports without development dependencies. Preserve the guard against malformed Unicode from upstream streaming decoders.
15
+
16
+ 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.
17
+
18
+ ### Upgrade
19
+
20
+ Stop the agent, update dsh if needed, then install into the existing profile:
21
+
22
+ ```sh
23
+ dsh plugin --profile YOUR_PROFILE add dsh-a2a-server@0.3.0
24
+ ```
25
+
26
+ 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.
27
+
28
+ 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,28 @@
1
+ # 更新日志
2
+
3
+ [English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
4
+
5
+ ## 0.3.0
6
+
7
+ 要求 DeepSeek Harness `>=0.2.1-rc.2 <0.3.0`,使用 A2A 1.0 JSON-RPC。
8
+
9
+ - 访客可以聊天,并通过 `read_file` 读取主人指定的资料目录。拦截路径越界、链接、插件私有状态及其他工具;公共模型提示与主人运行时上下文分离。
10
+ - 主人 RPC 和监控迁到需要认证的本机 IPC;公网 HTTP 仅接受访客凭据。空白名单 `trustedPeers: []` 拒绝全部访客。
11
+ - 限制并发、每日任务数、执行时限、模型调用、输出和流缓冲;同一 context 的并发请求不会串答,SSE 断开后释放订阅。
12
+ - 在保留期内持久化任务结果与空闲对话,支持驱逐和重启后的恢复。重启中断的任务标记失败,不自动重放。
13
+ - 增加由 Portal 配置的自动简报循环:默认每小时更新、一个持久化开关、重启去重,完成后直接写入 `.hop/handoff.md`。
14
+ - 补齐运行依赖声明,验证脱离开发依赖后可导入。保留上游流式解码异常的 Unicode 防护。
15
+
16
+ 验证:15 个文件共 217 项测试、类型检查、构建和仅使用声明运行依赖的包导入检查。测试使用确定性模型;真实模型与跨网络部署需要单独验收。
17
+
18
+ ### 升级
19
+
20
+ 停止 agent,必要时升级 dsh,然后更新原有 profile 中的插件:
21
+
22
+ ```sh
23
+ dsh plugin --profile YOUR_PROFILE add dsh-a2a-server@0.3.0
24
+ ```
25
+
26
+ 将 `YOUR_PROFILE` 替换为已保存的 profile 名称,安装后重新启动。配套 [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent) 也需要更新,以使用私有管理入口和自动简报开关。升级已有 profile 时不要再次使用 `--from-default-profile`。
27
+
28
+ 原来的 HTTP 主人/监控调用必须迁移至本机 IPC。部署前检查访客许可与资料目录,详见[迁移指南](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.md)。
package/README.md CHANGED
@@ -15,14 +15,14 @@
15
15
  <p align="center">
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
- <img src="https://img.shields.io/badge/A2A-v0.3.0%20JSON--RPC-4D6BFE?style=flat" alt="A2A v0.3.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-129-2EA44F?style=flat" alt="129 tests">
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.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)
24
24
  agent reachable over the [A2A (Agent2Agent)](https://a2a-protocol.org) protocol. It
25
- serves an Agent Card at a well-known URI and implements the **v0.3.0 JSON-RPC
25
+ serves an Agent Card at a well-known URI and implements the **v1.0 JSON-RPC
26
26
  binding**, so any compliant peer that knows the deployment's URL can discover the
27
27
  agent and submit tasks to it.
28
28
 
@@ -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.3.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 `tasks/get` 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 from 0.2.x
69
+
70
+ Version 0.3.0 requires DSH `>=0.2.1-rc.2 <0.3.0`. Stop the agent, install
71
+ `dsh-a2a-server@0.3.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
+ See the [0.3.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.
@@ -93,9 +115,9 @@ curl -s http://127.0.0.1:9922/.well-known/agent-card.json
93
115
  curl -s http://127.0.0.1:9922/a2a \
94
116
  -H "authorization: Bearer demo123" \
95
117
  -H 'content-type: application/json' \
96
- -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{
97
- "message":{"kind":"message","messageId":"m1","role":"user",
98
- "parts":[{"kind":"text","text":"hello"}]}}}'
118
+ -H 'a2a-version: 1.0' -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{
119
+ "message":{"messageId":"m1","role":"ROLE_USER",
120
+ "parts":[{"text":"hello"}]}}}'
99
121
  ```
100
122
 
101
123
  | Variable | Default | Meaning |
@@ -103,10 +125,10 @@ 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 | Authorized visitor data root and parent of per-peer working directories |
107
129
  | `DEEPSEEK_MODEL` | `deepseek-v4-flash` | Model id requested from the adapter |
108
130
 
109
- A one-shot probe runs 48 checks against a live server and exits non-zero on any
131
+ A one-shot probe runs 67 checks against a live server and exits non-zero on any
110
132
  mismatch:
111
133
 
112
134
  ```sh
@@ -121,22 +143,44 @@ 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 |
124
147
 
125
- | JSON-RPC method | v1.0 alias | Status |
148
+ | JSON-RPC method | Status |
149
+ | --- | --- |
150
+ | `SendMessage` | Waiting negotiated per request |
151
+ | `SendStreamingMessage` | SSE |
152
+ | `GetTask` | Idempotent; answers after settlement |
153
+ | `ListTasks` | Cursor-paginated, filterable, live and retained tasks |
154
+ | `CancelTask` | Cancels the running turn |
155
+ | `SubscribeToTask` | Live stream; `-32004` on a terminal task |
156
+ | `GetExtendedAgentCard` | Extra skills for an authenticated peer; `-32007` when unconfigured |
157
+ | `*TaskPushNotificationConfig` | `-32003` |
158
+
159
+ **Only A2A v1.0 is served.** The v0.3 method names, `kind` discriminators,
160
+ lowercase enums, and nested `file` parts are gone rather than aliased: every
161
+ reply this server writes is v1.0 JSON, which a v0.3 client could not read, so
162
+ answering a v0.3 request would fail further from its cause than refusing it. A
163
+ retired method answers `-32601` naming its replacement, and an explicit
164
+ `A2A-Version: 0.3` answers `-32009` (`VersionNotSupportedError`). A request with
165
+ no `A2A-Version` header is served as 1.0 — the only version this interface
166
+ declares on its card.
167
+
168
+ What that means concretely, against v0.3:
169
+
170
+ | Concern | v0.3 | v1.0 as served here |
126
171
  | --- | --- | --- |
127
- | `message/send` | `SendMessage` | Blocking negotiated per request |
128
- | `message/stream` | `SendStreamingMessage` | SSE |
129
- | `tasks/get` | `GetTask` | Idempotent; answers after settlement |
130
- | `tasks/cancel` | `CancelTask` | Cancels the running turn |
131
- | `tasks/resubscribe` | `SubscribeToTask` | Live stream, or one terminal frame |
132
- | `tasks/pushNotificationConfig/*` | `*TaskPushNotificationConfig` | `-32003` |
133
- | `tasks/list` | `ListTasks` | `-32601` |
134
- | `agent/getAuthenticatedExtendedCard` | `GetExtendedAgentCard` | `-32601` |
135
-
136
- Both dialects are accepted. v0.3 (`message/send`, `"working"`, `kind`-tagged
137
- parts) is the mainline; the v1.0 spellings (`SendMessage`, `TASK_STATE_WORKING`,
138
- member-presence parts) are normalized inbound and rendered back in whichever
139
- dialect the request used.
172
+ | Task reference | `params.taskId` | `params.id` |
173
+ | `SendMessage` result | bare `Task` | `{ "task": … }` |
174
+ | Task state | `"working"` | `"TASK_STATE_WORKING"` |
175
+ | Message role | `"user"` | `"ROLE_USER"` |
176
+ | Part | `{"kind":"text","text":…}` | `{"text":…}`; file parts flatten to `url`/`raw`/`filename`/`mediaType` |
177
+ | Stream frame | `{"kind":"status-update",…,"final":true}` | `{"statusUpdate":{…}}`; the stream's close is the terminal signal |
178
+ | Stream opening | a non-final status update | the `Task` object itself |
179
+ | Wait control | `configuration.blocking` | `configuration.returnImmediately` (inverted; blocking is the default) |
180
+ | Card endpoint | `url` + `preferredTransport` | `supportedInterfaces[]`, each carrying its own `protocolVersion` |
181
+ | Card auth | `securitySchemes` + `security` | schemes wrapped in `httpAuthSecurityScheme`; `securityRequirements` |
182
+ | Extended card | `supportsAuthenticatedExtendedCard` | `capabilities.extendedAgentCard` |
183
+ | A2A error detail | none | `error.data[]` carrying a `google.rpc.ErrorInfo` with `reason` and `domain` |
140
184
 
141
185
  Inbound messages may carry text, file, and data parts. File and data parts are
142
186
  rendered into the model's context as bracketed references. Replies are text.
@@ -153,19 +197,23 @@ another peer answers exactly as an absent one.
153
197
  config:
154
198
  basePath: /a2a
155
199
  publicUrl: https://agents.example.com/a2a # advertised on the card
156
- protocolVersion: 0.3.0
157
200
  provider: deepseek-official
158
201
  model: deepseek-v4-flash
159
202
 
160
203
  card:
161
204
  name: dsh-harness
162
- description: Reads code, runs commands, reports findings.
205
+ description: Answers questions using code and text in the authorized directory.
163
206
  public: true
164
207
  skills:
165
208
  - id: general
166
209
  name: general
167
- description: General-purpose task execution.
210
+ description: Chat and scoped file reading.
168
211
  tags: [coding, research]
212
+ extendedSkills: # authenticated peers only
213
+ - id: internal
214
+ name: internal diagnostics
215
+ description: Explains project documentation from the authorized directory.
216
+ tags: [internal]
169
217
  provider:
170
218
  organization: Example Inc.
171
219
  url: https://example.com
@@ -196,21 +244,36 @@ another peer answers exactly as an absent one.
196
244
  | --- | --- | --- |
197
245
  | `basePath` | `/a2a` | JSON-RPC route |
198
246
  | `publicUrl` | derived from `Host` | Routable URL published on the card |
199
- | `protocolVersion` | `0.3.0` | Version advertised on the card |
200
247
  | `provider` · `model` | — | Model route for every agent this server creates |
201
248
  | `card.public` | `true` | Serve the card without a credential |
202
249
  | `card.skills` | `[]` | Declared skills; falls back to one `general` entry |
250
+ | `card.extendedSkills` | `[]` | Skills revealed only through `GetExtendedAgentCard` |
203
251
  | `peers` | `{}` | Identity → credential **reference name** |
204
- | `trustedPeers` | all authenticated | Allow-list of identities that may run tasks |
252
+ | `trustedPeers` | all authenticated | Access allow-list for conversation and scoped file reads; `[]` denies everyone |
205
253
  | `rateLimitPerMinute` | `60` | Sliding window per identity |
206
254
  | `maxContextTurns` | `5` | Messages accepted per context before `rejected` |
207
- | `sendMode` | `block` | Default when the client states no preference |
208
- | `blockTimeoutMs` | `60000` | After which a blocking request is declined |
255
+ | `sendMode` | `block` | Default when the client sends no `returnImmediately` |
256
+ | `blockTimeoutMs` | `60000` | Maximum blocking wait; returns the current task on timeout so the client can poll |
209
257
  | `contextIdleTtlMs` | `1800000` | Idle time before a context's agent is released |
210
258
  | `maxResidentContexts` | `64` | Ceiling on resident contexts |
259
+ | `maxConcurrentTasks` / `maxConcurrentTasksPerPeer` | `8` / `2` | Atomic task admission; excess requests receive 429 |
260
+ | `maxTasksPerPeerPerDay` | `200` | Persisted task allowance per identity per UTC day |
261
+ | `taskTimeoutMs` | `120000` | Cancels execution when its deadline expires |
262
+ | `maxModelCallsPerTask` / `maxOutputTokens` | `8` / `4096` | Model request and per-request output limits |
263
+ | `maxInputChars` / `maxOutputChars` | `32000` / `64000` | Input/output text limits |
264
+ | `streamHeartbeatMs` / `maxStreamsPerTask` | `15000` / `4` | Heartbeat interval and subscription cap |
265
+ | `maxStreamBufferBytes` | `262144` | Disconnect a stream whose buffered frame exceeds the cap |
266
+ | `storage.directory` | `~/.dsh-a2a/<deployment-key>` | Private checkpoints; one process per directory |
267
+ | `storage.retentionMs` / `storage.maxContexts` | `86400000` / `256` | Retention after last activity, and maximum stored contexts |
268
+ | `storage.maxContextBytes` | `4000000` | Maximum checkpoint bytes per context |
211
269
  | `isolation.workspaceMode` | `per-peer` | `per-peer` or `shared` |
212
- | `isolation.workspaceRoot` | — | Required; parent directory or shared cwd |
213
- | `isolation.peerWorkspaces` | `{}` | Per-identity working-directory override |
270
+ | `isolation.workspaceRoot` | — | Required; authorized shared read-only data root and parent directory or shared cwd |
271
+ | `isolation.peerWorkspaces` | `{}` | Per-identity cwd and authorized read-root override |
272
+ | `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 |
273
+ | `briefing.prompt` | — | Optional single-update instruction executed as the bounded local owner at startup and hourly; configured by Portal, requires owner administration |
274
+ | `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 |
275
+ | `admin.tokenEnv` | — | Credential reference for local owner and monitoring requests; rejected by public RPC |
276
+ | `admin.socketPath` | deployment-specific | Windows named pipe or private Unix socket for owner RPC and monitoring |
214
277
  | `push.enabled` | `false` | Reserved; see [Boundaries](#boundaries) |
215
278
 
216
279
  Configuration is refused at load when `isolation.workspaceRoot` is absent, a peer
@@ -229,7 +292,8 @@ peers:
229
292
 
230
293
  ```yaml
231
294
  # ~/.dsh/.credentials.yaml
232
- A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
295
+ refs:
296
+ A2A_PEER_ALICE: <32-byte-hex-from-openssl-rand>
233
297
  ```
234
298
 
235
299
  `ctx.credentials` resolves the reference per request across four layers — the
@@ -245,11 +309,29 @@ identities.
245
309
  | --- | --- | --- |
246
310
  | Model context | One peer's conversation cannot enter another's model request | Separate `contextId` → separate Session → separate log |
247
311
  | Protocol access | A peer cannot read, continue, or cancel another's context or task | Ownership by authenticated identity |
248
- | Tooling | A peer's agent cannot use tools to read another peer's session | `workspaceMode: per-peer` |
312
+ | Tooling | Only the scoped `read_file` is available; no writes, shell, network or cross-session tools | Inherited-tool masking, scoped reader, and execution guard |
313
+ | Prompt context | Public agents have a dedicated system prompt and suppress optional host runtime context | Scoped complete prompt and context suppression |
249
314
 
250
315
  `per-peer` (the default) derives each identity's `cwd` from `workspaceRoot`.
251
- `shared` places every peer in one directory, which suits peers collaborating on a
252
- single repository; under it, files written by one peer are readable by another.
316
+ `shared` places every peer in one directory. These are working-directory choices,
317
+ not filesystem sandboxes. `read_file` reads from the owner-selected
318
+ `workspaceRoot`; a `peerWorkspaces` override restricts that peer to the overridden
319
+ directory. Relative tool paths resolve from this **data root**, not the per-peer
320
+ cwd. These files are available to admitted visitors, so choose the data root
321
+ accordingly. The A2A checkpoint directory is always excluded.
322
+
323
+ `read_file({"path":"."})` lists the root's entries. A file path reads UTF-8 text;
324
+ `offset` (1-based) and `limit` page lines or directory entries. Reads are bounded
325
+ to 2 MiB files, 32,000 returned characters and 500 lines/entries per call; directory
326
+ scans cover at most 1,000 entries. Binary/office-document extraction is not yet
327
+ implemented. Canonical path checks refuse escapes; symlinks, junctions, hard
328
+ links and Windows alternate data streams are rejected. No shell is invoked.
329
+ `preamble.files` continues to provide an optional opening briefing.
330
+
331
+ Owner RPC uses the same `/a2a` path on **local IPC**, authenticated with the admin
332
+ token. It retains the standard caretaker preset. The public HTTP listener accepts
333
+ only visitor credentials and never mounts the monitoring API. See the
334
+ [migration and deployment notes](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.en.md).
253
335
 
254
336
  ## Boundaries
255
337
 
@@ -257,17 +339,23 @@ single repository; under it, files written by one peer are readable by another.
257
339
  - JSONRPC binding only. gRPC and HTTP+JSON are not served, and the card says so.
258
340
  - Push notifications are not implemented. `push.enabled` selects which error the
259
341
  push methods return; the card advertises `pushNotifications: false`.
260
- - No extended Agent Card, `stateTransitionHistory`, protocol extensions, or card
261
- signatures.
342
+ - No `stateTransitionHistory`, protocol extensions, or card signatures.
262
343
  - Streaming emits committed assistant messages. Per-chunk streaming is not
263
344
  implemented.
264
- - No orphan-task watchdog: a task wedged non-terminal stays that way.
265
- - Cross-session tool denial is not implemented; isolation rests on
266
- `workspaceMode`.
345
+ - Execution deadlines cancel the agent. Admission remains occupied until the
346
+ underlying adapter finishes cancellation; an adapter ignoring AbortSignal
347
+ needs process-level supervision. These limits are not a currency budget.
348
+ - Only rooted `read_file` is enabled for visitors. Other tools require separate
349
+ capability work; changing the sandbox mode does not grant those tools. This
350
+ application-level file reader is not an OS sandbox against a malicious local
351
+ administrator mutating the filesystem.
267
352
  - A token ceiling settles a task as `completed`; the real turn ending travels in
268
353
  `Task.metadata.dsh.stopReason`, which A2A's state enum cannot express.
269
- - Task state survives settlement but not a process restart: session persistence
270
- is not composed, so the projection has no log to cold-fold after a reboot.
354
+ - Task results and idle conversation history survive eviction/restart within
355
+ the retention window. Interrupted tasks become failed and are never replayed.
356
+ Cold restoration creates a new harness session behind the stable contextId.
357
+ - One task may run per context; concurrent submissions receive 409. Message IDs
358
+ are not deduplication keys: proxies must not blindly retry submissions.
271
359
  - `ctx.webServer` provides no TLS. Any non-loopback exposure belongs behind a
272
360
  reverse proxy.
273
361
  - A configuration change restarts the plugin and cancels in-flight tasks.
@@ -277,8 +365,8 @@ single repository; under it, files written by one peer are readable by another.
277
365
  ```
278
366
  src/
279
367
  ├── protocol/ dependency-free library: no Cordis, no HTTP, no harness
280
- │ ├── wire.ts the A2A vocabulary, normalized to v0.3 spelling
281
- │ ├── normalize.ts v0.3 <-> v1.0 dialect translation, both directions
368
+ │ ├── wire.ts the A2A v1.0 vocabulary: one shape, no translation
369
+ │ ├── parse.ts inbound parsing and the method table
282
370
  │ ├── jsonrpc.ts framing and the A2A error codes
283
371
  │ ├── card.ts Agent Card construction
284
372
  │ └── sse.ts SSE frame encoding
@@ -288,6 +376,10 @@ src/
288
376
  ├── tasks.ts task slots and the three-stage turn correlation
289
377
  ├── projection.ts the a2aTask fold over the session log
290
378
  ├── security.ts authentication, rate limiting, defanging, redaction
379
+ ├── workspace-read.ts bounded read_file within the authorized data root
380
+ ├── management.ts private owner RPC and monitoring over local IPC
381
+ ├── state.ts durable task and conversation checkpoints
382
+ ├── briefing.ts hourly briefing updates and persistent scheduling
291
383
  ├── config.ts schema plus the cross-field checks that fail at load
292
384
  └── types.ts declaration merges into SessionEventMap / MessageSourceMap
293
385
  ```
@@ -303,17 +395,19 @@ carries the committed output, so the fold serves the answer without reaching
303
395
  back into message history.
304
396
 
305
397
  **Residency is explicit.** HTTP has no connection lifetime, so each `contextId`
306
- maps to an Activation evicted when idle, leaving the durable Session behind.
398
+ maps to an Activation evicted when idle. Private checkpoints retain task results
399
+ and conversation history; cold restoration creates a new harness session.
307
400
 
308
401
  ## Development
309
402
 
310
403
  ```sh
311
404
  pnpm install
312
405
  pnpm typecheck
313
- pnpm test # 129 tests across 9 files
406
+ pnpm test # protocol, lifecycle, security and workspace-read regressions
314
407
  pnpm serve # a listening server
315
- pnpm probe # 48 checks against a running server
408
+ pnpm probe # 67 checks against a running server
316
409
  pnpm build # emit lib/
410
+ pnpm test:package # verify imports with only declared runtime packages
317
411
  ```
318
412
 
319
413
  The end-to-end suites boot a real Cordis composition with a real agent loop and
@@ -327,13 +421,16 @@ Built against [deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deep
327
421
  The official project supplies the agent runtime, the plugin system, and the
328
422
  capability seams this plugin consumes. This project supplies:
329
423
 
330
- - The A2A v0.3.0 JSON-RPC binding, served inbound
331
- - Agent Card construction and dialect normalization
424
+ - The A2A v1.0 JSON-RPC binding, served inbound
425
+ - Agent Card construction and inbound request parsing
332
426
  - Mapping between A2A tasks and harness turns
333
427
  - Per-peer authentication, isolation, and workspace policy
334
428
 
335
429
  The harness is pre-release and does not promise compatibility across renames or
336
- repackaging, so peer dependencies are pinned exactly to `0.1.0-rc.6`.
430
+ repackaging, so compatibility is declared through `engines.dsh`
431
+ (`>=0.2.1-rc.2 <0.3.0`) plus range-bounded `@deepseek-ai/dsh-*` peer
432
+ dependencies: an install preflight refuses any combination the plugin was not
433
+ built against, and the range admits every 0.2.x patch that follows.
337
434
 
338
435
  ## Community
339
436