@ryubyte/dsh-a2a 0.2.0 → 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.
- package/README.en.md +19 -81
- package/README.md +16 -77
- package/lib/client.js +63 -18
- package/lib/index.js +119 -13
- package/lib/types/a2a-config.d.ts +2 -0
- package/lib/types/a2a-config.d.ts.map +1 -1
- package/lib/types/client/dashboard.css.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/dashboard.d.ts +2 -0
- package/lib/types/dashboard.d.ts.map +1 -1
- package/lib/types/index.d.ts +16 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/server.d.ts +12 -3
- package/lib/types/server.d.ts.map +1 -1
- package/package.json +24 -8
- package/src/a2a-config.ts +3 -0
- package/src/client/dashboard.css.ts +5 -0
- package/src/client/index.ts +127 -20
- package/src/dashboard.ts +10 -0
- package/src/index.ts +132 -9
- package/src/server.ts +14 -3
package/README.en.md
CHANGED
|
@@ -9,6 +9,23 @@ It turns a DSH instance into a full A2A peer in both directions:
|
|
|
9
9
|
- **Client mode (outbound)** — discover a remote agent via its **AgentCard**, map each **skill** to a DSH model tool (`a2a__<name>__<skill>`), and execute remote tasks through A2A **JSON-RPC**.
|
|
10
10
|
- **Server mode (inbound)** — expose this DSH to other agents: `/.well-known/agent-card.json` plus a JSON-RPC endpoint (`SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SendStreamingMessage` over SSE) on the DSH webserver.
|
|
11
11
|
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install into a profile with one command and restart:
|
|
15
|
+
|
|
16
|
+
dsh plugin --profile web add @ryubyte/dsh-a2a
|
|
17
|
+
dsh --profile web
|
|
18
|
+
|
|
19
|
+
`dsh plugin` installs the package into the profile and auto-registers the
|
|
20
|
+
plugin layer (this package declares `dsh.bundle.patch` in `package.json`) — no
|
|
21
|
+
manual configuration is needed.
|
|
22
|
+
|
|
23
|
+
## Screenshots
|
|
24
|
+
|
|
25
|
+
| Connected agents (this DSH as a client) | Public service (this DSH as an agent) |
|
|
26
|
+
|---|---|
|
|
27
|
+
|  |  |
|
|
28
|
+
|
|
12
29
|
## Features
|
|
13
30
|
|
|
14
31
|
### Protocol surface (A2A v1.0)
|
|
@@ -51,46 +68,6 @@ land in that profile directory, so they are read no matter which working
|
|
|
51
68
|
directory you launch from. Hand-writing `a2a.json` is still supported (below),
|
|
52
69
|
but **a new user can do everything through the UI**.
|
|
53
70
|
|
|
54
|
-
## Screenshots
|
|
55
|
-
|
|
56
|
-
| Connected agents (this DSH as a client) | Public service (this DSH as an agent) |
|
|
57
|
-
|---|---|
|
|
58
|
-
|  |  |
|
|
59
|
-
|
|
60
|
-
## Installation
|
|
61
|
-
|
|
62
|
-
### Option 1: from npm (recommended)
|
|
63
|
-
|
|
64
|
-
Once published to npm, install into the target profile with one command and
|
|
65
|
-
restart:
|
|
66
|
-
|
|
67
|
-
dsh plugin --profile <profile> add @ryubyte/dsh-a2a
|
|
68
|
-
dsh --profile <profile>
|
|
69
|
-
|
|
70
|
-
`dsh plugin` installs the package into the profile and auto-registers the
|
|
71
|
-
plugin layer (this package declares `dsh.bundle.patch` in `package.json`) — no
|
|
72
|
-
manual configuration is needed.
|
|
73
|
-
|
|
74
|
-
### Option 2: from the repository (development)
|
|
75
|
-
|
|
76
|
-
For hacking on the code, requires Node.js >= 22:
|
|
77
|
-
|
|
78
|
-
# 1. Clone the repo
|
|
79
|
-
git clone https://github.com/ryubyte/dsh-a2a.git
|
|
80
|
-
cd dsh-a2a
|
|
81
|
-
|
|
82
|
-
# 2. Install deps and build
|
|
83
|
-
npm install
|
|
84
|
-
npm run build
|
|
85
|
-
|
|
86
|
-
# 3. Link into the target profile
|
|
87
|
-
dsh plugin --profile <profile> add link:$(pwd)
|
|
88
|
-
|
|
89
|
-
# 4. Restart
|
|
90
|
-
dsh --profile <profile>
|
|
91
|
-
|
|
92
|
-
After changing source, re-run `npm run build` and restart.
|
|
93
|
-
|
|
94
71
|
## Usage
|
|
95
72
|
|
|
96
73
|
### 1. Configure a remote agent (outbound)
|
|
@@ -160,7 +137,7 @@ Other A2A clients discover this DSH via the auto-inferred
|
|
|
160
137
|
|
|
161
138
|
On each inbound task the server picks an executor in this order:
|
|
162
139
|
|
|
163
|
-
1. **Explicit `execute`** (injected
|
|
140
|
+
1. **Explicit `execute`** (injected via composition config) — yours wins.
|
|
164
141
|
2. **This DSH's own agent session** (the default, when an agent loop is
|
|
165
142
|
present) — **one session per A2A `contextId`**: the first task for a context
|
|
166
143
|
`ctx.agents.create()`s a session (later tasks for the same context reuse it,
|
|
@@ -195,37 +172,6 @@ On each inbound task the server picks an executor in this order:
|
|
|
195
172
|
> a `/bin/sh -c` command — trusted clients only. For production put a TLS
|
|
196
173
|
> reverse proxy in front and set `authToken` as needed.
|
|
197
174
|
|
|
198
|
-
### 3. Programmatic use
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
import { A2AClient, A2AServer, TaskStore } from '@ryubyte/dsh-a2a';
|
|
202
|
-
|
|
203
|
-
// outbound: connect to a remote agent
|
|
204
|
-
const client = await A2AClient.connect('https://agent.example.com');
|
|
205
|
-
const { task } = await client.sendMessage({
|
|
206
|
-
messageId: crypto.randomUUID(),
|
|
207
|
-
role: 'ROLE_USER',
|
|
208
|
-
parts: [{ text: 'Draft a report' }],
|
|
209
|
-
});
|
|
210
|
-
for await (const ev of client.streamMessage({ messageId: crypto.randomUUID(), role: 'ROLE_USER', parts: [{ text: 'hi' }] })) {
|
|
211
|
-
// ev.statusUpdate | ev.artifactUpdate | ev.task | ev.message
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// inbound: wire a custom executor to your own agent loop
|
|
215
|
-
const store = new TaskStore();
|
|
216
|
-
const server = new A2AServer({
|
|
217
|
-
baseUrl: 'http://127.0.0.1:3080',
|
|
218
|
-
agentName: 'My DSH',
|
|
219
|
-
agentDescription: '...',
|
|
220
|
-
agentVersion: '1.0.0',
|
|
221
|
-
execute: async ({ message }) => ({
|
|
222
|
-
messageId: crypto.randomUUID(),
|
|
223
|
-
role: 'ROLE_AGENT',
|
|
224
|
-
parts: [{ text: await myAgentRun(message.parts) }],
|
|
225
|
-
}),
|
|
226
|
-
}, store);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
175
|
## Directory structure
|
|
230
176
|
|
|
231
177
|
src/
|
|
@@ -282,12 +228,4 @@ const server = new A2AServer({
|
|
|
282
228
|
webserver binds loopback by default, so put a TLS reverse proxy in front
|
|
283
229
|
before exposing beyond localhost.
|
|
284
230
|
- Bearer tokens — both the outbound per-agent token and the inbound endpoint
|
|
285
|
-
`authToken` — are stored in plaintext in `a2a.json`; secure the file yourself.
|
|
286
|
-
|
|
287
|
-
## Development
|
|
288
|
-
|
|
289
|
-
```bash
|
|
290
|
-
npm install
|
|
291
|
-
npm run build # tsc(types) + tsdown(lib) + wrap client → lib/client.js
|
|
292
|
-
npm test # node:test + tsx (unit/integration) + client-bundle smoke
|
|
293
|
-
```
|
|
231
|
+
`authToken` — are stored in plaintext in `a2a.json`; secure the file yourself.
|
package/README.md
CHANGED
|
@@ -9,6 +9,21 @@
|
|
|
9
9
|
- **Client 模式(出站)**——通过远程 Agent 的 **AgentCard** 发现它,把每个 **skill** 映射为 DSH 模型工具(`a2a__<name>__<skill>`),通过 A2A **JSON-RPC** 执行远程任务。
|
|
10
10
|
- **Server 模式(入站)**——把本 DSH 暴露给其他 Agent:在 DSH webserver 上提供 `/.well-known/agent-card.json` 与 JSON-RPC 端点(`SendMessage`、`GetTask`、`ListTasks`、`CancelTask`、基于 SSE 的 `SendStreamingMessage`)。
|
|
11
11
|
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
一行命令装进 profile,重启即生效:
|
|
15
|
+
|
|
16
|
+
dsh plugin --profile web add @ryubyte/dsh-a2a
|
|
17
|
+
dsh --profile web
|
|
18
|
+
|
|
19
|
+
`dsh plugin` 会把包装进 profile 并自动加入插件层(本包在 package.json 里声明了 `dsh.bundle.patch`),无需手动改任何配置。
|
|
20
|
+
|
|
21
|
+
## 截图
|
|
22
|
+
|
|
23
|
+
| 连接的 Agent(本 DSH 作为客户端) | 对外服务(本 DSH 作为 Agent) |
|
|
24
|
+
|---|---|
|
|
25
|
+
|  |  |
|
|
26
|
+
|
|
12
27
|
## 功能
|
|
13
28
|
|
|
14
29
|
### 协议能力(A2A v1.0)
|
|
@@ -43,43 +58,6 @@
|
|
|
43
58
|
|
|
44
59
|
配置以 **`a2a.json`(按 profile 存放,可完全通过 UI 管理)为准**,无需改动组合配置即可管理 Agent 与对外服务。文件定位不依赖启动目录:插件解析进程命令行里的 `--profile <name>`(`dsh web` 别名同样识别为 `web` profile),读写 `~/.dsh/profiles/<name>/a2a.json`——UI 首次生成的配置也会写到该 profile 目录,从任意工作目录启动都能读到。手写 `a2a.json` 仍然支持(见下文),但**新用户完全靠 UI 即可完成配置与上线**。
|
|
45
60
|
|
|
46
|
-
## 截图
|
|
47
|
-
|
|
48
|
-
| 连接的 Agent(本 DSH 作为客户端) | 对外服务(本 DSH 作为 Agent) |
|
|
49
|
-
|---|---|
|
|
50
|
-
|  |  |
|
|
51
|
-
|
|
52
|
-
## 安装
|
|
53
|
-
|
|
54
|
-
### 方式一:从 npm 安装(推荐)
|
|
55
|
-
|
|
56
|
-
插件发布到 npm 后,一行命令装进目标 profile,重启即生效:
|
|
57
|
-
|
|
58
|
-
dsh plugin --profile <profile> add @ryubyte/dsh-a2a
|
|
59
|
-
dsh --profile <profile>
|
|
60
|
-
|
|
61
|
-
`dsh plugin` 会把包装进 profile 并自动加入插件层(本包在 package.json 里声明了 `dsh.bundle.patch`),无需手动改任何配置。
|
|
62
|
-
|
|
63
|
-
### 方式二:从仓库安装(开发调试)
|
|
64
|
-
|
|
65
|
-
用于改代码调试,需要 Node.js >= 22 与 pnpm:
|
|
66
|
-
|
|
67
|
-
# 1. 克隆仓库
|
|
68
|
-
git clone https://github.com/ryubyte/dsh-a2a.git
|
|
69
|
-
cd dsh-a2a
|
|
70
|
-
|
|
71
|
-
# 2. 安装依赖并构建
|
|
72
|
-
npm install
|
|
73
|
-
npm run build
|
|
74
|
-
|
|
75
|
-
# 3. 链接进目标 profile
|
|
76
|
-
dsh plugin --profile <profile> add link:$(pwd)
|
|
77
|
-
|
|
78
|
-
# 4. 重启
|
|
79
|
-
dsh --profile <profile>
|
|
80
|
-
|
|
81
|
-
改完源码后重跑 `npm run build` 再重启即可。
|
|
82
|
-
|
|
83
61
|
## 使用
|
|
84
62
|
|
|
85
63
|
### 1. 配置远程 Agent(出站)
|
|
@@ -139,7 +117,7 @@
|
|
|
139
117
|
|
|
140
118
|
server 模式收到任务后,按以下优先级选择 executor:
|
|
141
119
|
|
|
142
|
-
1. **显式 `execute
|
|
120
|
+
1. **显式 `execute`**(通过 composition 配置注入)—— 用你的。
|
|
143
121
|
2. **本 DSH 的 agent 会话**(默认,当 agent 循环就位时)—— 按 A2A `contextId` **一个对话一个会话**:某 `contextId` 首个任务 `ctx.agents.create()` 建会话(后续同 `contextId` 复用同一会话,历史累积;进程重启后按需 `resume`),把消息作为 `next-turn` 唤醒项发给它,等 `whenIdle()`,取最新 assistant 回复作为 A2A artifact。同一 `contextId` 的并发任务按序串行,不同 `contextId` 并行。会话不逐任务释放(否则无法累积上下文),插件卸载/下线时统一释放。开 server 即可「接收并处理任务」,无需写代码。
|
|
144
122
|
3. **`notConfiguredExecutor`**(兜底)—— 既没注入 `execute`、composition 里又没有可用 agent 循环(如仅 dsh-base 的最小 profile)时,返回「no executor configured — inject one」,**不执行任何命令**。
|
|
145
123
|
|
|
@@ -151,37 +129,6 @@ server 模式收到任务后,按以下优先级选择 executor:
|
|
|
151
129
|
>
|
|
152
130
|
> 本机自测也可显式传入 `shellExecutor`(把 prompt 当作 shell 命令执行,仅限可信客户端);生产请置于 TLS 反向代理之后并按需配 `authToken`。
|
|
153
131
|
|
|
154
|
-
### 3. 编程接入
|
|
155
|
-
|
|
156
|
-
```ts
|
|
157
|
-
import { A2AClient, A2AServer, TaskStore } from '@ryubyte/dsh-a2a';
|
|
158
|
-
|
|
159
|
-
// 出站:连接远程 Agent
|
|
160
|
-
const client = await A2AClient.connect('https://agent.example.com');
|
|
161
|
-
const { task } = await client.sendMessage({
|
|
162
|
-
messageId: crypto.randomUUID(),
|
|
163
|
-
role: 'ROLE_USER',
|
|
164
|
-
parts: [{ text: 'Draft a report' }],
|
|
165
|
-
});
|
|
166
|
-
for await (const ev of client.streamMessage({ messageId: crypto.randomUUID(), role: 'ROLE_USER', parts: [{ text: 'hi' }] })) {
|
|
167
|
-
// ev.statusUpdate | ev.artifactUpdate | ev.task | ev.message
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 入站:自定义 executor 接入 DSH agent 循环
|
|
171
|
-
const store = new TaskStore();
|
|
172
|
-
const server = new A2AServer({
|
|
173
|
-
baseUrl: 'http://127.0.0.1:3080',
|
|
174
|
-
agentName: 'My DSH',
|
|
175
|
-
agentDescription: '...',
|
|
176
|
-
agentVersion: '1.0.0',
|
|
177
|
-
execute: async ({ message }) => ({
|
|
178
|
-
messageId: crypto.randomUUID(),
|
|
179
|
-
role: 'ROLE_AGENT',
|
|
180
|
-
parts: [{ text: await myAgentRun(message.parts) }],
|
|
181
|
-
}),
|
|
182
|
-
}, store);
|
|
183
|
-
```
|
|
184
|
-
|
|
185
132
|
## 目录结构
|
|
186
133
|
|
|
187
134
|
src/
|
|
@@ -217,11 +164,3 @@ const server = new A2AServer({
|
|
|
217
164
|
- 入站服务的执行策略见[入站任务由谁执行](#入站任务由谁执行executor-优先级):有 agent 循环时默认由本 DSH 会话执行(绑定工作目录 cwd),否则(且未注入 executor)**拒绝执行**并返回「no executor configured」。`shellExecutor` 仅限本地自测。无论哪种 executor,都切勿向不受信网络暴露 server 模式(默认无鉴权,除非配置 `authToken`)。
|
|
218
165
|
- A2A 规范要求生产 `AgentInterface.url` 使用 HTTPS;DSH webServer 默认仅绑 loopback,对外暴露前需自行加 TLS。
|
|
219
166
|
- Bearer token(出站按 Agent 配置的、入站保护端点的 `authToken`)均明文存于 `a2a.json`,请自行保证该文件权限。
|
|
220
|
-
|
|
221
|
-
## 开发
|
|
222
|
-
|
|
223
|
-
```bash
|
|
224
|
-
npm install
|
|
225
|
-
npm run build # tsc(types) + tsdown(lib) + wrap client → lib/client.js
|
|
226
|
-
npm test # node:test + tsx(单元 / 集成)+ client-bundle smoke
|
|
227
|
-
```
|
package/lib/client.js
CHANGED
|
@@ -199,6 +199,11 @@ window.__ModuleLoader__.load({
|
|
|
199
199
|
.a2a-auth-state[data-on='false'] { color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
200
200
|
.a2a-auth-input-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
201
201
|
.a2a-auth-input-row .a2a-input { flex: 1; min-width: 220px; }
|
|
202
|
+
.a2a-cors-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
203
|
+
.a2a-cors-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-family: var(--ds-font-family-code, ui-monospace, monospace); padding: 2px 6px 2px 8px; border-radius: var(--dsw-radius-sm, 6px); background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.05)); border: .5px solid var(--dsw-alias-border-l3, #d0d3d9); color: var(--dsw-alias-label-secondary, #4a4f57); }
|
|
204
|
+
.a2a-cors-chip-x { border: none; background: none; cursor: pointer; font-size: 14px; line-height: 1; padding: 0 2px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
205
|
+
.a2a-cors-chip-x:hover:not(:disabled) { color: var(--dsw-alias-state-error-primary, #f04438); }
|
|
206
|
+
.a2a-cors-chip-x:disabled { cursor: default; opacity: .4; }
|
|
202
207
|
|
|
203
208
|
/* ---- inbound table ---- */
|
|
204
209
|
.a2a-table-wrap { overflow-x: auto; border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 10px; }
|
|
@@ -303,21 +308,13 @@ window.__ModuleLoader__.load({
|
|
|
303
308
|
const dispose = injectDashboardStyles();
|
|
304
309
|
return () => dispose();
|
|
305
310
|
}, "dsh-a2a: dashboard styles");
|
|
306
|
-
ctx.effect(() => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
inject: () => ({})
|
|
314
|
-
}, DashboardSection);
|
|
315
|
-
return () => off();
|
|
316
|
-
} catch (err) {
|
|
317
|
-
console.error("[dsh-a2a] failed to register settings section:", err);
|
|
318
|
-
return () => {};
|
|
319
|
-
}
|
|
320
|
-
}, "dsh-a2a: settings section");
|
|
311
|
+
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
312
|
+
name: "settings.section",
|
|
313
|
+
id: "a2a",
|
|
314
|
+
order: 90,
|
|
315
|
+
label: () => "A2A 连接",
|
|
316
|
+
inject: () => ({})
|
|
317
|
+
}, DashboardSection)), "dsh-a2a: settings section");
|
|
321
318
|
}
|
|
322
319
|
async function fetchSnapshot() {
|
|
323
320
|
const res = await fetch("/a2a/api", { headers: { accept: "application/json" } });
|
|
@@ -390,6 +387,8 @@ window.__ModuleLoader__.load({
|
|
|
390
387
|
const [addToken, setAddToken] = (0, react.useState)("");
|
|
391
388
|
/** Inbound authToken input (serve tab); empty string means "clear". */
|
|
392
389
|
const [authInput, setAuthInput] = (0, react.useState)("");
|
|
390
|
+
/** New-origin input for the inbound CORS allowlist editor (serve tab). */
|
|
391
|
+
const [corsInput, setCorsInput] = (0, react.useState)("");
|
|
393
392
|
/** Serve-identity edit mode + form draft (server tab). All hooks stay at the
|
|
394
393
|
* top level in fixed order; a single shared draft avoids per-card hooks. */
|
|
395
394
|
const [editingServe, setEditingServe] = (0, react.useState)(false);
|
|
@@ -541,7 +540,7 @@ window.__ModuleLoader__.load({
|
|
|
541
540
|
})), (0, react.createElement)("div", { className: "a2a-hint" }, "若远程 Agent 需要鉴权,填入 token;导入与后续调用都会带上。token 会明文存入 a2a.json。"), discovering ? (0, react.createElement)("div", { className: "a2a-hint" }, "正在读取 Agent Card…") : discovered ? (0, react.createElement)("div", { className: "a2a-preview" }, (0, react.createElement)("div", { className: "a2a-preview-head" }, (0, react.createElement)("span", { className: "a2a-preview-name" }, discovered.name), discovered.version ? (0, react.createElement)("span", { className: "a2a-preview-version a2a-mono" }, `v${discovered.version}`) : null, stateDot("connected")), discovered.description ? (0, react.createElement)("div", { className: "a2a-preview-desc" }, discovered.description) : null, (discovered.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { className: "a2a-skill-list" }, (0, react.createElement)("div", { className: "a2a-skill-list-title" }, "Skills"), ...(discovered.skills ?? []).map((s) => (0, react.createElement)("div", { key: s.id }, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null, discovered.endpoint ? (0, react.createElement)("div", { className: "a2a-preview-meta a2a-mono" }, `端点 ${discovered.endpoint}`) : null, (0, react.createElement)("div", { className: "a2a-btn-row" }, actionBtn("连接", true, () => void doConnect(), busy?.startsWith("add-agent:") ?? false), actionBtn("取消", false, () => {
|
|
542
541
|
setDiscovered(null);
|
|
543
542
|
setAddUrl("");
|
|
544
|
-
}, false))) : (0, react.createElement)("div", { className: "a2a-hint" }, "输入远程 Agent 的 Agent Card URL 并点击\"导入\",将自动读取其名称、描述、技能与能力,确认后建立连接。"))) : null, tab === "server" ? (0, react.createElement)("div", { className: "a2a-stack" }, serve ? editingServe ? serveEditPanel(serveDraft, setServeDraft, (also) => void saveServeIdentity(also), () => setEditingServe(false), busy, false) : needsServeSetup(serve) ? serveEditPanel(serveDraft, setServeDraft, (also) => void saveServeIdentity(also), () => setEditingServe(false), busy, true) : serveViewPanel(serve, busy, act, authInput, setAuthInput, startEditServe) : null, (0, react.createElement)("div", { className: "a2a-crew" }, sec("入站连接(谁在连接本 DSH)", inbound.length), inbound.length === 0 ? emptyRow("暂无入站连接。其他 A2A 客户端调用本 DSH 的 JSON-RPC 端点后会显示在这里。") : (0, react.createElement)("div", { className: "a2a-table-wrap" }, (0, react.createElement)("table", { className: "a2a-table" }, (0, react.createElement)("thead", null, headRow([
|
|
543
|
+
}, false))) : (0, react.createElement)("div", { className: "a2a-hint" }, "输入远程 Agent 的 Agent Card URL 并点击\"导入\",将自动读取其名称、描述、技能与能力,确认后建立连接。"))) : null, tab === "server" ? (0, react.createElement)("div", { className: "a2a-stack" }, serve ? editingServe ? serveEditPanel(serveDraft, setServeDraft, (also) => void saveServeIdentity(also), () => setEditingServe(false), busy, false) : needsServeSetup(serve) ? serveEditPanel(serveDraft, setServeDraft, (also) => void saveServeIdentity(also), () => setEditingServe(false), busy, true) : serveViewPanel(serve, busy, act, authInput, setAuthInput, corsInput, setCorsInput, startEditServe) : null, (0, react.createElement)("div", { className: "a2a-crew" }, sec("入站连接(谁在连接本 DSH)", inbound.length), inbound.length === 0 ? emptyRow("暂无入站连接。其他 A2A 客户端调用本 DSH 的 JSON-RPC 端点后会显示在这里。") : (0, react.createElement)("div", { className: "a2a-table-wrap" }, (0, react.createElement)("table", { className: "a2a-table" }, (0, react.createElement)("thead", null, headRow([
|
|
545
544
|
"来源",
|
|
546
545
|
"地址",
|
|
547
546
|
"首次连接",
|
|
@@ -722,7 +721,7 @@ window.__ModuleLoader__.load({
|
|
|
722
721
|
* "编辑" button that flips the section into {@link serveEditPanel}. Pure (no
|
|
723
722
|
* hooks): the edit-mode flag and draft live in the parent section.
|
|
724
723
|
*/
|
|
725
|
-
function serveViewPanel(serve, busy, act, authInput, setAuthInput, onEdit) {
|
|
724
|
+
function serveViewPanel(serve, busy, act, authInput, setAuthInput, corsInput, setCorsInput, onEdit) {
|
|
726
725
|
return (0, react.createElement)("div", { className: "a2a-panel" }, (0, react.createElement)("div", { className: "a2a-serve-head" }, sec("A2A 服务(本 DSH 作为 Agent 对外)", 0), stateDot(serve.enabled ? "connected" : "disconnected"), (0, react.createElement)("span", {
|
|
727
726
|
className: "a2a-serve-status",
|
|
728
727
|
"data-on": String(serve.enabled)
|
|
@@ -732,7 +731,7 @@ window.__ModuleLoader__.load({
|
|
|
732
731
|
})) : null, serve.agentCardUrl ? (0, react.createElement)("div", { className: "a2a-meta-row" }, (0, react.createElement)("span", { className: "a2a-meta-key" }, "Agent Card"), (0, react.createElement)("span", { className: "a2a-serve-endpoint a2a-mono" }, serve.agentCardUrl), (0, react.createElement)(CopyButton, {
|
|
733
732
|
text: serve.agentCardUrl,
|
|
734
733
|
label: "Agent Card URL"
|
|
735
|
-
})) : null, (serve.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { className: "a2a-skill-list" }, (0, react.createElement)("div", { className: "a2a-skill-list-title" }, "Skills"), ...(serve.skills ?? []).map((s) => (0, react.createElement)("div", { key: s.id }, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null), serve.enabled ? executorNote(serve) : null, serve.enabled ? authControl(serve, busy, act, authInput, setAuthInput) : null);
|
|
734
|
+
})) : null, (serve.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { className: "a2a-skill-list" }, (0, react.createElement)("div", { className: "a2a-skill-list-title" }, "Skills"), ...(serve.skills ?? []).map((s) => (0, react.createElement)("div", { key: s.id }, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null), serve.enabled ? executorNote(serve) : null, serve.enabled ? authControl(serve, busy, act, authInput, setAuthInput) : null, serve.enabled ? corsControl(serve, busy, act, corsInput, setCorsInput) : null);
|
|
736
735
|
}
|
|
737
736
|
/**
|
|
738
737
|
* Serve-identity edit form (also the guided-setup card for fresh installs).
|
|
@@ -814,6 +813,52 @@ window.__ModuleLoader__.load({
|
|
|
814
813
|
setAuthInput("");
|
|
815
814
|
}, busy === "set-server-auth:") : null), (0, react.createElement)("div", { className: "a2a-hint" }, "token 会明文存入 a2a.json,重启后仍生效。设置后,入站 JSON-RPC 请求需带 Authorization: Bearer <token>。"));
|
|
816
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Inbound CORS allowlist control for the serve tab: shows the allowed browser
|
|
818
|
+
* origins and lets the operator add/remove them. Empty = no CORS (default),
|
|
819
|
+
* so cross-origin browser clients are blocked. Origins are exact
|
|
820
|
+
* scheme+host+port strings; the literal `*` opens it to any site (warned).
|
|
821
|
+
* Applies live (persisted to a2a.json) — no restart needed.
|
|
822
|
+
*/
|
|
823
|
+
function corsControl(serve, busy, act, corsInput, setCorsInput) {
|
|
824
|
+
const origins = serve.corsOrigins ?? [];
|
|
825
|
+
const hasWildcard = origins.includes("*");
|
|
826
|
+
const isBusy = busy === "set-server-cors:";
|
|
827
|
+
const save = (next) => {
|
|
828
|
+
act("set-server-cors", "", { corsOrigins: next });
|
|
829
|
+
};
|
|
830
|
+
const addOrigin = () => {
|
|
831
|
+
const v = corsInput.trim();
|
|
832
|
+
if (!v || origins.includes(v)) {
|
|
833
|
+
setCorsInput("");
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
save([...origins, v]);
|
|
837
|
+
setCorsInput("");
|
|
838
|
+
};
|
|
839
|
+
return (0, react.createElement)("div", { className: "a2a-auth" }, (0, react.createElement)("div", { className: "a2a-auth-row" }, (0, react.createElement)("span", { className: "a2a-auth-label" }, "跨域访问 (CORS)"), stateDot(origins.length > 0 ? hasWildcard ? "reconnecting" : "connected" : "disabled"), (0, react.createElement)("span", {
|
|
840
|
+
className: "a2a-auth-state",
|
|
841
|
+
"data-on": String(origins.length > 0)
|
|
842
|
+
}, origins.length === 0 ? "未开启(浏览器跨源调用被拒)" : hasWildcard ? "允许任意来源(*)" : `已允许 ${origins.length} 个来源`)), origins.length > 0 ? (0, react.createElement)("div", { className: "a2a-cors-list" }, ...origins.map((o) => (0, react.createElement)("span", {
|
|
843
|
+
key: o,
|
|
844
|
+
className: "a2a-cors-chip"
|
|
845
|
+
}, o, (0, react.createElement)("button", {
|
|
846
|
+
className: "a2a-cors-chip-x",
|
|
847
|
+
title: "移除",
|
|
848
|
+
disabled: isBusy,
|
|
849
|
+
onClick: () => save(origins.filter((x) => x !== o))
|
|
850
|
+
}, "×")))) : null, (0, react.createElement)("div", { className: "a2a-auth-input-row" }, (0, react.createElement)("input", {
|
|
851
|
+
className: "a2a-input",
|
|
852
|
+
type: "text",
|
|
853
|
+
placeholder: "添加来源,如 http://localhost:8080 或 *",
|
|
854
|
+
value: corsInput,
|
|
855
|
+
disabled: isBusy,
|
|
856
|
+
onChange: (e) => setCorsInput(e.target.value),
|
|
857
|
+
onKeyDown: (e) => {
|
|
858
|
+
if (e.key === "Enter") addOrigin();
|
|
859
|
+
}
|
|
860
|
+
}), actionBtn("添加", true, addOrigin, isBusy || !corsInput.trim()), origins.length > 0 ? actionBtn("清空", false, () => save([]), isBusy) : null), (0, react.createElement)("div", { className: "a2a-hint" }, hasWildcard ? "⚠️ 已允许任意来源(*):用户浏览器打开的任何网站都能跨源调用本入站端点。仅在完全可信的本机环境使用,并建议同时配置入站鉴权 token。" : "填入完整来源(协议+主机+端口,精确匹配)。留空则默认禁止浏览器跨源调用;写 * 可放开任意来源(不推荐)。跨域配置明文存入 a2a.json,实时生效。"));
|
|
861
|
+
}
|
|
817
862
|
function inboundRow(p, busy, act) {
|
|
818
863
|
return (0, react.createElement)("tr", { key: p.id }, (0, react.createElement)("td", { "data-label": "来源" }, p.label), (0, react.createElement)("td", {
|
|
819
864
|
className: "a2a-cell-mono",
|
package/lib/index.js
CHANGED
|
@@ -695,6 +695,16 @@ var A2AServer = class {
|
|
|
695
695
|
applyAuthScheme(card, token);
|
|
696
696
|
return card;
|
|
697
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* True when the request carries the configured bearer token. Public wrapper
|
|
700
|
+
* for transports that must decide response headers/status BEFORE streaming
|
|
701
|
+
* frames — see index.ts's SSE branch, which must send `content-type:
|
|
702
|
+
* text/event-stream` + CORS headers before the first `res.write()`, and
|
|
703
|
+
* therefore needs the auth outcome up front.
|
|
704
|
+
*/
|
|
705
|
+
checkAuth(req) {
|
|
706
|
+
return this.authorized(req);
|
|
707
|
+
}
|
|
698
708
|
/** True when the request carries the configured bearer token. */
|
|
699
709
|
authorized(req) {
|
|
700
710
|
const token = this.options.authToken;
|
|
@@ -1190,10 +1200,7 @@ function createDshAgentExecutor(agents, opts) {
|
|
|
1190
1200
|
type: "text",
|
|
1191
1201
|
text: prompt
|
|
1192
1202
|
}],
|
|
1193
|
-
source: {
|
|
1194
|
-
kind: "plugin",
|
|
1195
|
-
plugin
|
|
1196
|
-
}
|
|
1203
|
+
source: { kind: `plugin:${plugin}` }
|
|
1197
1204
|
});
|
|
1198
1205
|
agent.send(userMessage, "next-turn", true);
|
|
1199
1206
|
if (justOpened) try {
|
|
@@ -1440,6 +1447,14 @@ var DashboardRegistry = class {
|
|
|
1440
1447
|
message: "set-server-auth is not wired (server mode not mounted)"
|
|
1441
1448
|
};
|
|
1442
1449
|
}
|
|
1450
|
+
case "set-server-cors": {
|
|
1451
|
+
const raw = payload?.corsOrigins;
|
|
1452
|
+
const origins = Array.isArray(raw) ? raw.filter((o) => typeof o === "string") : [];
|
|
1453
|
+
return this.hooks.setServerCors ? this.hooks.setServerCors(origins) : {
|
|
1454
|
+
ok: false,
|
|
1455
|
+
message: "set-server-cors is not wired (server mode not mounted)"
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1443
1458
|
case "server-status": return this.hooks.serverStatus ? Promise.resolve(this.hooks.serverStatus()) : {
|
|
1444
1459
|
ok: false,
|
|
1445
1460
|
message: "server-status is not wired (server mode not mounted)"
|
|
@@ -1676,6 +1691,7 @@ function mergePersisted(base, persisted) {
|
|
|
1676
1691
|
if (pServer.agentVersion) merged.agentVersion = pServer.agentVersion;
|
|
1677
1692
|
if (pServer.endpointPath) merged.endpointPath = pServer.endpointPath;
|
|
1678
1693
|
if (pServer.authToken !== void 0) merged.authToken = pServer.authToken;
|
|
1694
|
+
if (pServer.corsOrigins !== void 0) merged.corsOrigins = pServer.corsOrigins;
|
|
1679
1695
|
if (pServer.skills) merged.skills = pServer.skills;
|
|
1680
1696
|
}
|
|
1681
1697
|
return merged;
|
|
@@ -2222,12 +2238,23 @@ function apply(ctx, config = {}) {
|
|
|
2222
2238
|
}, store);
|
|
2223
2239
|
let enabled = wantsServer;
|
|
2224
2240
|
let disposers = [];
|
|
2241
|
+
let corsAllowlist = normalizeOrigins(mergedConfig.corsOrigins);
|
|
2242
|
+
if (corsAllowlist.includes("*")) logger?.warn?.("[a2a] corsOrigins includes \"*\": the inbound A2A endpoint accepts cross-origin browser calls from ANY site. Set an explicit origin allowlist unless this host is fully trusted and, ideally, gate it with authToken.");
|
|
2225
2243
|
const registerRoutes = () => {
|
|
2244
|
+
const cors = (req) => corsHeadersFor(req.headers.origin, corsAllowlist);
|
|
2226
2245
|
disposers.push(webServer.register({
|
|
2227
2246
|
kind: "exact",
|
|
2228
2247
|
path: "/.well-known/agent-card.json",
|
|
2229
|
-
handler: async (
|
|
2230
|
-
|
|
2248
|
+
handler: async (req, res) => {
|
|
2249
|
+
if (req.method === "OPTIONS") {
|
|
2250
|
+
res.writeHead(204, cors(req));
|
|
2251
|
+
res.end();
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
res.writeHead(200, {
|
|
2255
|
+
"content-type": "application/json",
|
|
2256
|
+
...cors(req)
|
|
2257
|
+
});
|
|
2231
2258
|
res.end(JSON.stringify(server.card));
|
|
2232
2259
|
}
|
|
2233
2260
|
}));
|
|
@@ -2235,34 +2262,54 @@ function apply(ctx, config = {}) {
|
|
|
2235
2262
|
kind: "prefix",
|
|
2236
2263
|
path: server.endpointPath,
|
|
2237
2264
|
handler: async (req, res) => {
|
|
2265
|
+
const ch = cors(req);
|
|
2266
|
+
if (req.method === "OPTIONS") {
|
|
2267
|
+
res.writeHead(204, ch);
|
|
2268
|
+
res.end();
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2238
2271
|
const isStream = (req.headers.accept ?? "").includes("text/event-stream");
|
|
2239
2272
|
if (req.method === "GET" && (req.url ?? "").startsWith(server.endpointPath + "/card")) {
|
|
2240
|
-
res.writeHead(200, {
|
|
2273
|
+
res.writeHead(200, {
|
|
2274
|
+
"content-type": "application/json",
|
|
2275
|
+
...ch
|
|
2276
|
+
});
|
|
2241
2277
|
res.end(JSON.stringify(server.card));
|
|
2242
2278
|
return;
|
|
2243
2279
|
}
|
|
2244
2280
|
if (req.method !== "POST") {
|
|
2245
|
-
res.writeHead(405, {
|
|
2281
|
+
res.writeHead(405, {
|
|
2282
|
+
"content-type": "text/plain",
|
|
2283
|
+
...ch
|
|
2284
|
+
});
|
|
2246
2285
|
res.end("Method Not Allowed");
|
|
2247
2286
|
return;
|
|
2248
2287
|
}
|
|
2249
2288
|
const body = await readBody(req);
|
|
2250
2289
|
if (isStream) {
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
res.writeHead(stream.status, {
|
|
2290
|
+
if (!server.checkAuth(toServerReq(req))) {
|
|
2291
|
+
res.writeHead(401, {
|
|
2254
2292
|
"content-type": "text/plain",
|
|
2255
|
-
...
|
|
2293
|
+
...ch,
|
|
2294
|
+
"WWW-Authenticate": "Bearer"
|
|
2256
2295
|
});
|
|
2257
2296
|
res.end("Unauthorized");
|
|
2258
2297
|
return;
|
|
2259
2298
|
}
|
|
2299
|
+
res.writeHead(200, {
|
|
2300
|
+
"content-type": "text/event-stream",
|
|
2301
|
+
"cache-control": "no-cache",
|
|
2302
|
+
connection: "keep-alive",
|
|
2303
|
+
...ch
|
|
2304
|
+
});
|
|
2305
|
+
await server.handleStream(toServerReq(req), body, (frame) => res.write(frame));
|
|
2260
2306
|
res.end();
|
|
2261
2307
|
return;
|
|
2262
2308
|
}
|
|
2263
2309
|
const out = await server.handle(toServerReq(req), body);
|
|
2264
2310
|
res.writeHead(out.status, {
|
|
2265
2311
|
"content-type": out.contentType,
|
|
2312
|
+
...ch,
|
|
2266
2313
|
...out.headers ?? {}
|
|
2267
2314
|
});
|
|
2268
2315
|
res.end(out.body);
|
|
@@ -2385,11 +2432,34 @@ function apply(ctx, config = {}) {
|
|
|
2385
2432
|
message: token ? "已设置入站鉴权 token" : "已清除入站鉴权 token"
|
|
2386
2433
|
};
|
|
2387
2434
|
},
|
|
2435
|
+
setServerCors: async (origins) => {
|
|
2436
|
+
corsAllowlist = normalizeOrigins(origins);
|
|
2437
|
+
if (corsAllowlist.includes("*")) logger?.warn?.("[a2a] corsOrigins set to include \"*\": the inbound A2A endpoint now accepts cross-origin browser calls from ANY site.");
|
|
2438
|
+
const next = loadConfig();
|
|
2439
|
+
next.server = {
|
|
2440
|
+
...next.server ?? {},
|
|
2441
|
+
corsOrigins: corsAllowlist.length > 0 ? corsAllowlist : void 0
|
|
2442
|
+
};
|
|
2443
|
+
const res = saveConfig(next);
|
|
2444
|
+
if (!res.ok) {
|
|
2445
|
+
logger?.error?.(`[a2a] failed to persist corsOrigins to ${res.path}: ${res.message}`);
|
|
2446
|
+
return {
|
|
2447
|
+
ok: false,
|
|
2448
|
+
message: `applied in-memory but failed to save: ${res.message}`
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
logger?.info?.(`[a2a] inbound CORS allowlist updated (${corsAllowlist.length} origin(s), saved to ${res.path})`);
|
|
2452
|
+
return {
|
|
2453
|
+
ok: true,
|
|
2454
|
+
message: corsAllowlist.length > 0 ? `已更新跨域白名单 (${corsAllowlist.length} 个来源)` : "已清空跨域白名单(默认禁止跨源)"
|
|
2455
|
+
};
|
|
2456
|
+
},
|
|
2388
2457
|
serverStatus: () => ({
|
|
2389
2458
|
ok: true,
|
|
2390
2459
|
message: JSON.stringify({
|
|
2391
2460
|
enabled,
|
|
2392
|
-
...serverCard()
|
|
2461
|
+
...serverCard(),
|
|
2462
|
+
corsOrigins: corsAllowlist
|
|
2393
2463
|
})
|
|
2394
2464
|
})
|
|
2395
2465
|
});
|
|
@@ -2538,6 +2608,42 @@ async function readBody(req) {
|
|
|
2538
2608
|
req.once("error", reject);
|
|
2539
2609
|
});
|
|
2540
2610
|
}
|
|
2611
|
+
/** Normalize a CORS origin allowlist: trim, drop empties, de-duplicate. */
|
|
2612
|
+
function normalizeOrigins(origins) {
|
|
2613
|
+
if (!origins) return [];
|
|
2614
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2615
|
+
for (const o of origins) {
|
|
2616
|
+
const t = typeof o === "string" ? o.trim() : "";
|
|
2617
|
+
if (t) seen.add(t);
|
|
2618
|
+
}
|
|
2619
|
+
return [...seen];
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Resolve CORS headers for one inbound A2A request against the configured
|
|
2623
|
+
* origin allowlist. Returns `{}` (no CORS) when the request has no `Origin`,
|
|
2624
|
+
* the allowlist is empty, or the origin isn't allowed — so a disallowed
|
|
2625
|
+
* cross-origin browser call is blocked by the browser exactly as before this
|
|
2626
|
+
* feature existed. An allowed origin is echoed back verbatim (never `*` unless
|
|
2627
|
+
* the operator explicitly configured `['*']`), with `Vary: Origin` so shared
|
|
2628
|
+
* caches don't leak one origin's response to another. Credentials are never
|
|
2629
|
+
* allowed: inbound auth is a Bearer header, not a cookie.
|
|
2630
|
+
*/
|
|
2631
|
+
function corsHeadersFor(reqOrigin, allowed) {
|
|
2632
|
+
if (allowed.length === 0) return {};
|
|
2633
|
+
const wildcard = allowed.includes("*");
|
|
2634
|
+
let allowOrigin;
|
|
2635
|
+
if (wildcard) allowOrigin = reqOrigin ?? "*";
|
|
2636
|
+
else if (reqOrigin && allowed.includes(reqOrigin)) allowOrigin = reqOrigin;
|
|
2637
|
+
if (!allowOrigin) return {};
|
|
2638
|
+
const headers = {
|
|
2639
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
2640
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
2641
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
2642
|
+
"Access-Control-Max-Age": "86400"
|
|
2643
|
+
};
|
|
2644
|
+
if (allowOrigin !== "*") headers["Vary"] = "Origin";
|
|
2645
|
+
return headers;
|
|
2646
|
+
}
|
|
2541
2647
|
/** Loopback/same-origin trust fence for the dashboard API (no remote write). */
|
|
2542
2648
|
function trustedLoopback(req) {
|
|
2543
2649
|
const host = req.headers.host;
|
|
@@ -42,6 +42,8 @@ export interface PersistedServerConfig {
|
|
|
42
42
|
endpointPath?: string;
|
|
43
43
|
/** Optional shared bearer token protecting the inbound /a2a endpoint. */
|
|
44
44
|
authToken?: string;
|
|
45
|
+
/** Browser origins allowed to call the inbound A2A routes (CORS allowlist); empty/absent = no CORS. `["*"]` opts into wildcard. */
|
|
46
|
+
corsOrigins?: string[];
|
|
45
47
|
skills?: Array<{
|
|
46
48
|
id: string;
|
|
47
49
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2a-config.d.ts","sourceRoot":"","sources":["../../src/a2a-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,eAAO,MAAM,mBAAmB,aAAa,CAAC;AAE9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACzG;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAChC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAaxD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,SAAS,CAW5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAoD1C;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,kBAAkB,CAW/C;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAatG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,kBAAkB,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"a2a-config.d.ts","sourceRoot":"","sources":["../../src/a2a-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,eAAO,MAAM,mBAAmB,aAAa,CAAC;AAE9C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mIAAmI;IACnI,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACzG;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAChC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAaxD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,SAAS,CAW5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAoD1C;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,kBAAkB,CAW/C;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAatG;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,kBAAkB,GAAG,eAAe,CAoBpG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.css.d.ts","sourceRoot":"","sources":["../../../src/client/dashboard.css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,gBAAgB,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"dashboard.css.d.ts","sourceRoot":"","sources":["../../../src/client/dashboard.css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,gBAAgB,mCAAmC,CAAC;AAwQjE;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,IAAI,CAiBlD"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* on the product's `--dsw-alias-*` design tokens, so light/dark themes and
|
|
14
14
|
* narrow-window layouts follow the DSH shell. No inline styles.
|
|
15
15
|
*/
|
|
16
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
16
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
17
17
|
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
18
18
|
import { type ReactElement } from 'react';
|
|
19
19
|
/** Services this plugin needs from the client runtime. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAG9E,0DAA0D;AAC1D,eAAO,MAAM,MAAM,UAAY,CAAC;AAEhC,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA6B9C;AA8DD,KAAK,YAAY,GAAG,YAAY,CAAC,kBAAkB,CAAC,CAAC;AAiGrD,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAgVnE"}
|
package/lib/types/dashboard.d.ts
CHANGED
|
@@ -101,6 +101,8 @@ export interface DashboardControlHooks {
|
|
|
101
101
|
setServerEnabled?: (enabled: boolean) => Promise<ControlResult>;
|
|
102
102
|
/** Set or clear the inbound server's shared bearer token at runtime (persisted). */
|
|
103
103
|
setServerAuthToken?: (token?: string) => Promise<ControlResult>;
|
|
104
|
+
/** Replace the inbound CORS origin allowlist at runtime (persisted); empty = no CORS. */
|
|
105
|
+
setServerCors?: (origins: string[]) => Promise<ControlResult>;
|
|
104
106
|
/** Read current inbound server status (for the serve panel). */
|
|
105
107
|
serverStatus?: () => ControlResult;
|
|
106
108
|
/** Runtime-remove an outbound agent by its connection id. */
|