@hanphone/dsh-a2a 0.2.0 → 0.3.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/README.md +106 -106
- package/README.zh.md +50 -74
- package/cordis.patch.yml +4 -7
- package/lib/client.js +186 -73
- package/lib/index.js +1275 -868
- package/lib/types/api.d.ts +30 -23
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +56 -23
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts +50 -19
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +59 -46
- package/lib/types/commands.js.map +1 -1
- package/lib/types/events.d.ts +32 -9
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -32
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +177 -272
- package/lib/types/index.js.map +1 -1
- package/lib/types/outbound/calls.d.ts.map +1 -1
- package/lib/types/outbound/calls.js +8 -6
- package/lib/types/outbound/calls.js.map +1 -1
- package/lib/types/protocol.d.ts +174 -109
- package/lib/types/protocol.d.ts.map +1 -1
- package/lib/types/protocol.js +58 -34
- package/lib/types/protocol.js.map +1 -1
- package/lib/types/server/a2a-server.d.ts +4 -0
- package/lib/types/server/a2a-server.d.ts.map +1 -1
- package/lib/types/server/a2a-server.js +6 -2
- package/lib/types/server/a2a-server.js.map +1 -1
- package/lib/types/server/card.d.ts +6 -30
- package/lib/types/server/card.d.ts.map +1 -1
- package/lib/types/server/card.js +13 -49
- package/lib/types/server/card.js.map +1 -1
- package/lib/types/server/exec/agent-runtime.d.ts +20 -0
- package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
- package/lib/types/server/exec/agent-runtime.js +2 -1
- package/lib/types/server/exec/agent-runtime.js.map +1 -1
- package/lib/types/server/routes.d.ts +5 -1
- package/lib/types/server/routes.d.ts.map +1 -1
- package/lib/types/server/routes.js +9 -5
- package/lib/types/server/routes.js.map +1 -1
- package/lib/types/server/store.d.ts +32 -0
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js +10 -0
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +154 -0
- package/lib/types/servers/inbound-manager.d.ts.map +1 -0
- package/lib/types/servers/inbound-manager.js +335 -0
- package/lib/types/servers/inbound-manager.js.map +1 -0
- package/lib/types/servers/outbound-manager.d.ts +129 -0
- package/lib/types/servers/outbound-manager.d.ts.map +1 -0
- package/lib/types/servers/outbound-manager.js +238 -0
- package/lib/types/servers/outbound-manager.js.map +1 -0
- package/lib/types/service.d.ts +104 -40
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +36 -27
- package/lib/types/service.js.map +1 -1
- package/package.json +2 -2
- package/src/api.ts +65 -53
- package/src/client/index.ts +272 -148
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +203 -310
- package/src/outbound/calls.ts +8 -6
- package/src/protocol.ts +204 -95
- package/src/server/a2a-server.ts +9 -2
- package/src/server/card.ts +13 -62
- package/src/server/exec/agent-runtime.ts +20 -2
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +42 -3
- package/src/servers/inbound-manager.ts +416 -0
- package/src/servers/outbound-manager.ts +289 -0
- package/src/service.ts +125 -40
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
package/src/client/index.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A2A settings dashboard — browser half.
|
|
3
3
|
*
|
|
4
|
-
* Registers one `settings.section` ("A2A 连接")
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Registers one `settings.section` ("A2A 连接") managing the multi-instance
|
|
5
|
+
* A2A composition: inbound server instances (create/start/stop/remove/edit,
|
|
6
|
+
* each with its own agent preset, creator-declared skill declarations, and
|
|
7
|
+
* auth env) and outbound server connections (create/start/stop/remove/refresh,
|
|
8
|
+
* each with a remote URL and optional preset). All data and controls live
|
|
9
|
+
* behind the host's loopback-only `/a2a/api` route (src/api.ts); this half is
|
|
10
|
+
* a thin read/write client with no protocol knowledge and no direct storage
|
|
11
|
+
* access.
|
|
10
12
|
*
|
|
11
13
|
* Styling: one idempotent `<style>` injector built on the product's
|
|
12
14
|
* `--dsw-alias-*` design tokens so light/dark themes follow the DSH shell.
|
|
13
|
-
* No inline styles.
|
|
15
|
+
* No inline styles beyond fixed-width inputs.
|
|
14
16
|
* @module dsh-a2a/client
|
|
15
17
|
*/
|
|
16
18
|
|
|
@@ -33,7 +35,7 @@ function injectStyles(): () => void {
|
|
|
33
35
|
.dsh-a2a-panel { display: flex; flex-direction: column; gap: 16px; padding: 12px 0; }
|
|
34
36
|
.dsh-a2a-card { border: 1px solid var(--dsw-alias-border, #d0d7de); border-radius: 8px; padding: 12px 14px; }
|
|
35
37
|
.dsh-a2a-card h3 { margin: 0 0 8px; font-size: 14px; }
|
|
36
|
-
.dsh-a2a-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; }
|
|
38
|
+
.dsh-a2a-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; flex-wrap: wrap; }
|
|
37
39
|
.dsh-a2a-row .muted { color: var(--dsw-alias-text-secondary, #59636e); font-size: 12px; }
|
|
38
40
|
.dsh-a2a-row button { padding: 2px 10px; border-radius: 6px; border: 1px solid var(--dsw-alias-border, #d0d7de); background: transparent; cursor: pointer; }
|
|
39
41
|
.dsh-a2a-row button.primary { background: var(--dsw-alias-accent, #0f6); color: var(--dsw-alias-bg, #fff); border-color: transparent; }
|
|
@@ -41,10 +43,13 @@ function injectStyles(): () => void {
|
|
|
41
43
|
.dsh-a2a-state.ok { background: #1a7f3722; color: #1a7f37; }
|
|
42
44
|
.dsh-a2a-state.bad { background: #d1242f22; color: #d1242f; }
|
|
43
45
|
.dsh-a2a-form { display: flex; gap: 6px; margin: 8px 0; flex-wrap: wrap; }
|
|
44
|
-
.dsh-a2a-form input { padding: 4px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border, #d0d7de); background: transparent; color: inherit; }
|
|
46
|
+
.dsh-a2a-form input, .dsh-a2a-form select, .dsh-a2a-form textarea { padding: 4px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border, #d0d7de); background: transparent; color: inherit; }
|
|
47
|
+
.dsh-a2a-form textarea { font-family: inherit; width: 100%; min-height: 64px; resize: vertical; }
|
|
45
48
|
.dsh-a2a-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
46
|
-
.dsh-a2a-table th, .dsh-a2a-table td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--dsw-alias-border, #d0d7de); }
|
|
49
|
+
.dsh-a2a-table th, .dsh-a2a-table td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--dsw-alias-border, #d0d7de); vertical-align: top; }
|
|
50
|
+
.dsh-a2a-skill { display: inline-block; margin: 1px 4px 1px 0; padding: 0 6px; border-radius: 10px; background: #6e778122; font-size: 12px; }
|
|
47
51
|
.dsh-a2a-error { color: #d1242f; font-size: 12px; }
|
|
52
|
+
.dsh-a2a-hint { color: var(--dsw-alias-text-secondary, #59636e); font-size: 12px; margin: 2px 0 6px; }
|
|
48
53
|
`
|
|
49
54
|
document.head.appendChild(tag)
|
|
50
55
|
return () => tag.remove()
|
|
@@ -53,8 +58,7 @@ function injectStyles(): () => void {
|
|
|
53
58
|
export function apply(ctx: ClientContext): void {
|
|
54
59
|
ctx.effect(() => injectStyles(), `${NS}: dashboard styles`)
|
|
55
60
|
// settings.section is declared at runtime by ui-settings-general; slots.inject
|
|
56
|
-
// defers registration until that declaration exists and follows its lifetime
|
|
57
|
-
// (a bare register before the declaration can fail or land invisible).
|
|
61
|
+
// defers registration until that declaration exists and follows its lifetime.
|
|
58
62
|
ctx.slots.inject('settings.section', () => ctx.slots.register(
|
|
59
63
|
{
|
|
60
64
|
name: 'settings.section',
|
|
@@ -67,37 +71,59 @@ export function apply(ctx: ClientContext): void {
|
|
|
67
71
|
))
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
/** Wire types — mirror the host's src/api.ts
|
|
71
|
-
export interface
|
|
72
|
-
readonly
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
readonly skills: readonly string[]
|
|
76
|
-
readonly name?: string
|
|
77
|
-
readonly description?: string
|
|
78
|
-
readonly version?: string
|
|
79
|
-
readonly configured: boolean
|
|
80
|
-
}
|
|
81
|
-
readonly tasks: readonly unknown[]
|
|
82
|
-
readonly agents: readonly unknown[]
|
|
83
|
-
readonly inbounds: readonly unknown[]
|
|
74
|
+
/** Wire types — mirror the host's src/api.ts + src/service.ts views. */
|
|
75
|
+
export interface SkillView {
|
|
76
|
+
readonly id: string
|
|
77
|
+
readonly name: string
|
|
78
|
+
readonly description?: string
|
|
84
79
|
}
|
|
85
80
|
|
|
86
|
-
interface
|
|
81
|
+
export interface InboundServerView {
|
|
82
|
+
readonly id: string
|
|
83
|
+
readonly name: string
|
|
84
|
+
readonly description: string
|
|
85
|
+
readonly version: string
|
|
86
|
+
readonly endpointPath: string
|
|
87
|
+
readonly preset?: string
|
|
88
|
+
readonly authTokenEnv?: string
|
|
89
|
+
readonly cardPath: string
|
|
90
|
+
readonly cardUrl?: string
|
|
91
|
+
readonly enabled: boolean
|
|
92
|
+
readonly skills: readonly SkillView[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface OutboundServerView {
|
|
87
96
|
readonly id: string
|
|
88
97
|
readonly name: string
|
|
89
98
|
readonly agentCardUrl: string
|
|
99
|
+
readonly preset?: string
|
|
90
100
|
readonly enabled: boolean
|
|
101
|
+
readonly timeoutMs: number
|
|
91
102
|
readonly state: string
|
|
92
103
|
readonly skillCount: number
|
|
93
104
|
readonly toolCount: number
|
|
94
105
|
readonly lastError?: string
|
|
95
106
|
}
|
|
96
107
|
|
|
108
|
+
export interface PresetView {
|
|
109
|
+
readonly id: string
|
|
110
|
+
readonly name?: string
|
|
111
|
+
readonly description?: string
|
|
112
|
+
/** Whether this preset is the deployment default when none is named. */
|
|
113
|
+
readonly isDefault?: boolean
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface ApiSnapshot {
|
|
117
|
+
readonly inbounds: readonly InboundServerView[]
|
|
118
|
+
readonly outbounds: readonly OutboundServerView[]
|
|
119
|
+
readonly tasks: readonly unknown[]
|
|
120
|
+
readonly peers: readonly unknown[]
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
interface InboundPeerView {
|
|
98
124
|
readonly id: string
|
|
99
125
|
readonly label: string
|
|
100
|
-
readonly source?: string
|
|
126
|
+
readonly source?: string | null
|
|
101
127
|
readonly firstSeen?: string
|
|
102
128
|
readonly lastSeen?: string
|
|
103
129
|
readonly taskCount: number
|
|
@@ -118,6 +144,12 @@ async function fetchSnapshot(): Promise<ApiSnapshot> {
|
|
|
118
144
|
return (await res.json()) as ApiSnapshot
|
|
119
145
|
}
|
|
120
146
|
|
|
147
|
+
async function fetchPresets(): Promise<PresetView[]> {
|
|
148
|
+
const res = await fetch('/a2a/api/presets', { headers: { accept: 'application/json' } })
|
|
149
|
+
if (!res.ok) return []
|
|
150
|
+
return (await res.json()) as PresetView[]
|
|
151
|
+
}
|
|
152
|
+
|
|
121
153
|
async function postControl(payload: Record<string, unknown>): Promise<{ readonly ok: boolean; readonly message: string }> {
|
|
122
154
|
const res = await fetch('/a2a/api', {
|
|
123
155
|
method: 'POST',
|
|
@@ -132,29 +164,38 @@ type SectionProps = PropsRuntime<'settings.section'>
|
|
|
132
164
|
|
|
133
165
|
export function A2aSection(_props: SectionProps): ReactElement {
|
|
134
166
|
const [snap, setSnap] = useState<ApiSnapshot | undefined>(undefined)
|
|
167
|
+
const [presets, setPresets] = useState<readonly PresetView[]>([])
|
|
135
168
|
const [error, setError] = useState<string | undefined>(undefined)
|
|
136
|
-
const [agentName, setAgentName] = useState('')
|
|
137
|
-
const [agentUrl, setAgentUrl] = useState('')
|
|
138
169
|
const [busy, setBusy] = useState(false)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const [
|
|
142
|
-
const [
|
|
170
|
+
|
|
171
|
+
// Inbound create/edit form state.
|
|
172
|
+
const [editingIn, setEditingIn] = useState<string | undefined>(undefined)
|
|
173
|
+
const [inName, setInName] = useState('')
|
|
174
|
+
const [inDesc, setInDesc] = useState('')
|
|
175
|
+
const [inVersion, setInVersion] = useState('1.0.0')
|
|
176
|
+
const [inPreset, setInPreset] = useState('')
|
|
177
|
+
const [inAuthEnv, setInAuthEnv] = useState('')
|
|
178
|
+
|
|
179
|
+
// Outbound create form state.
|
|
180
|
+
const [outName, setOutName] = useState('')
|
|
181
|
+
const [outUrl, setOutUrl] = useState('')
|
|
182
|
+
const [outPreset, setOutPreset] = useState('')
|
|
183
|
+
const [outTokenEnv, setOutTokenEnv] = useState('')
|
|
184
|
+
const [outTimeout, setOutTimeout] = useState('60000')
|
|
143
185
|
|
|
144
186
|
useEffect(() => {
|
|
145
187
|
let alive = true
|
|
146
188
|
const tick = async (): Promise<void> => {
|
|
147
189
|
try {
|
|
148
|
-
const next = await fetchSnapshot()
|
|
190
|
+
const [next, presetRows] = await Promise.all([fetchSnapshot(), fetchPresets()])
|
|
149
191
|
if (alive) {
|
|
150
192
|
setSnap(next)
|
|
193
|
+
setPresets(presetRows)
|
|
194
|
+
// Default an unset preset picker to the deployment default (if any).
|
|
195
|
+
const def = presetRows.find((p) => p.isDefault)?.id
|
|
196
|
+
setInPreset((current) => current === '' && def !== undefined ? def : current)
|
|
197
|
+
setOutPreset((current) => current === '' && def !== undefined ? def : current)
|
|
151
198
|
setError(undefined)
|
|
152
|
-
// Seed the identity form from the live card when not editing.
|
|
153
|
-
if (!editing) {
|
|
154
|
-
setIdName(next.server.name ?? '')
|
|
155
|
-
setIdDescription(next.server.description ?? '')
|
|
156
|
-
setIdVersion(next.server.version ?? '')
|
|
157
|
-
}
|
|
158
199
|
}
|
|
159
200
|
} catch (err) {
|
|
160
201
|
if (alive) setError((err as Error).message)
|
|
@@ -181,9 +222,69 @@ export function A2aSection(_props: SectionProps): ReactElement {
|
|
|
181
222
|
}
|
|
182
223
|
}
|
|
183
224
|
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
225
|
+
const startCreateInbound = (): void => {
|
|
226
|
+
setEditingIn(undefined)
|
|
227
|
+
setInName('')
|
|
228
|
+
setInDesc('')
|
|
229
|
+
setInVersion('1.0.0')
|
|
230
|
+
setInPreset(presets.find((p) => p.isDefault)?.id ?? '')
|
|
231
|
+
setInAuthEnv('')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const submitInbound = (): void => {
|
|
235
|
+
if (!inName.trim()) return
|
|
236
|
+
if (editingIn !== undefined) {
|
|
237
|
+
void control({
|
|
238
|
+
action: 'inbound.update',
|
|
239
|
+
id: editingIn,
|
|
240
|
+
name: inName.trim(),
|
|
241
|
+
description: inDesc.trim(),
|
|
242
|
+
version: inVersion.trim() || '1.0.0',
|
|
243
|
+
...(inPreset !== '' ? { preset: inPreset } : {}),
|
|
244
|
+
...(inAuthEnv.trim() !== '' ? { authTokenEnv: inAuthEnv.trim() } : {}),
|
|
245
|
+
}).then(() => setEditingIn(undefined))
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
void control({
|
|
249
|
+
action: 'inbound.create',
|
|
250
|
+
name: inName.trim(),
|
|
251
|
+
description: inDesc.trim() || 'An A2A inbound server',
|
|
252
|
+
version: inVersion.trim() || '1.0.0',
|
|
253
|
+
...(inPreset !== '' ? { preset: inPreset } : {}),
|
|
254
|
+
...(inAuthEnv.trim() !== '' ? { authTokenEnv: inAuthEnv.trim() } : {}),
|
|
255
|
+
}).then(() => startCreateInbound())
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const beginEditInbound = (v: InboundServerView): void => {
|
|
259
|
+
setEditingIn(v.id)
|
|
260
|
+
setInName(v.name)
|
|
261
|
+
setInDesc(v.description)
|
|
262
|
+
setInVersion(v.version)
|
|
263
|
+
setInPreset(v.preset ?? presets.find((p) => p.isDefault)?.id ?? '')
|
|
264
|
+
setInAuthEnv(v.authTokenEnv ?? '')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const submitOutbound = (): void => {
|
|
268
|
+
if (!outName.trim() || !outUrl.trim()) return
|
|
269
|
+
const timeout = Number.parseInt(outTimeout, 10)
|
|
270
|
+
void control({
|
|
271
|
+
action: 'outbound.create',
|
|
272
|
+
name: outName.trim(),
|
|
273
|
+
agentCardUrl: outUrl.trim(),
|
|
274
|
+
...(outPreset !== '' ? { preset: outPreset } : {}),
|
|
275
|
+
...(outTokenEnv.trim() !== '' ? { bearerTokenEnv: outTokenEnv.trim() } : {}),
|
|
276
|
+
...(Number.isFinite(timeout) && timeout > 0 ? { timeoutMs: timeout } : {}),
|
|
277
|
+
}).then(() => {
|
|
278
|
+
setOutName('')
|
|
279
|
+
setOutUrl('')
|
|
280
|
+
setOutPreset(presets.find((p) => p.isDefault)?.id ?? '')
|
|
281
|
+
setOutTokenEnv('')
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const inbounds = snap?.inbounds ?? []
|
|
286
|
+
const outbounds = snap?.outbounds ?? []
|
|
287
|
+
const peers = (snap?.peers ?? []) as readonly InboundPeerView[]
|
|
187
288
|
const tasks = (snap?.tasks ?? []) as readonly TaskView[]
|
|
188
289
|
|
|
189
290
|
return createElement(
|
|
@@ -191,170 +292,193 @@ export function A2aSection(_props: SectionProps): ReactElement {
|
|
|
191
292
|
{ className: 'dsh-a2a-panel' },
|
|
192
293
|
error !== undefined ? createElement('div', { className: 'dsh-a2a-error' }, error) : null,
|
|
193
294
|
|
|
194
|
-
// ── inbound server
|
|
295
|
+
// ── inbound server instances ────────────────────────────────────────
|
|
195
296
|
createElement(
|
|
196
297
|
'div',
|
|
197
298
|
{ className: 'dsh-a2a-card' },
|
|
198
|
-
createElement('h3', null,
|
|
199
|
-
|
|
200
|
-
? createElement('div', { className: 'muted' }, '
|
|
299
|
+
createElement('h3', null, `入站 Servers(${inbounds.length})`),
|
|
300
|
+
inbounds.length === 0
|
|
301
|
+
? createElement('div', { className: 'muted' }, '还没有入站 server。创建一个即可对外发布 A2A 端点。')
|
|
201
302
|
: createElement(
|
|
202
|
-
'
|
|
203
|
-
{ className: 'dsh-a2a-
|
|
204
|
-
createElement(
|
|
205
|
-
'
|
|
206
|
-
|
|
207
|
-
|
|
303
|
+
'table',
|
|
304
|
+
{ className: 'dsh-a2a-table' },
|
|
305
|
+
createElement('thead', null,
|
|
306
|
+
createElement('tr', null,
|
|
307
|
+
createElement('th', null, '名称 / 端点'),
|
|
308
|
+
createElement('th', null, 'Preset'),
|
|
309
|
+
createElement('th', null, '技能宣告'),
|
|
310
|
+
createElement('th', null, '状态'),
|
|
311
|
+
createElement('th', null, '操作'),
|
|
312
|
+
),
|
|
208
313
|
),
|
|
209
|
-
createElement(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
314
|
+
createElement('tbody', null,
|
|
315
|
+
...inbounds.map((v) =>
|
|
316
|
+
createElement('tr', { key: v.id },
|
|
317
|
+
createElement('td', null,
|
|
318
|
+
createElement('div', null, v.name),
|
|
319
|
+
createElement('div', { className: 'muted' }, `${v.endpointPath} · ${v.cardUrl ?? v.cardPath}`),
|
|
320
|
+
v.authTokenEnv !== undefined && v.authTokenEnv !== ''
|
|
321
|
+
? createElement('div', { className: 'muted' }, `鉴权: $${v.authTokenEnv}`)
|
|
322
|
+
: null,
|
|
323
|
+
),
|
|
324
|
+
createElement('td', null, v.preset ?? createElement('span', { className: 'muted' }, '默认')),
|
|
325
|
+
createElement('td', null,
|
|
326
|
+
createElement('div', null,
|
|
327
|
+
...v.skills.map((s) =>
|
|
328
|
+
createElement('span', { className: 'dsh-a2a-skill', key: s.id, title: s.description ?? '' }, s.name),
|
|
329
|
+
),
|
|
330
|
+
),
|
|
331
|
+
),
|
|
332
|
+
createElement('td', null,
|
|
333
|
+
createElement('span', { className: `dsh-a2a-state ${v.enabled ? 'ok' : 'bad'}` }, v.enabled ? '运行中' : '已停用'),
|
|
334
|
+
),
|
|
335
|
+
createElement('td', null,
|
|
336
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: v.enabled ? 'inbound.disable' : 'inbound.enable', id: v.id }) }, v.enabled ? '停用' : '启用'),
|
|
337
|
+
' ',
|
|
338
|
+
createElement('button', { disabled: busy, onClick: () => beginEditInbound(v) }, '编辑'),
|
|
339
|
+
' ',
|
|
340
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'inbound.remove', id: v.id }) }, '删除'),
|
|
341
|
+
),
|
|
342
|
+
),
|
|
343
|
+
),
|
|
217
344
|
),
|
|
218
345
|
),
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
346
|
+
createElement('h3', { style: { marginTop: '12px' } }, editingIn !== undefined ? '编辑入站 Server' : '新建入站 Server'),
|
|
347
|
+
createElement(
|
|
348
|
+
'form',
|
|
349
|
+
{
|
|
350
|
+
className: 'dsh-a2a-form',
|
|
351
|
+
onSubmit: (ev: { preventDefault(): void }) => {
|
|
352
|
+
ev.preventDefault()
|
|
353
|
+
submitInbound()
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
createElement('input', { value: inName, placeholder: '名称', onChange: (e: { target: { value: string } }) => setInName(e.target.value) }),
|
|
357
|
+
createElement('input', { value: inVersion, placeholder: '版本', onChange: (e: { target: { value: string } }) => setInVersion(e.target.value), style: { width: '70px' } }),
|
|
358
|
+
createElement('select', { value: inPreset, onChange: (e: { target: { value: string } }) => setInPreset(e.target.value) },
|
|
359
|
+
...presets.map((p) => createElement('option', { value: p.id, key: p.id, title: p.description ?? p.id }, p.name ?? p.id)),
|
|
360
|
+
),
|
|
361
|
+
createElement('input', { value: inAuthEnv, placeholder: '鉴权 env 变量名(可选)', onChange: (e: { target: { value: string } }) => setInAuthEnv(e.target.value) }),
|
|
362
|
+
),
|
|
235
363
|
createElement(
|
|
236
364
|
'form',
|
|
237
365
|
{
|
|
238
366
|
className: 'dsh-a2a-form',
|
|
239
367
|
onSubmit: (ev: { preventDefault(): void }) => {
|
|
240
368
|
ev.preventDefault()
|
|
241
|
-
|
|
242
|
-
setEditing(false)
|
|
243
|
-
})
|
|
369
|
+
submitInbound()
|
|
244
370
|
},
|
|
371
|
+
style: { alignItems: 'flex-end' },
|
|
245
372
|
},
|
|
246
|
-
createElement('input', { value:
|
|
247
|
-
createElement('
|
|
248
|
-
createElement('
|
|
249
|
-
createElement('button', { type: 'submit', className: 'primary', disabled: busy }, '保存身份'),
|
|
373
|
+
createElement('input', { value: inDesc, placeholder: '描述', onChange: (e: { target: { value: string } }) => setInDesc(e.target.value), style: { flex: 1, minWidth: '260px' } }),
|
|
374
|
+
createElement('button', { type: 'submit', className: 'primary', disabled: busy || !inName.trim() }, editingIn !== undefined ? '保存修改' : '创建 Server'),
|
|
375
|
+
editingIn !== undefined ? createElement('button', { type: 'button', disabled: busy, onClick: () => startCreateInbound() }, '取消') : null,
|
|
250
376
|
),
|
|
251
|
-
createElement('div', { className: 'dsh-a2a-
|
|
377
|
+
createElement('div', { className: 'dsh-a2a-hint' }, '该 server 绑定的 preset 决定其技能(一切皆插件):AgentCard 技能 = 该 preset 可用的模型技能,创建后自动派生。'),
|
|
252
378
|
),
|
|
253
379
|
|
|
254
|
-
// ──
|
|
380
|
+
// ── outbound server instances ───────────────────────────────────────
|
|
255
381
|
createElement(
|
|
256
382
|
'div',
|
|
257
383
|
{ className: 'dsh-a2a-card' },
|
|
258
|
-
createElement('h3', null,
|
|
259
|
-
|
|
260
|
-
? createElement('div', { className: 'muted' }, '
|
|
384
|
+
createElement('h3', null, `出站 Servers(${outbounds.length})`),
|
|
385
|
+
outbounds.length === 0
|
|
386
|
+
? createElement('div', { className: 'muted' }, '还没有出站 server。添加一个远端 A2A 连接即可把其技能注册为模型工具。')
|
|
261
387
|
: createElement(
|
|
262
388
|
'table',
|
|
263
389
|
{ className: 'dsh-a2a-table' },
|
|
264
390
|
createElement('thead', null,
|
|
265
391
|
createElement('tr', null,
|
|
266
|
-
createElement('th', null, '
|
|
267
|
-
createElement('th', null, '
|
|
268
|
-
createElement('th', null, '
|
|
269
|
-
createElement('th', null, '
|
|
392
|
+
createElement('th', null, '名称 / 远端'),
|
|
393
|
+
createElement('th', null, 'Preset'),
|
|
394
|
+
createElement('th', null, '状态'),
|
|
395
|
+
createElement('th', null, '工具'),
|
|
270
396
|
createElement('th', null, '操作'),
|
|
271
397
|
),
|
|
272
398
|
),
|
|
273
399
|
createElement('tbody', null,
|
|
274
|
-
...
|
|
275
|
-
createElement('tr', { key:
|
|
276
|
-
createElement('td', null, p.source ?? p.label),
|
|
277
|
-
createElement('td', null, `${p.taskCount}(活跃 ${p.activeTaskIds.length})`),
|
|
278
|
-
createElement('td', null, p.streaming ? '●' : '—'),
|
|
400
|
+
...outbounds.map((v) =>
|
|
401
|
+
createElement('tr', { key: v.id },
|
|
279
402
|
createElement('td', null,
|
|
280
|
-
createElement('
|
|
403
|
+
createElement('div', null, v.name),
|
|
404
|
+
createElement('div', { className: 'muted' }, v.agentCardUrl),
|
|
405
|
+
v.lastError !== undefined ? createElement('div', { className: 'dsh-a2a-error' }, v.lastError) : null,
|
|
281
406
|
),
|
|
407
|
+
createElement('td', null, v.preset ?? createElement('span', { className: 'muted' }, '默认')),
|
|
282
408
|
createElement('td', null,
|
|
283
|
-
createElement('
|
|
409
|
+
createElement('span', { className: `dsh-a2a-state ${v.state === 'connected' ? 'ok' : 'bad'}` }, v.state === 'connected' ? '已连接' : v.state),
|
|
410
|
+
),
|
|
411
|
+
createElement('td', null, `${v.toolCount} / ${v.skillCount}`),
|
|
412
|
+
createElement('td', null,
|
|
413
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: v.enabled ? 'outbound.disable' : 'outbound.enable', id: v.id }) }, v.enabled ? '停用' : '启用'),
|
|
414
|
+
' ',
|
|
415
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'outbound.refresh', id: v.id }) }, '刷新'),
|
|
416
|
+
' ',
|
|
417
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'outbound.remove', id: v.id }) }, '删除'),
|
|
284
418
|
),
|
|
285
419
|
),
|
|
286
420
|
),
|
|
287
421
|
),
|
|
288
422
|
),
|
|
423
|
+
createElement('h3', { style: { marginTop: '12px' } }, '新建出站 Server'),
|
|
424
|
+
createElement(
|
|
425
|
+
'form',
|
|
426
|
+
{
|
|
427
|
+
className: 'dsh-a2a-form',
|
|
428
|
+
onSubmit: (ev: { preventDefault(): void }) => {
|
|
429
|
+
ev.preventDefault()
|
|
430
|
+
submitOutbound()
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
createElement('input', { value: outName, placeholder: '名称', onChange: (e: { target: { value: string } }) => setOutName(e.target.value) }),
|
|
434
|
+
createElement('input', { value: outUrl, placeholder: '远端 AgentCard URL', onChange: (e: { target: { value: string } }) => setOutUrl(e.target.value), style: { flex: 1, minWidth: '240px' } }),
|
|
435
|
+
createElement('select', { value: outPreset, onChange: (e: { target: { value: string } }) => setOutPreset(e.target.value) },
|
|
436
|
+
...presets.map((p) => createElement('option', { value: p.id, key: p.id, title: p.description ?? p.id }, p.name ?? p.id)),
|
|
437
|
+
),
|
|
438
|
+
createElement('input', { value: outTokenEnv, placeholder: 'Bearer env 变量名(可选)', onChange: (e: { target: { value: string } }) => setOutTokenEnv(e.target.value) }),
|
|
439
|
+
createElement('input', { value: outTimeout, placeholder: '超时 ms', onChange: (e: { target: { value: string } }) => setOutTimeout(e.target.value), style: { width: '90px' } }),
|
|
440
|
+
createElement('button', { type: 'submit', className: 'primary', disabled: busy || !outName.trim() || !outUrl.trim() }, '添加出站 Server'),
|
|
441
|
+
),
|
|
289
442
|
),
|
|
290
443
|
|
|
291
|
-
// ──
|
|
444
|
+
// ── inbound peer monitoring ─────────────────────────────────────────
|
|
292
445
|
createElement(
|
|
293
446
|
'div',
|
|
294
447
|
{ className: 'dsh-a2a-card' },
|
|
295
|
-
createElement('h3', null,
|
|
296
|
-
|
|
297
|
-
? createElement('div', { className: 'muted' }, '
|
|
448
|
+
createElement('h3', null, `入站连接(${peers.length})`),
|
|
449
|
+
peers.length === 0
|
|
450
|
+
? createElement('div', { className: 'muted' }, '当前无远程对端正在调用本服务。')
|
|
298
451
|
: createElement(
|
|
299
452
|
'table',
|
|
300
453
|
{ className: 'dsh-a2a-table' },
|
|
301
|
-
createElement(
|
|
302
|
-
'thead',
|
|
303
|
-
null,
|
|
454
|
+
createElement('thead', null,
|
|
304
455
|
createElement('tr', null,
|
|
305
|
-
createElement('th', null, '
|
|
306
|
-
createElement('th', null, '
|
|
307
|
-
createElement('th', null, '
|
|
456
|
+
createElement('th', null, '来源'),
|
|
457
|
+
createElement('th', null, '任务'),
|
|
458
|
+
createElement('th', null, '流式'),
|
|
459
|
+
createElement('th', null, '首次 / 最近'),
|
|
308
460
|
createElement('th', null, '操作'),
|
|
309
461
|
),
|
|
310
462
|
),
|
|
311
|
-
createElement(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
'
|
|
317
|
-
{ key: a.id },
|
|
318
|
-
createElement('td', null,
|
|
319
|
-
createElement('div', null, a.name),
|
|
320
|
-
createElement('div', { className: 'muted' }, a.agentCardUrl),
|
|
321
|
-
a.lastError !== undefined ? createElement('div', { className: 'dsh-a2a-error' }, a.lastError) : null,
|
|
322
|
-
),
|
|
463
|
+
createElement('tbody', null,
|
|
464
|
+
...peers.map((p) =>
|
|
465
|
+
createElement('tr', { key: p.id },
|
|
466
|
+
createElement('td', null, p.source ?? p.label),
|
|
467
|
+
createElement('td', null, `${p.taskCount}(活跃 ${p.activeTaskIds.length})`),
|
|
468
|
+
createElement('td', null, p.streaming ? '●' : '—'),
|
|
323
469
|
createElement('td', null,
|
|
324
|
-
createElement('span', { className:
|
|
470
|
+
createElement('span', { className: 'muted' }, `${p.firstSeen ?? ''} / ${p.lastSeen ?? ''}`),
|
|
325
471
|
),
|
|
326
|
-
createElement('td', null, `${a.toolCount} / ${a.skillCount}`),
|
|
327
472
|
createElement('td', null,
|
|
328
|
-
createElement('button', { disabled: busy, onClick: () => void control({ action:
|
|
329
|
-
' ',
|
|
330
|
-
createElement('button', { disabled: busy, onClick: () => void control({ action: 'agent.refresh', id: a.id }) }, '刷新'),
|
|
331
|
-
' ',
|
|
332
|
-
createElement('button', { disabled: busy, onClick: () => void control({ action: 'agent.remove', id: a.id }) }, '删除'),
|
|
473
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'inbound.close', id: p.id }) }, '关闭'),
|
|
333
474
|
),
|
|
334
475
|
),
|
|
335
476
|
),
|
|
336
477
|
),
|
|
337
478
|
),
|
|
338
|
-
createElement(
|
|
339
|
-
'form',
|
|
340
|
-
{
|
|
341
|
-
className: 'dsh-a2a-form',
|
|
342
|
-
onSubmit: (ev: { preventDefault(): void }) => {
|
|
343
|
-
ev.preventDefault()
|
|
344
|
-
if (!agentName.trim() || !agentUrl.trim()) return
|
|
345
|
-
void control({ action: 'agent.add', name: agentName.trim(), agentCardUrl: agentUrl.trim() }).then(() => {
|
|
346
|
-
setAgentName('')
|
|
347
|
-
setAgentUrl('')
|
|
348
|
-
})
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
createElement('input', { value: agentName, placeholder: '名称', onChange: (e: { target: { value: string } }) => setAgentName(e.target.value) }),
|
|
352
|
-
createElement('input', { value: agentUrl, placeholder: 'AgentCard URL', onChange: (e: { target: { value: string } }) => setAgentUrl(e.target.value) }),
|
|
353
|
-
createElement('button', { type: 'submit', className: 'primary', disabled: busy }, '添加 Agent'),
|
|
354
|
-
),
|
|
355
479
|
),
|
|
356
480
|
|
|
357
|
-
// ── tasks
|
|
481
|
+
// ── tasks ───────────────────────────────────────────────────────────
|
|
358
482
|
createElement(
|
|
359
483
|
'div',
|
|
360
484
|
{ className: 'dsh-a2a-card' },
|