dsh-a2a-server 0.3.0 → 0.4.1
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.
- package/CHANGELOG.md +45 -28
- package/CHANGELOG.zh-CN.md +45 -28
- package/README.md +42 -16
- package/README.zh-CN.md +42 -13
- package/docker/Dockerfile +5 -0
- package/docs/public-visitors.en.md +39 -0
- package/docs/public-visitors.md +86 -0
- package/docs/visitor-isolation.en.md +50 -0
- package/docs/visitor-isolation.md +74 -0
- package/lib/config.d.ts +12 -4
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +9 -0
- package/lib/config.js.map +1 -1
- package/lib/docker-executor.d.ts +11 -0
- package/lib/docker-executor.d.ts.map +1 -0
- package/lib/docker-executor.js +115 -0
- package/lib/docker-executor.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +92 -12
- package/lib/index.js.map +1 -1
- package/lib/isolation.d.ts +21 -0
- package/lib/isolation.d.ts.map +1 -0
- package/lib/isolation.js +108 -0
- package/lib/isolation.js.map +1 -0
- package/lib/live-visitors.d.ts +21 -0
- package/lib/live-visitors.d.ts.map +1 -0
- package/lib/live-visitors.js +66 -0
- package/lib/live-visitors.js.map +1 -0
- package/lib/management.d.ts.map +1 -1
- package/lib/management.js +3 -1
- package/lib/management.js.map +1 -1
- package/lib/microsandbox-executor.d.ts +39 -0
- package/lib/microsandbox-executor.d.ts.map +1 -0
- package/lib/microsandbox-executor.js +316 -0
- package/lib/microsandbox-executor.js.map +1 -0
- package/lib/microsandbox-runtime.d.ts +28 -0
- package/lib/microsandbox-runtime.d.ts.map +1 -0
- package/lib/microsandbox-runtime.js +164 -0
- package/lib/microsandbox-runtime.js.map +1 -0
- package/lib/protocol/card.d.ts +1 -0
- package/lib/protocol/card.d.ts.map +1 -1
- package/lib/protocol/card.js +3 -0
- package/lib/protocol/card.js.map +1 -1
- package/lib/public-visitors.d.ts +38 -0
- package/lib/public-visitors.d.ts.map +1 -0
- package/lib/public-visitors.js +107 -0
- package/lib/public-visitors.js.map +1 -0
- package/lib/router.d.ts +15 -0
- package/lib/router.d.ts.map +1 -1
- package/lib/router.js +122 -9
- package/lib/router.js.map +1 -1
- package/lib/sandbox-files.d.ts +16 -0
- package/lib/sandbox-files.d.ts.map +1 -0
- package/lib/sandbox-files.js +168 -0
- package/lib/sandbox-files.js.map +1 -0
- package/lib/sandbox-lifecycle.d.ts +48 -0
- package/lib/sandbox-lifecycle.d.ts.map +1 -0
- package/lib/sandbox-lifecycle.js +125 -0
- package/lib/sandbox-lifecycle.js.map +1 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/visitor-read.d.ts +4 -0
- package/lib/visitor-read.d.ts.map +1 -0
- package/lib/visitor-read.js +20 -0
- package/lib/visitor-read.js.map +1 -0
- package/lib/visitor-worker.d.ts +2 -0
- package/lib/visitor-worker.d.ts.map +1 -0
- package/lib/visitor-worker.js +18 -0
- package/lib/visitor-worker.js.map +1 -0
- package/lib/visitor-worker.mjs +14686 -0
- package/lib/visitor-worker.py +78 -0
- package/package.json +14 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
[English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
|
|
4
|
-
|
|
5
|
-
## 0.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
[English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
## 0.4.1 (2026-11-05)
|
|
6
|
+
|
|
7
|
+
- Allow Portal to add invited visitors to a running Agent through authenticated local IPC, without interrupting tasks or changing existing identities. Persist first, acknowledge application, retain only token hashes in the live registry, and preserve per-visitor isolation. Older plugins require one restart after upgrading; editing existing credentials still requires restart.
|
|
8
|
+
- Add opt-in public visitor admission with unique private credentials, isolated contexts/workspaces and five accepted messages per identity across conversations and restarts. Invited peers keep their existing access.
|
|
9
|
+
- Return structured pre-execution authentication challenges and distinct invalid-token, invitation-required and quota-exhausted errors. Clients can register on demand and continue once; the server issues identities, rejects caller-chosen IDs and enforces quotas before model work. Support the Portal's live switch, persist token hashes only, and cap registrations. See the [public visitor/client guide](docs/public-visitors.en.md).
|
|
10
|
+
|
|
11
|
+
## 0.4.0 (2026-11-04)
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
- Add automatically prepared Microsandbox 0.7.11 execution: one VM per authenticated visitor, read-only public projections and independent persistent private disks.
|
|
18
|
+
- 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.
|
|
19
|
+
- 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.
|
|
20
|
+
- Verify native Python mutation boundaries, lifecycle, restart persistence, automatic preparation and Portal startup progress. See [isolation guide](docs/visitor-isolation.en.md).
|
|
21
|
+
|
|
22
|
+
## 0.3.0
|
|
23
|
+
|
|
24
|
+
Requires DeepSeek Harness `>=0.2.1-rc.2 <0.3.0` and speaks A2A 1.0 JSON-RPC.
|
|
25
|
+
|
|
26
|
+
- 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.
|
|
27
|
+
- Move owner RPC and monitoring to authenticated local IPC. Public HTTP accepts visitor credentials only. An empty `trustedPeers` list denies all visitors.
|
|
28
|
+
- 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.
|
|
29
|
+
- Persist task results and idle conversation history across eviction and restart within the retention window. Fail interrupted work without replaying it.
|
|
30
|
+
- 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`.
|
|
31
|
+
- Declare required runtime packages and verify imports without development dependencies. Preserve the guard against malformed Unicode from upstream streaming decoders.
|
|
32
|
+
|
|
33
|
+
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.
|
|
34
|
+
|
|
35
|
+
### Upgrade
|
|
36
|
+
|
|
37
|
+
Stop the agent, update dsh if needed, then install into the existing profile:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
dsh plugin --profile YOUR_PROFILE add dsh-a2a-server@0.3.0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
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).
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
|
-
# 更新日志
|
|
2
|
-
|
|
3
|
-
[English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
|
|
4
|
-
|
|
5
|
-
## 0.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
[English](CHANGELOG.md) · [简体中文](CHANGELOG.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
## 0.4.1(2026-11-05)
|
|
6
|
+
|
|
7
|
+
- Portal 可通过仅本机主人可用的管理接口,即时添加受邀访客,无需重启,不中断已有任务或更换身份。先保存再确认生效,运行时新增名单只保存 Token 哈希,工作区继续按访客隔离。旧插件升级后需重启一次;修改已有凭据仍需重启。
|
|
8
|
+
- 增加默认关闭的公开访客入口:每个身份有独立凭据、对话和工作区,跨对话、重启累计最多 5 次消息;受邀访客保持原有权限。
|
|
9
|
+
- 提供执行前的结构化匿名认证指引,区分无效凭据、需要邀请和额度耗尽;客户端可按需登记并继续原消息一次。身份由服务端签发,拒绝自报 ID,调用模型前强制检查额度。支持 Portal 实时开关,仅保存 Token 哈希并限制登记数量。详见[公开访客与客户端接入](docs/public-visitors.md)。
|
|
10
|
+
|
|
11
|
+
## 0.4.0(2026-11-04)
|
|
12
|
+
|
|
13
|
+
- Microsandbox 公共资料副本排除各层级的 `node_modules`,不跟随依赖链接,也不修改宿主项目。其他链接、私有文件导入和 Docker 挂载仍保持严格检查。
|
|
14
|
+
|
|
15
|
+
- 增加仅本机主人可用的 `prepare-stop`,收尾任务并停止沙箱、保留磁盘后,Portal/CLI 才结束后端。真实模型中继测试发现,Windows 直接结束进程树会丢失尚未写盘的近期文件,现已修复正常停止流程。
|
|
16
|
+
|
|
17
|
+
- 自动准备 Microsandbox 0.7.11;每位已认证访客独立 VM,公共资料副本只读、私有磁盘可读写并持久保存。
|
|
18
|
+
- 工具完成后空闲 30 秒暂停、10 分钟停止;同访客工具串行,限制驻留数量,停止/重启保留文件,并回收遗留实例。
|
|
19
|
+
- 聊天记录和凭据不挂入沙箱;旧私有文件迁出公共目录,拒绝链接及不安全目录结构。访客仍只开放 read_file。
|
|
20
|
+
- 增加真实 Python 隔离、生命周期、重启持久化、自动准备与 Portal 启动进度测试,详见[隔离指南](docs/visitor-isolation.md)。
|
|
21
|
+
|
|
22
|
+
## 0.3.0
|
|
23
|
+
|
|
24
|
+
要求 DeepSeek Harness `>=0.2.1-rc.2 <0.3.0`,使用 A2A 1.0 JSON-RPC。
|
|
25
|
+
|
|
26
|
+
- 访客可以聊天,并通过 `read_file` 读取主人指定的资料目录。拦截路径越界、链接、插件私有状态及其他工具;公共模型提示与主人运行时上下文分离。
|
|
27
|
+
- 主人 RPC 和监控迁到需要认证的本机 IPC;公网 HTTP 仅接受访客凭据。空白名单 `trustedPeers: []` 拒绝全部访客。
|
|
28
|
+
- 限制并发、每日任务数、执行时限、模型调用、输出和流缓冲;同一 context 的并发请求不会串答,SSE 断开后释放订阅。
|
|
29
|
+
- 在保留期内持久化任务结果与空闲对话,支持驱逐和重启后的恢复。重启中断的任务标记失败,不自动重放。
|
|
30
|
+
- 增加由 Portal 配置的自动简报循环:默认每小时更新、一个持久化开关、重启去重,完成后直接写入 `.hop/handoff.md`。
|
|
31
|
+
- 补齐运行依赖声明,验证脱离开发依赖后可导入。保留上游流式解码异常的 Unicode 防护。
|
|
32
|
+
|
|
33
|
+
验证:15 个文件共 217 项测试、类型检查、构建和仅使用声明运行依赖的包导入检查。测试使用确定性模型;真实模型与跨网络部署需要单独验收。
|
|
34
|
+
|
|
35
|
+
### 升级
|
|
36
|
+
|
|
37
|
+
停止 agent,必要时升级 dsh,然后更新原有 profile 中的插件:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
dsh plugin --profile YOUR_PROFILE add dsh-a2a-server@0.3.0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
将 `YOUR_PROFILE` 替换为已保存的 profile 名称,安装后重新启动。配套 [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent) 也需要更新,以使用私有管理入口和自动简报开关。升级已有 profile 时不要再次使用 `--from-default-profile`。
|
|
44
|
+
|
|
45
|
+
原来的 HTTP 主人/监控调用必须迁移至本机 IPC。部署前检查访客许可与资料目录,详见[迁移指南](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.md)。
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ for visitor permissions, automatic briefings, private owner access and Tailscale
|
|
|
38
38
|
## Install
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
|
-
dsh plugin --profile web add dsh-a2a-server@0.
|
|
41
|
+
dsh plugin --profile web add dsh-a2a-server@0.4.1
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
`dsh plugin` forwards to pnpm inside the profile directory and appends this
|
|
@@ -65,10 +65,10 @@ ships in `dsh-web-app` rather than `dsh-base`, so a `headless` profile needs
|
|
|
65
65
|
`@deepseek-ai/dsh-host-webserver` mounted before this bundle serves anything.
|
|
66
66
|
`dsh --profile <name> --dump-config` prints the composed rows.
|
|
67
67
|
|
|
68
|
-
### Upgrading
|
|
68
|
+
### Upgrading to 0.4.1
|
|
69
69
|
|
|
70
|
-
Version 0.
|
|
71
|
-
`dsh-a2a-server@0.
|
|
70
|
+
Version 0.4.1 requires DSH `>=0.2.1-rc.2 <0.3.0`. Stop the agent, install
|
|
71
|
+
`dsh-a2a-server@0.4.1` into its existing profile, then restart it. Owner RPC and
|
|
72
72
|
monitoring now use authenticated local IPC; update the companion Portal as well.
|
|
73
73
|
Visitors retain chat and scoped workspace reads, while other tools are denied.
|
|
74
74
|
An empty `trustedPeers: []` denies all visitors. Task results persist across
|
|
@@ -77,7 +77,11 @@ restart within the configured retention window.
|
|
|
77
77
|
The current [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent)
|
|
78
78
|
configures default-on hourly briefings with one persistent switch. The plugin
|
|
79
79
|
alone does not enable automatic briefing without its `briefing` configuration.
|
|
80
|
-
See the [0.
|
|
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.1 release notes](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/CHANGELOG.md).
|
|
81
|
+
|
|
82
|
+
### Public visitors (0.4.1)
|
|
83
|
+
|
|
84
|
+
The companion Portal can opt into public access, off by default. Clients send normally; an explicit pre-execution 401 challenge lets the updated skill register, save a server-issued credential and continue the original message once. The local server enforces five accepted messages across conversations and restarts, then asks the visitor to contact the owner for access. This is an optional RPC extension, not a redirect or automatic behavior of every A2A client. See the [client flow, quota rules and credential storage](docs/public-visitors.en.md). Existing invited peers retain their access; when public admission is enabled, its issued identities are admitted independently of `trustedPeers`.
|
|
81
85
|
|
|
82
86
|
## Quick start
|
|
83
87
|
|
|
@@ -125,7 +129,7 @@ curl -s http://127.0.0.1:9922/a2a \
|
|
|
125
129
|
| `A2A_PEERS` | `alice` | `name[:token]` list; a token is required for each |
|
|
126
130
|
| `A2A_PORT` | `9900` | Listening port |
|
|
127
131
|
| `A2A_SEND_MODE` | `block` | `block` or `immediate` |
|
|
128
|
-
| `A2A_WORKSPACE_ROOT` | temp dir |
|
|
132
|
+
| `A2A_WORKSPACE_ROOT` | temp dir | Shared public data; visitor workspaces live outside this root |
|
|
129
133
|
| `DEEPSEEK_MODEL` | `deepseek-v4-flash` | Model id requested from the adapter |
|
|
130
134
|
|
|
131
135
|
A one-shot probe runs 67 checks against a live server and exits non-zero on any
|
|
@@ -144,6 +148,7 @@ node example/probe.mjs http://127.0.0.1:9922 demo123
|
|
|
144
148
|
| `/.well-known/agent.json` | GET | Public by default |
|
|
145
149
|
| `{basePath}` (default `/a2a`) | POST | Bearer required |
|
|
146
150
|
| `{basePath}/admin/contexts[/<id>]` | GET | Local IPC only + admin token; never registered on public HTTP |
|
|
151
|
+
| `{basePath}/admin/prepare-stop` | POST | Local IPC + admin token; drain tasks and stop sandboxes before terminating DSH |
|
|
147
152
|
|
|
148
153
|
| JSON-RPC method | Status |
|
|
149
154
|
| --- | --- |
|
|
@@ -266,9 +271,11 @@ another peer answers exactly as an absent one.
|
|
|
266
271
|
| `storage.directory` | `~/.dsh-a2a/<deployment-key>` | Private checkpoints; one process per directory |
|
|
267
272
|
| `storage.retentionMs` / `storage.maxContexts` | `86400000` / `256` | Retention after last activity, and maximum stored contexts |
|
|
268
273
|
| `storage.maxContextBytes` | `4000000` | Maximum checkpoint bytes per context |
|
|
269
|
-
| `isolation.workspaceMode` | `per-peer` |
|
|
270
|
-
| `isolation.workspaceRoot` | — | Required;
|
|
271
|
-
| `isolation.peerWorkspaces` | `{}` |
|
|
274
|
+
| `isolation.workspaceMode` | `per-peer` | Legacy migration field; visitors now have an external private cwd |
|
|
275
|
+
| `isolation.workspaceRoot` | — | Required; shared public read-only data root |
|
|
276
|
+
| `isolation.peerWorkspaces` | `{}` | Legacy per-identity read-root override |
|
|
277
|
+
| `isolation.privateRoot` | deployment-specific home directory | Private workspace parent, outside public data |
|
|
278
|
+
| `isolation.execution` | `read-only` | Select `microsandbox` for per-visitor VMs (Portal default); legacy `docker` also supported |
|
|
272
279
|
| `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
280
|
| `briefing.prompt` | — | Optional single-update instruction executed as the bounded local owner at startup and hourly; configured by Portal, requires owner administration |
|
|
274
281
|
| `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 |
|
|
@@ -312,13 +319,12 @@ identities.
|
|
|
312
319
|
| 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
320
|
| Prompt context | Public agents have a dedicated system prompt and suppress optional host runtime context | Scoped complete prompt and context suppression |
|
|
314
321
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
accordingly. The A2A checkpoint directory is always excluded.
|
|
322
|
+
Visitor working directories now live outside `workspaceRoot`. The selected root
|
|
323
|
+
contains public data; `read_file` defaults to it and accepts `root: "workspace"`
|
|
324
|
+
for the current visitor's files. Portal automatically prepares Microsandbox:
|
|
325
|
+
one VM per visitor, read-only public projections and persistent private disks.
|
|
326
|
+
Idle VMs pause after 30 seconds and stop after 10 minutes, retaining files.
|
|
327
|
+
Backend conversation storage is not mounted. See [setup and limits](docs/visitor-isolation.en.md).
|
|
322
328
|
|
|
323
329
|
`read_file({"path":"."})` lists the root's entries. A file path reads UTF-8 text;
|
|
324
330
|
`offset` (1-based) and `limit` page lines or directory entries. Reads are bounded
|
|
@@ -446,3 +452,23 @@ If the code has expired, open an issue and we will refresh it.
|
|
|
446
452
|
## License
|
|
447
453
|
|
|
448
454
|
MIT
|
|
455
|
+
## Public relay integration
|
|
456
|
+
|
|
457
|
+
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).
|
|
458
|
+
|
|
459
|
+
### Relay integration test
|
|
460
|
+
|
|
461
|
+
`tests/relay.integration.spec.ts` is skipped unless **both** variables point at local checkouts:
|
|
462
|
+
|
|
463
|
+
- `A2A_RELAY_SOURCE` — the [a2a-relay-server](https://github.com/huangjuhua-aigc/a2a-relay-server) repository root
|
|
464
|
+
- `A2A_CONNECTOR_SOURCE` — Talk2MyAgent's `packages/a2a-connector` directory (run `pnpm install` in Talk2MyAgent first)
|
|
465
|
+
|
|
466
|
+
```sh
|
|
467
|
+
A2A_RELAY_SOURCE=/path/to/a2a-relay-server \
|
|
468
|
+
A2A_CONNECTOR_SOURCE=/path/to/Talk2MyAgent/packages/a2a-connector \
|
|
469
|
+
pnpm vitest run tests/relay.integration.spec.ts
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
It uses a stub model, temporary data and dynamic ports; no API key or DSH install is needed.
|
|
473
|
+
|
|
474
|
+
For public access, quota and visitor isolation scenarios, including an opt-in real-model local Relay walkthrough and MP4 recording scripts, see the [test matrix and reproduction guide (Chinese)](docs/test-cases-public-visitors.md).
|
package/README.zh-CN.md
CHANGED
|
@@ -28,13 +28,17 @@ URL 的合规 peer 都能发现这个 agent 并向它提交任务。
|
|
|
28
28
|
本插件**只做入站**:从不主动连接其他 agent,没有 client、没有 peer 目录、没有 A2A
|
|
29
29
|
subagent provider。它是 `ctx.agents` 之上的传输适配层,不是能力接缝。
|
|
30
30
|
|
|
31
|
+
## 公开访客(0.4.1)
|
|
32
|
+
|
|
33
|
+
配套 Portal 可开启默认关闭的「允许公开访客」。统一使用 add/send 直接聊天;首次无 Token 时,技能只在收到明确的“消息未执行、需要匿名登录”指引后登记、保存服务端签发的凭据,再继续原消息。每个身份跨对话、重启累计最多 5 次,由本地服务端检查;用完提示联系 Agent 主人开通权限。这是可选 RPC 扩展,不是浏览器跳转,也不是所有 A2A 客户端自动支持的行为。详见[访客接入体验、额度与凭据保存](docs/public-visitors.md)。受邀访客保持原有权限;开启公开接入后,系统签发的公开身份不受静态 `trustedPeers` 名单限制。
|
|
34
|
+
|
|
31
35
|
## 安装
|
|
32
36
|
|
|
33
37
|
需要本机配置界面或跨网络访问时,先看配套 [Talk2MyAgent 使用说明](https://github.com/huangjuhua-aigc/Talk2MyAgent)。
|
|
34
38
|
访客读取权限、自动简报、主人 IPC 与 Tailscale 部署详见[部署与迁移指南](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-access-hardening.md)。
|
|
35
39
|
|
|
36
40
|
```sh
|
|
37
|
-
dsh plugin --profile web add dsh-a2a-server@0.
|
|
41
|
+
dsh plugin --profile web add dsh-a2a-server@0.4.1
|
|
38
42
|
```
|
|
39
43
|
|
|
40
44
|
`dsh plugin` 会在 profile 目录里转发给 pnpm,并把这个 bundle 追加进
|
|
@@ -59,17 +63,19 @@ dsh plugin --profile web add ./path/to/dsh-a2a-server
|
|
|
59
63
|
`dsh-base` 中,因此 `headless` profile 需要先挂载 `@deepseek-ai/dsh-host-webserver`。
|
|
60
64
|
`dsh --profile <name> --dump-config` 可以打印组合出来的配置行。
|
|
61
65
|
|
|
62
|
-
###
|
|
66
|
+
### 升级到 0.4.1
|
|
63
67
|
|
|
64
|
-
0.
|
|
65
|
-
`dsh-a2a-server@0.
|
|
68
|
+
0.4.1 适配 DSH `>=0.2.1-rc.2 <0.3.0`。先停止 agent,将
|
|
69
|
+
`dsh-a2a-server@0.4.1` 安装到原有 profile,再重新启动;配套 Portal 也需更新。
|
|
66
70
|
主人 RPC 和监控已改为需要认证的本机 IPC,公网 HTTP 不接受主人凭据。
|
|
67
71
|
访客可聊天并读取授权目录,其他工具关闭;空白名单 `trustedPeers: []` 拒绝所有访客。
|
|
68
72
|
任务结果与空闲会话在保留期内可跨驱逐和重启恢复。
|
|
69
73
|
|
|
70
74
|
新版 [Talk2MyAgent Portal](https://github.com/huangjuhua-aigc/Talk2MyAgent) 默认开启每小时简报更新,
|
|
71
75
|
用一个开关控制,完成后直接写入 `.hop/handoff.md`。单独安装协议插件而未配置 `briefing`
|
|
72
|
-
|
|
76
|
+
不会自动执行简报任务。
|
|
77
|
+
|
|
78
|
+
Portal 自动准备 Microsandbox,为每位访客提供独立沙箱。公共资料使用只读副本并排除 `node_modules`,私有文件保存在独立持久磁盘;空闲 30 秒暂停、10 分钟停止。独立 DSH 的旧配置保留原执行模式,安装插件本身不会自动启用 VM。详见[沙箱指南](docs/visitor-isolation.md)和 [0.4.1 发布说明](https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/CHANGELOG.zh-CN.md)。
|
|
73
79
|
|
|
74
80
|
## 快速开始
|
|
75
81
|
|
|
@@ -116,7 +122,7 @@ curl -s http://127.0.0.1:9922/a2a \
|
|
|
116
122
|
| `A2A_PEERS` | `alice` | `名字[:token]` 列表;每个 peer 都必须有 token |
|
|
117
123
|
| `A2A_PORT` | `9900` | 监听端口 |
|
|
118
124
|
| `A2A_SEND_MODE` | `block` | `block` 或 `immediate` |
|
|
119
|
-
| `A2A_WORKSPACE_ROOT` | 临时目录 |
|
|
125
|
+
| `A2A_WORKSPACE_ROOT` | 临时目录 | 公共资料目录;访客工作区在此目录之外 |
|
|
120
126
|
| `DEEPSEEK_MODEL` | `deepseek-v4-flash` | 向适配器请求的模型 id |
|
|
121
127
|
|
|
122
128
|
一次性探针会对运行中的服务执行 67 项检查,任何一项不符即以非零码退出:
|
|
@@ -134,6 +140,7 @@ node example/probe.mjs http://127.0.0.1:9922 demo123
|
|
|
134
140
|
| `/.well-known/agent.json` | GET | 默认公开 |
|
|
135
141
|
| `{basePath}`(默认 `/a2a`) | POST | 必须携带 Bearer |
|
|
136
142
|
| `{basePath}/admin/contexts[/<id>]` | GET | 仅本机 IPC + admin token,公网 HTTP 不挂载 |
|
|
143
|
+
| `{basePath}/admin/prepare-stop` | POST | 仅本机 IPC + admin token;收尾任务、停止沙箱后确认可结束 DSH |
|
|
137
144
|
|
|
138
145
|
| JSON-RPC 方法 | 状态 |
|
|
139
146
|
| --- | --- |
|
|
@@ -238,9 +245,11 @@ part 是被**删除**而不是做了别名:本服务写出的每一个回包
|
|
|
238
245
|
| `blockTimeoutMs` | `60000` | 最长阻塞等待;到期返回任务当前状态,客户端可继续查询 |
|
|
239
246
|
| `contextIdleTtlMs` | `1800000` | context 的 agent 被释放前的空闲时长 |
|
|
240
247
|
| `maxResidentContexts` | `64` | 常驻 context 数量上限 |
|
|
241
|
-
| `isolation.workspaceMode` | `per-peer` |
|
|
242
|
-
| `isolation.workspaceRoot` | — |
|
|
243
|
-
| `isolation.peerWorkspaces` | `{}` |
|
|
248
|
+
| `isolation.workspaceMode` | `per-peer` | 旧配置迁移字段;访客均使用外置私有 cwd |
|
|
249
|
+
| `isolation.workspaceRoot` | — | 必填;只放公共资料 |
|
|
250
|
+
| `isolation.peerWorkspaces` | `{}` | 旧版按访客覆盖读取根目录 |
|
|
251
|
+
| `isolation.privateRoot` | 用户主目录下按部署分隔 | 公共资料之外的私有工作区父目录 |
|
|
252
|
+
| `isolation.execution` | `read-only` | 设为 `microsandbox` 使用独立访客 VM(Portal 默认);兼容 `docker` |
|
|
244
253
|
| `preamble.files` | `[".hop/handoff.md"]` | 主人背景简报文件(**agent 级**:相对工作区根目录解析,全体访客共用一份;绝对路径直通),合并进每个 context 的首条访客消息;文件不存在则跳过 |
|
|
245
254
|
| `briefing.prompt` | — | 可选的单次简报更新指令,由 Portal 配置,通过有执行预算的主人 agent 每小时运行 |
|
|
246
255
|
| `briefing.settingsFile` | — | 绝对路径;读取 `{ "enabled": boolean }` 设置,缺省开启,每 5 秒检查 |
|
|
@@ -281,10 +290,10 @@ refs:
|
|
|
281
290
|
| 工具层 | 仅提供受控 `read_file`,不开放写入、命令、联网或跨会话工具 | 屏蔽继承工具、专用读取工具、执行校验 |
|
|
282
291
|
| 提示词 | 访客不继承可选的主人个性化提示与运行时上下文 | 独立的公共系统提示词 |
|
|
283
292
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
293
|
+
访客 cwd 现在放在 `workspaceRoot` 之外。所选目录只放公共资料,`read_file` 默认读取它,
|
|
294
|
+
传入 `root: "workspace"` 则读取自己的私有工作区。Portal 自动准备 Microsandbox,每位访客独立 VM,
|
|
295
|
+
公共资料副本只读、自己的持久化磁盘可写;空闲 30 秒暂停、10 分钟停止,文件保留,不挂后端聊天记录。
|
|
296
|
+
详见[安装、迁移与隔离边界](docs/visitor-isolation.md)。
|
|
288
297
|
|
|
289
298
|
`read_file({"path":"."})` 列出资料目录;文件支持 UTF-8 文本,按 `offset`/`limit`
|
|
290
299
|
分页。单文件上限 2 MiB,单次最多 32000 字符、500 行/项,目录扫描最多 1000 项。
|
|
@@ -389,3 +398,23 @@ peer 依赖来表达:安装预检会拒绝任何插件未曾适配过的组合
|
|
|
389
398
|
## 许可
|
|
390
399
|
|
|
391
400
|
MIT
|
|
401
|
+
## 公网中继集成
|
|
402
|
+
|
|
403
|
+
Talk2MyAgent Portal 已实现自动注册 Relay、后台 Connector、发布开关和访客邀请。插件仍提供本地 A2A 接口,文件权限和公开简报逻辑保持原样。中继服务单独部署;实现状态与测试命令见 [中继实现记录](docs/a2a-relay-implementation.zh-CN.md)。
|
|
404
|
+
|
|
405
|
+
### 中继集成测试
|
|
406
|
+
|
|
407
|
+
`tests/relay.integration.spec.ts` 需要**同时**设置以下两个变量才会运行,否则跳过:
|
|
408
|
+
|
|
409
|
+
- `A2A_RELAY_SOURCE`:[a2a-relay-server](https://github.com/huangjuhua-aigc/a2a-relay-server) 仓库根目录
|
|
410
|
+
- `A2A_CONNECTOR_SOURCE`:Talk2MyAgent 的 `packages/a2a-connector` 目录(先在 Talk2MyAgent 执行 `pnpm install`)
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
A2A_RELAY_SOURCE=/path/to/a2a-relay-server \
|
|
414
|
+
A2A_CONNECTOR_SOURCE=/path/to/Talk2MyAgent/packages/a2a-connector \
|
|
415
|
+
pnpm vitest run tests/relay.integration.spec.ts
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
测试使用桩模型、临时数据和动态端口,无需 API 密钥或安装 DSH。
|
|
419
|
+
|
|
420
|
+
公开接入、额度、访客隔离以及真实模型+本地中继的录屏复跑,见[测试用例与执行说明](docs/test-cases-public-visitors.md)。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Public visitors and client onboarding
|
|
2
|
+
|
|
3
|
+
This optional Talk2MyAgent A2A extension is off by default. Enabling it in Portal lets a new client obtain its own visitor credential without an invitation from the owner. Conversations still require a Bearer token and remain isolated by a stable visitor ID.
|
|
4
|
+
|
|
5
|
+
## Adding invited visitors while running
|
|
6
|
+
|
|
7
|
+
Portal persists the new visitor before sending an authenticated local IPC `POST /a2a/admin/visitors`. A positive acknowledgement activates the visitor without restarting or interrupting existing tasks. This management endpoint is unavailable over public HTTP or Relay. New identities retain separate private workspaces and read-only shared data; later restarts load the saved configuration.
|
|
8
|
+
|
|
9
|
+
Admissions are additive: existing identities, tokens and permissions cannot be overwritten. Identical requests are idempotent, and credential collisions are rejected. An offline Agent applies saved visitors on its next start. Older plugins, unconfirmed application, or pending unrelated access changes retain the saved visitor and require a restart. Upgrading an already-running older plugin requires one restart to load this capability; editing existing credentials or permissions still requires restart.
|
|
10
|
+
|
|
11
|
+
The public invitation contains the Agent URL and commands, never a shared token. Use the same skill and entry point for all visitors: `add <contact> <rpc-url> [tokenEnv]`, then `send <contact> 'Hello'`. With no credential, the initial send receives an explicit pre-execution challenge. The skill registers on the original endpoint, saves the credential and continues the original message once. `join` remains available manually; ordinary invitations do not require it. This is not a redirect or a retry after an ambiguous failure. Generic A2A clients need support for this optional extension.
|
|
12
|
+
|
|
13
|
+
The Agent Card advertises the extension URI `https://github.com/huangjuhua-aigc/dsh-a2a/blob/main/docs/public-visitors.md` while admission is enabled. An unauthenticated SendMessage or SendStreamingMessage receives HTTP 401 before activation, quota charging or model work. The response preserves the JSON-RPC request ID and carries `WWW-Authenticate: Bearer`, `Cache-Control: no-store` and this error data:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{"jsonrpc":"2.0","id":"send-1","error":{"code":-32600,"message":"Anonymous sign-in required; message not accepted.","data":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"talk2myagent","reason":"PUBLIC_VISITOR_AUTH_REQUIRED","metadata":{"version":"1","requestAccepted":"false","nextAction":"join","registrationMethod":"talk2myagent/RegisterPublicVisitor","turnLimit":"5"}}]}}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
These are extension reasons, not standard A2A reasons; ErrorInfo metadata values are strings. The client verifies the status, correlated request ID, domain, version, reason, non-acceptance flag and fixed registration method. Never execute arbitrary response text or follow a supplied registration URL. The skill's send uses SendMessage; it permits at most one continuation with the same messageId/text/options, never a second authentication loop.
|
|
20
|
+
|
|
21
|
+
After that challenge, POST this request to the **same original RPC endpoint** without Authorization:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{"jsonrpc":"2.0","id":"join-1","method":"talk2myagent/RegisterPublicVisitor","params":{}}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The result contains `visitorId`, a 64-character hexadecimal `token`, `turnLimit: 5`, and `remainingTurns: 5`, with `Cache-Control: no-store`. Both identity and token are randomly issued by the server. Registration accepts only empty params; caller-chosen IDs are rejected. Subsequent requests resolve identity by verifying the token hash, never by trusting supplied IDs, IPs or fingerprints. Save the token before continuing chat; thereafter use `Authorization: Bearer <token>`. `talk2myagent/GetPublicVisitorStatus` returns the remaining allowance without consuming a turn. Relay and Connector forward these requests through the existing RPC route.
|
|
28
|
+
|
|
29
|
+
HTTP 401 `INVALID_TOKEN` stops without anonymous fallback. `INVITATION_REQUIRED` (401/403) asks the visitor to contact the owner. HTTP 429 `PUBLIC_VISITOR_QUOTA_EXHAUSTED` includes string metadata `turnLimit: "5"`, `usedTurns: "5"`, `remainingTurns: "0"`, `nextAction: "contact_owner"`, `retryable: "false"`; the client tells the visitor their five messages are used and to contact the Agent owner for access. The local dsh-a2a-server enforces quotas before model work. Registration rate/capacity errors have separate reasons `PUBLIC_VISITOR_REGISTRATION_RATE_LIMITED` and `PUBLIC_VISITOR_CAPACITY_REACHED`.
|
|
30
|
+
|
|
31
|
+
The skill stores credentials separately under `~/.a2a/private-credentials/<SHA-256 of complete RPC URL>.json`, or the equivalent directory below `A2A_HOME`. It requests directory mode 0700 and file mode 0600; Windows also relies on the user directory ACL. This is a local secret file, not an encrypted credential manager. Do not expose it to models, public data directories, source control or shared sync. The contact book contains a reference, not the secret. The server stores only the token hash.
|
|
32
|
+
|
|
33
|
+
Repeated join operations and concurrent first sends reuse the saved identity through a local registration lock. Reimporting the same address without a token preserves the existing identity. Missing credentials for an existing public identity, or missing environment variables for an invited identity, are errors and never trigger registration. Conflicting contacts are not overwritten. Saving failures prevent authenticated continuation. Only the explicit pre-execution challenge permits continuation; timeouts, disconnects, generic errors and invalid credentials never cause replay or identity replacement. Poll a known task ID instead. Move the credential securely to continue the same identity on another device; username, IP and fingerprint cannot recover it. Continue a conversation with `--context <contextId>` and inspect the allowance with `quota <contact>`.
|
|
34
|
+
|
|
35
|
+
The five-message lifetime allowance spans conversations and server restarts, including synchronous and streaming submissions. Tool calls, polling and subscriptions do not consume extra turns. Charges are persisted before model work and may remain after failure, cancellation or disconnect. The public registry permits at most 20 new identities per hour per Agent and retains at most 1000 identities; it does not recycle identities or delete visitor files automatically. This bounds issuance but cannot enforce a per-human allowance without verified identity.
|
|
36
|
+
|
|
37
|
+
Closing admission denies new registrations and subsequent requests from existing public identities. Already accepted work may finish. Reopening does not reset quotas. Invited visitors retain their existing policy. Public visitors share only the owner's read-only public data and published briefing, while conversations, tasks and persistent workspaces remain private. A sandbox starts on the first tool call; only read_file remains exposed.
|
|
38
|
+
|
|
39
|
+
Standalone deployments may set `publicVisitors.enabled: true`. Portal supplies a private `publicVisitors.settingsFile` for live updates; unreadable or malformed settings fail closed. Older plugins/configurations require an upgrade and restart. Version 0.4.0 does not implement this feature. The `public-` identity prefix is reserved for the server.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 公开访客:客户端通信与体验
|
|
2
|
+
|
|
3
|
+
这是 Talk2MyAgent 的可选 A2A 扩展,默认关闭。主人在 Portal 打开“允许公开访客”后,陌生客户端可以自行领取独立访客身份,无需主人预先创建姓名和 Token。普通聊天仍需 Bearer Token。
|
|
4
|
+
|
|
5
|
+
## 受邀访客的即时添加
|
|
6
|
+
|
|
7
|
+
新版 Portal 的“添加访客”先保存姓名和凭据,再通过带主人认证的本机 IPC `POST /a2a/admin/visitors` 添加到运行中的 Agent。确认成功显示“已立即生效,无需重启”。公网 HTTP 和 Relay 不开放这个管理接口。
|
|
8
|
+
|
|
9
|
+
这里只允许增加 Portal 分配的新身份,不覆盖已有身份、Token 或权限;相同身份与凭据的重复请求幂等,凭据冲突会拒绝。进行中的任务、对话 ID 和私有工作区保持不变;新访客仍只有自己的工作区和只读共享资料。Portal 保存的配置在以后重启时继续加载。
|
|
10
|
+
|
|
11
|
+
Agent 未运行时保存后下次启动生效。旧插件、管理接口未确认应用,或存在尚未应用的删除/权限修改时,Portal 保留已保存配置并明确提示重启,不自动中断服务。首次升级到支持即时添加的插件仍需重启一次;修改已有访客 Token 或权限暂不支持即时应用。
|
|
12
|
+
|
|
13
|
+
## 访客看到的流程
|
|
14
|
+
|
|
15
|
+
1. 主人复制一条公开邀请,里面只有 Agent 地址、技能安装说明和接入命令,没有共享 Token。
|
|
16
|
+
2. 访客将邀请交给自己的 AI。与受邀访问使用同一技能:`add <通讯录ID> <完整RPC地址>` 保存地址,然后直接 `send <通讯录ID> '你好'`。有受邀 Token 时,add 仍可带原有的环境变量引用。
|
|
17
|
+
3. 首次未带 Token 的消息得到 HTTP 401 和明确的匿名登录指引,服务端确认该消息尚未接收、未执行。技能显示“正在建立匿名访客身份…”,在原 RPC 地址登记,先把秘密 Token 单独保存到本机,再记录身份引用。
|
|
18
|
+
4. 保存成功后,技能携带 Token 继续发送原消息,保留原 messageId、文本和发送选项。后续每次请求复用同一凭据,不再登记;模型和用户无需复制或查看 Token。
|
|
19
|
+
5. 继续一段聊天要带 `--context <contextId>`;`quota <通讯录ID>` 查询剩余次数。换聊天窗口不重置总额度。
|
|
20
|
+
|
|
21
|
+
`join` 保留为显式手动命令,但普通邀请无需先调用它。自动匿名登录不是重定向,不打开浏览器,也不是普通失败重试。客户端只有在首次没有凭据且收到完整、匹配本次请求的指引时才登记,随后最多继续发送一次。普通 A2A 客户端需实现下述可选扩展;服务端无法强制客户端保存 Token。
|
|
22
|
+
|
|
23
|
+
## 通信约定
|
|
24
|
+
|
|
25
|
+
公开 Agent Card 的 `capabilities.extensions` 在开关开启时声明本页 URI。现有 Relay 和 Connector 继续透传相同 `/a2a` RPC 地址,无需新增公开 HTTP 路由。管理接口仍只有本机主人可以访问。
|
|
26
|
+
|
|
27
|
+
客户端先向完整 RPC 地址提交正常 `SendMessage`。开关开启且没有 Authorization 时,服务端在创建上下文、扣额度、调用模型之前返回 HTTP 401,保留请求的 JSON-RPC id,附带 `WWW-Authenticate: Bearer` 和 `Cache-Control: no-store`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{"jsonrpc":"2.0","id":"send-1","error":{"code":-32600,"message":"Anonymous sign-in required. This message has not been accepted.","data":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"talk2myagent","reason":"PUBLIC_VISITOR_AUTH_REQUIRED","metadata":{"version":"1","requestAccepted":"false","nextAction":"join","registrationMethod":"talk2myagent/RegisterPublicVisitor","turnLimit":"5"}}]}}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`error.data` 使用 ErrorInfo 数组;这是 `talk2myagent` 扩展域,非 A2A 标准错误原因。metadata 值均为字符串。客户端必须校验 HTTP 状态、JSON-RPC id、域、版本、reason、`requestAccepted` 和固定登记方法,不能依据自然语言提示执行任意命令或访问响应中给出的其他地址。指引本身不签发身份、不扣额度;重复收到 401 不得形成循环。服务端也向未认证的 `SendStreamingMessage` 返回相同指引;当前技能的 send 使用普通 SendMessage。
|
|
34
|
+
|
|
35
|
+
收到有效指引后,不带 Authorization 向**原 RPC 地址** POST:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{"jsonrpc":"2.0","id":"join-1","method":"talk2myagent/RegisterPublicVisitor","params":{}}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
成功响应(示意,TOKEN 不是可使用的凭据):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{"jsonrpc":"2.0","id":"join-1","result":{"visitorId":"public-<32个十六进制字符>","token":"<64个十六进制字符>","turnLimit":5,"remainingTurns":5}}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
响应设置 `Cache-Control: no-store`。visitorId 和 Token 均由服务端随机签发,登记只接受空 params,拒绝客户端指定 visitorId。后续服务端由 Token 摘要查出 visitorId,不信任消息中的自报身份、IP、用户名或指纹。Token 仅首次返回,服务器只保存摘要。客户端必须先成功保存凭据,再携带它提交聊天消息;注册本身不启动模型、不创建沙箱、不消耗聊天额度。Token 没有自动过期;关闭公开访问会拒绝该类访客的后续请求,再次开启不会重新发放额度。
|
|
48
|
+
|
|
49
|
+
携带 Token 调用 `talk2myagent/GetPublicVisitorStatus`,可获得 `visitorId`、`turnLimit`、`remainingTurns`。所有正常消息和任务仍使用现有 A2A 方法;获取或取消任务时同样核验身份和所属关系。公开访客不能读取受邀访客的扩展名片。
|
|
50
|
+
|
|
51
|
+
| 情况 | HTTP / error.data reason | 客户端行为 |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| 首次发送、没有 Token、允许匿名 | 401 / PUBLIC_VISITOR_AUTH_REQUIRED | 验证指引、登记、保存 Token,最多继续发送一次 |
|
|
54
|
+
| 已提交的 Token 无效或已撤销 | 401 / INVALID_TOKEN | 保留身份,停止并提示恢复凭据或联系主人 |
|
|
55
|
+
| 匿名访问关闭或需要受邀权限 | 401 或 403 / INVITATION_REQUIRED | 联系主人,不匿名登记 |
|
|
56
|
+
| 累计五次已用完 | 429 / PUBLIC_VISITOR_QUOTA_EXHAUSTED | 提示“匿名访客额度已用完(5/5)。请联系这个 Agent 的主人开通访问权限。” |
|
|
57
|
+
| 登记速率或容量达到限制 | 429 / PUBLIC_VISITOR_REGISTRATION_RATE_LIMITED 或 PUBLIC_VISITOR_CAPACITY_REACHED | 停止登记,不继续提交聊天 |
|
|
58
|
+
|
|
59
|
+
额度耗尽的 metadata 还包含 `turnLimit:"5"`、`usedTurns:"5"`、`remainingTurns:"0"`、`nextAction:"contact_owner"`、`retryable:"false"`。额度由本地 dsh-a2a-server 强制执行;Relay 透传错误,客户端展示提示,不承担额度裁决。
|
|
60
|
+
|
|
61
|
+
## 客户端保存、失败与恢复
|
|
62
|
+
|
|
63
|
+
- 技能把 Token 放在 `~/.a2a/private-credentials/<完整RPC地址的SHA-256>.json`(或 `A2A_HOME` 下相同目录),创建时采用目录 0700、文件 0600;Windows 还依赖当前用户目录的 ACL。这是本地秘密文件,不是系统凭据管理器,也不是加密保险箱。不得共享该目录、放进公共资料或同步到 Git。
|
|
64
|
+
- 通讯录只保存地址、凭据引用和公开访客标记,不保存 Token。技能不把秘密打印到 stdout/stderr,也不让模型读取凭据文件。
|
|
65
|
+
- 重复 join 同一地址会复用原身份,不重新请求 Token。一个本地客户端的多个别名也复用同一地址的身份。
|
|
66
|
+
- 已有联系人指向其他地址时拒绝覆盖;不带 Token 的 add 重新导入同地址时保留已有身份。已配置受邀环境变量但该变量缺失,或已保存的匿名凭据缺失/失效,都不会降级为首次匿名访问。
|
|
67
|
+
- 服务关闭、凭据失效、超额或请求超时不会自动重新注册。注册响应丢失时,不会自动重试;此时可能留下一个未使用的服务端身份,但没有提交聊天任务。
|
|
68
|
+
- 本地写入失败不继续提交带凭据的聊天消息。若凭据已保存而通讯录写入失败,再次 send/join 复用已保存凭据。同地址的并发首次访问通过本地锁共用一次登记。
|
|
69
|
+
- 已保存的联系人丢失凭据时明确报错,不自动领取新身份。换电脑要安全迁移原凭据才能继续同一身份;用户名、IP、浏览器指纹不能恢复访问权。当前没有图形化凭据迁移或找回功能。
|
|
70
|
+
- 聊天响应中断时不自动重发;已有任务 ID 时查询任务。客户端要保存 contextId 才能续接指定对话。
|
|
71
|
+
|
|
72
|
+
## 配额与数据边界
|
|
73
|
+
|
|
74
|
+
每个公开 visitorId 累计最多提交 5 次消息,覆盖所有 contextId、同步与流式发送。查询任务、订阅已有任务、查询余额不扣次数;工具调用和同一消息内部的模型调用不单独算一轮。计数在启动模型工作前持久化,任务失败、取消或客户端断开可能已经计次;不自动退款。并发提交也不能突破 5 次。
|
|
75
|
+
|
|
76
|
+
主人关闭公开访问后,新登记、旧 Token 的新请求均被拒绝;已接收任务允许完成。重新开启、重启 Agent、切换聊天均不清零。原有受邀访客继续遵守其原来的配置,不适用公开访客的 5 次总额度。
|
|
77
|
+
|
|
78
|
+
每个公开身份拥有独立会话、任务和私有工作区。公共资料与公开简报共享,公共资料只读;工具仍仅 read_file,首次调用工具时才启动自己的 Microsandbox。Token 是身份凭据,交给别人就等于共享这个访客的数据权限。
|
|
79
|
+
|
|
80
|
+
匿名方式无法保证“每个自然人只有 5 次”。当前还限制每个 Agent 每小时最多登记 20 个公开身份、最多保留 1000 个身份;计数和身份都持久化,不通过自动删除重置额度。现有模型任务、请求体、沙箱并发限制继续有效。达到登记容量后拒绝创建,不自动删除私有文件。它不是短信实名验证,也不是防止有组织批量访问的完整方案。
|
|
81
|
+
|
|
82
|
+
## 配置与升级
|
|
83
|
+
|
|
84
|
+
独立部署可设置 `publicVisitors.enabled: true`。Portal 生成私有 `publicVisitors.settingsFile` 并原子更新开关;有设置文件时,以文件为准,缺失、损坏或无法读取均关闭公开访问。旧插件或旧配置需升级并重启;Portal 显示待应用提示。此功能需要包含 public-visitors 模块的新版本插件,已有 0.4.0 不支持。
|
|
85
|
+
|
|
86
|
+
保留前缀 `public-` 给系统生成的身份,不允许受邀访客自行使用该前缀。
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Per-visitor sandboxes (0.4.0)
|
|
2
|
+
|
|
3
|
+
Portal defaults to local Microsandbox 0.7.11 microVMs. DSH remains the trusted orchestrator for authentication, model requests and conversation storage. Only read_file is exposed to visitors today; Python, shell and write tools are not automatically enabled. Real Python tests verify private read/write capability for future tool integrations.
|
|
4
|
+
|
|
5
|
+
## Automatic preparation
|
|
6
|
+
|
|
7
|
+
Click Start in Portal. It checks/upgrades the plugin, downloads a pinned SDK and a digest-pinned Python image, and reports progress. Components are cached in the user directory; no manual Microsandbox or Docker installation is needed. Plain chat creates no visitor VM. The first tool call creates one; later calls reuse it.
|
|
8
|
+
|
|
9
|
+
Supported targets are Windows x64/ARM64 with WHP, Linux x64/ARM64 with KVM, and Apple Silicon macOS with ARM64 Node.js. Node.js 22+ is required. Only Windows x64 has been tested locally; macOS and Linux still need acceptance testing. Intel Macs are unsupported.
|
|
10
|
+
|
|
11
|
+
Enabling Windows Hypervisor Platform may require a user action and restart. Linux requires access to /dev/kvm. Administrator privileges, firmware settings and OS restarts cannot be silently arranged. Preparation failures are visible and retryable; tools never fall back to host execution.
|
|
12
|
+
|
|
13
|
+
Portal installs dsh-a2a-server@^0.4.0 from npm. For local development, build the dsh-a2a checkout with pnpm install and pnpm build, then set A2A_PLUGIN_SOURCE to its absolute path in the terminal launching Portal. The npm release does not need this override. Standalone configurations select isolation.execution: microsandbox. Existing configs without that field retain read-only mode, which has no VM boundary. The docker backend remains available for compatibility.
|
|
14
|
+
|
|
15
|
+
## File boundaries and persistence
|
|
16
|
+
|
|
17
|
+
| Data | Inside the VM | Local persistence |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Selected public data | /source, read-only copy | Original host directory is never mounted |
|
|
20
|
+
| Current visitor files | /work, read/write | Exclusive persistent virtual disk |
|
|
21
|
+
| Other visitors | Not mounted | Separate disks |
|
|
22
|
+
| Sessions, protocol state, credentials | Not mounted | Trusted backend storage |
|
|
23
|
+
|
|
24
|
+
Public data includes .hop/handoff.md. Before each tool call it is validated and copied; changes stop that visitor VM before replacing its public projection, then restart with the same private disk. Trees are capped at 512 MiB and 100000 entries, so large datasets increase tool latency. Microsandbox public projections exclude entries named node_modules at any depth, case-insensitively, including dependency links. Their contents are never traversed; the original project's dependencies are neither moved nor deleted. Other symlinks, junctions, hard links, special files and nested filesystems remain rejected. Private workspace imports and Docker raw mounts do not use this exclusion and still reject all links.
|
|
25
|
+
|
|
26
|
+
Portal stores VM mappings at ~/.a2a/workspaces/<agent>/.microsandbox/<peer>/record.json and read-only public projections beside them. Existing <peer>/ files are imported on first boot and retained as migration sources. New /work files persist in the virtual disk; they are **not synchronized back to the old host folder**. A generated-file download/export interface has not been added.
|
|
27
|
+
|
|
28
|
+
The SDK, native state and VM disks live under ~/.a2a/sandbox-runtime, overridable with A2A_SANDBOX_HOME before startup. Back up both the private mapping directory and runtime state. Do not delete either or run destroy/remove on user VMs; those actions delete disks. Removing a peer credential does not automatically delete its files.
|
|
29
|
+
|
|
30
|
+
Identity is agent + authenticated peer, not conversation or message. Multiple contexts for one peer share a VM and private files. People sharing a token share this identity; issue independent credentials.
|
|
31
|
+
|
|
32
|
+
## Lifecycle and quotas
|
|
33
|
+
|
|
34
|
+
After the last tool finishes, 30 idle seconds pause the VM; 10 idle minutes stop it. Pausing retains memory. Stopping releases runtime resources and keeps disks. New tool calls resume a paused VM or restart a stopped VM with its original disk. Active or queued work prevents idle transitions, and calls for one visitor are serialized.
|
|
35
|
+
|
|
36
|
+
Each backend allows four resident VMs including paused ones. At capacity, the oldest idle VM stops early; if every VM is busy, new tool work is rejected. Normal backend shutdown stops its VMs. A new controller reclaims recorded instances abandoned by a previous controller, without destroying disks. Abrupt power loss or forced termination can still lose unflushed writes.
|
|
37
|
+
|
|
38
|
+
VMs have one CPU, 512 MiB RAM, a 256 MiB root disk and 512 MiB private disk. Networking is disabled. Tools run as UID 1000 with a 30-second execution timeout and 256 KB output cap. The public projection is read-only/noexec/nosuid/nodev; private disks are nosuid/nodev. The host user, hypervisor and pinned image remain trusted; this is not an absolute defense against virtualization vulnerabilities.
|
|
39
|
+
|
|
40
|
+
read_file defaults to public data; root: workspace selects the current visitor's disk. Text reads remain bounded to UTF-8 files up to 2 MiB, 32000 returned characters and 500 lines/entries per call. Backend conversation records are not tool-readable.
|
|
41
|
+
|
|
42
|
+
## Migration and verification
|
|
43
|
+
|
|
44
|
+
Stop the old agent and start with the new Portal. It copies current visitors' legacy directories outside public data, verifies the copy and removes the old source. Conflicting destinations are never overwritten. First VM boot imports that visitor's private files and refuses conflicting content. Archive private leftovers for removed visitors outside public data manually.
|
|
45
|
+
|
|
46
|
+
Automatic public briefings still update .hop/handoff.md and appear in Portal. Trusted owner maintenance runs on the host, outside the visitor sandbox.
|
|
47
|
+
|
|
48
|
+
Run pnpm typecheck, pnpm test, pnpm test:package and pnpm test:microsandbox. The last command automatically prepares real microVMs and synthetic files to exercise public mutation denial, private CRUD, peer separation, link attacks, network/credential isolation, cancellation, lifecycle, persistence and abandoned-instance recovery.
|
|
49
|
+
|
|
50
|
+
Set A2A_MSB_WALLCLOCK=1 for a full ten-minute wall-clock lifecycle test. Otherwise controlled time triggers actual native VM transitions. A2A_MSB_TEST_RUNTIME optionally reuses a test runtime cache. Legacy Docker validation remains pnpm test:docker and requires a local Linux Docker engine plus the image built from docker/Dockerfile.
|