@hanphone/dsh-a2a 0.1.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.md +133 -87
- package/README.zh.md +64 -87
- package/cordis.patch.yml +4 -7
- package/lib/client.js +228 -36
- package/lib/index.js +1271 -625
- package/lib/types/api.d.ts +43 -13
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +60 -20
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts +50 -14
- 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 +178 -213
- 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 +7 -1
- package/lib/types/server/a2a-server.d.ts.map +1 -1
- package/lib/types/server/a2a-server.js +11 -3
- 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 +3 -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/identity.d.ts +38 -0
- package/lib/types/server/identity.d.ts.map +1 -0
- package/lib/types/server/identity.js +89 -0
- package/lib/types/server/identity.js.map +1 -0
- package/lib/types/server/inbound-registry.d.ts +60 -0
- package/lib/types/server/inbound-registry.d.ts.map +1 -0
- package/lib/types/server/inbound-registry.js +86 -0
- package/lib/types/server/inbound-registry.js.map +1 -0
- 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 +37 -1
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js +14 -0
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +131 -0
- package/lib/types/servers/inbound-manager.d.ts.map +1 -0
- package/lib/types/servers/inbound-manager.js +312 -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 +108 -23
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +39 -18
- package/lib/types/service.js.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +70 -31
- package/src/client/index.ts +339 -81
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +204 -249
- package/src/outbound/calls.ts +8 -6
- package/src/protocol.ts +204 -95
- package/src/server/a2a-server.ts +16 -4
- package/src/server/card.ts +13 -62
- package/src/server/exec/agent-runtime.ts +5 -2
- package/src/server/identity.ts +109 -0
- package/src/server/inbound-registry.ts +129 -0
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +50 -4
- package/src/servers/inbound-manager.ts +385 -0
- package/src/servers/outbound-manager.ts +289 -0
- package/src/service.ts +136 -28
- 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,24 +71,66 @@ 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
|
-
readonly
|
|
74
|
-
readonly
|
|
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
|
|
79
|
+
}
|
|
80
|
+
|
|
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[]
|
|
75
93
|
}
|
|
76
94
|
|
|
77
|
-
interface
|
|
95
|
+
export interface OutboundServerView {
|
|
78
96
|
readonly id: string
|
|
79
97
|
readonly name: string
|
|
80
98
|
readonly agentCardUrl: string
|
|
99
|
+
readonly preset?: string
|
|
81
100
|
readonly enabled: boolean
|
|
101
|
+
readonly timeoutMs: number
|
|
82
102
|
readonly state: string
|
|
83
103
|
readonly skillCount: number
|
|
84
104
|
readonly toolCount: number
|
|
85
105
|
readonly lastError?: string
|
|
86
106
|
}
|
|
87
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
|
+
|
|
123
|
+
interface InboundPeerView {
|
|
124
|
+
readonly id: string
|
|
125
|
+
readonly label: string
|
|
126
|
+
readonly source?: string | null
|
|
127
|
+
readonly firstSeen?: string
|
|
128
|
+
readonly lastSeen?: string
|
|
129
|
+
readonly taskCount: number
|
|
130
|
+
readonly activeTaskIds: readonly string[]
|
|
131
|
+
readonly streaming: boolean
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
interface TaskView {
|
|
89
135
|
readonly id: string
|
|
90
136
|
readonly contextId?: string
|
|
@@ -98,6 +144,12 @@ async function fetchSnapshot(): Promise<ApiSnapshot> {
|
|
|
98
144
|
return (await res.json()) as ApiSnapshot
|
|
99
145
|
}
|
|
100
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
|
+
|
|
101
153
|
async function postControl(payload: Record<string, unknown>): Promise<{ readonly ok: boolean; readonly message: string }> {
|
|
102
154
|
const res = await fetch('/a2a/api', {
|
|
103
155
|
method: 'POST',
|
|
@@ -108,21 +160,59 @@ async function postControl(payload: Record<string, unknown>): Promise<{ readonly
|
|
|
108
160
|
return body
|
|
109
161
|
}
|
|
110
162
|
|
|
163
|
+
/** One skill declaration per line: `id|name|description` (name/description default to id). */
|
|
164
|
+
function parseSkills(text: string): SkillView[] | undefined {
|
|
165
|
+
const lines = text.split('\n').map((l) => l.trim()).filter(Boolean)
|
|
166
|
+
if (lines.length === 0) return undefined
|
|
167
|
+
return lines.map((line) => {
|
|
168
|
+
const [id, name, ...descParts] = line.split('|').map((p) => p.trim())
|
|
169
|
+
const skillId = id ?? ''
|
|
170
|
+
return {
|
|
171
|
+
id: skillId,
|
|
172
|
+
name: name && name.length > 0 ? name : skillId,
|
|
173
|
+
...(descParts.length > 0 || (descParts[0]?.length ?? 0) > 0 ? { description: descParts.join('|') } : {}),
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function skillsToText(skills: readonly SkillView[]): string {
|
|
179
|
+
return skills.map((s) => [s.id, s.name, s.description].filter((p) => p !== undefined && p.length > 0).join('|')).join('\n')
|
|
180
|
+
}
|
|
181
|
+
|
|
111
182
|
type SectionProps = PropsRuntime<'settings.section'>
|
|
112
183
|
|
|
113
184
|
export function A2aSection(_props: SectionProps): ReactElement {
|
|
114
185
|
const [snap, setSnap] = useState<ApiSnapshot | undefined>(undefined)
|
|
186
|
+
const [presets, setPresets] = useState<readonly PresetView[]>([])
|
|
115
187
|
const [error, setError] = useState<string | undefined>(undefined)
|
|
116
|
-
const [agentName, setAgentName] = useState('')
|
|
117
|
-
const [agentUrl, setAgentUrl] = useState('')
|
|
118
188
|
const [busy, setBusy] = useState(false)
|
|
119
189
|
|
|
190
|
+
// Inbound create/edit form state.
|
|
191
|
+
const [editingIn, setEditingIn] = useState<string | undefined>(undefined)
|
|
192
|
+
const [inName, setInName] = useState('')
|
|
193
|
+
const [inDesc, setInDesc] = useState('')
|
|
194
|
+
const [inVersion, setInVersion] = useState('1.0.0')
|
|
195
|
+
const [inPreset, setInPreset] = useState('')
|
|
196
|
+
const [inAuthEnv, setInAuthEnv] = useState('')
|
|
197
|
+
const [inSkills, setInSkills] = useState('')
|
|
198
|
+
|
|
199
|
+
// Outbound create form state.
|
|
200
|
+
const [outName, setOutName] = useState('')
|
|
201
|
+
const [outUrl, setOutUrl] = useState('')
|
|
202
|
+
const [outPreset, setOutPreset] = useState('')
|
|
203
|
+
const [outTokenEnv, setOutTokenEnv] = useState('')
|
|
204
|
+
const [outTimeout, setOutTimeout] = useState('60000')
|
|
205
|
+
|
|
120
206
|
useEffect(() => {
|
|
121
207
|
let alive = true
|
|
122
208
|
const tick = async (): Promise<void> => {
|
|
123
209
|
try {
|
|
124
|
-
const next = await fetchSnapshot()
|
|
125
|
-
if (alive) {
|
|
210
|
+
const [next, presetRows] = await Promise.all([fetchSnapshot(), fetchPresets()])
|
|
211
|
+
if (alive) {
|
|
212
|
+
setSnap(next)
|
|
213
|
+
setPresets(presetRows)
|
|
214
|
+
setError(undefined)
|
|
215
|
+
}
|
|
126
216
|
} catch (err) {
|
|
127
217
|
if (alive) setError((err as Error).message)
|
|
128
218
|
}
|
|
@@ -148,8 +238,74 @@ export function A2aSection(_props: SectionProps): ReactElement {
|
|
|
148
238
|
}
|
|
149
239
|
}
|
|
150
240
|
|
|
151
|
-
const
|
|
152
|
-
|
|
241
|
+
const startCreateInbound = (): void => {
|
|
242
|
+
setEditingIn(undefined)
|
|
243
|
+
setInName('')
|
|
244
|
+
setInDesc('')
|
|
245
|
+
setInVersion('1.0.0')
|
|
246
|
+
setInPreset('')
|
|
247
|
+
setInAuthEnv('')
|
|
248
|
+
setInSkills('')
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const submitInbound = (): void => {
|
|
252
|
+
if (!inName.trim()) return
|
|
253
|
+
const skills = parseSkills(inSkills)
|
|
254
|
+
if (editingIn !== undefined) {
|
|
255
|
+
void control({
|
|
256
|
+
action: 'inbound.update',
|
|
257
|
+
id: editingIn,
|
|
258
|
+
name: inName.trim(),
|
|
259
|
+
description: inDesc.trim(),
|
|
260
|
+
version: inVersion.trim() || '1.0.0',
|
|
261
|
+
...(inPreset !== '' ? { preset: inPreset } : {}),
|
|
262
|
+
...(inAuthEnv.trim() !== '' ? { authTokenEnv: inAuthEnv.trim() } : {}),
|
|
263
|
+
...(skills !== undefined ? { skills } : {}),
|
|
264
|
+
}).then(() => setEditingIn(undefined))
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
void control({
|
|
268
|
+
action: 'inbound.create',
|
|
269
|
+
name: inName.trim(),
|
|
270
|
+
description: inDesc.trim() || 'An A2A inbound server',
|
|
271
|
+
version: inVersion.trim() || '1.0.0',
|
|
272
|
+
...(inPreset !== '' ? { preset: inPreset } : {}),
|
|
273
|
+
...(inAuthEnv.trim() !== '' ? { authTokenEnv: inAuthEnv.trim() } : {}),
|
|
274
|
+
...(skills !== undefined ? { skills } : {}),
|
|
275
|
+
}).then(() => startCreateInbound())
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const beginEditInbound = (v: InboundServerView): void => {
|
|
279
|
+
setEditingIn(v.id)
|
|
280
|
+
setInName(v.name)
|
|
281
|
+
setInDesc(v.description)
|
|
282
|
+
setInVersion(v.version)
|
|
283
|
+
setInPreset(v.preset ?? '')
|
|
284
|
+
setInAuthEnv(v.authTokenEnv ?? '')
|
|
285
|
+
setInSkills(skillsToText(v.skills))
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const submitOutbound = (): void => {
|
|
289
|
+
if (!outName.trim() || !outUrl.trim()) return
|
|
290
|
+
const timeout = Number.parseInt(outTimeout, 10)
|
|
291
|
+
void control({
|
|
292
|
+
action: 'outbound.create',
|
|
293
|
+
name: outName.trim(),
|
|
294
|
+
agentCardUrl: outUrl.trim(),
|
|
295
|
+
...(outPreset !== '' ? { preset: outPreset } : {}),
|
|
296
|
+
...(outTokenEnv.trim() !== '' ? { bearerTokenEnv: outTokenEnv.trim() } : {}),
|
|
297
|
+
...(Number.isFinite(timeout) && timeout > 0 ? { timeoutMs: timeout } : {}),
|
|
298
|
+
}).then(() => {
|
|
299
|
+
setOutName('')
|
|
300
|
+
setOutUrl('')
|
|
301
|
+
setOutPreset('')
|
|
302
|
+
setOutTokenEnv('')
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const inbounds = snap?.inbounds ?? []
|
|
307
|
+
const outbounds = snap?.outbounds ?? []
|
|
308
|
+
const peers = (snap?.peers ?? []) as readonly InboundPeerView[]
|
|
153
309
|
const tasks = (snap?.tasks ?? []) as readonly TaskView[]
|
|
154
310
|
|
|
155
311
|
return createElement(
|
|
@@ -157,107 +313,209 @@ export function A2aSection(_props: SectionProps): ReactElement {
|
|
|
157
313
|
{ className: 'dsh-a2a-panel' },
|
|
158
314
|
error !== undefined ? createElement('div', { className: 'dsh-a2a-error' }, error) : null,
|
|
159
315
|
|
|
160
|
-
// ── inbound server
|
|
316
|
+
// ── inbound server instances ────────────────────────────────────────
|
|
161
317
|
createElement(
|
|
162
318
|
'div',
|
|
163
319
|
{ className: 'dsh-a2a-card' },
|
|
164
|
-
createElement('h3', null,
|
|
165
|
-
|
|
166
|
-
? createElement('div', { className: 'muted' }, '
|
|
320
|
+
createElement('h3', null, `入站 Servers(${inbounds.length})`),
|
|
321
|
+
inbounds.length === 0
|
|
322
|
+
? createElement('div', { className: 'muted' }, '还没有入站 server。创建一个即可对外发布 A2A 端点。')
|
|
167
323
|
: createElement(
|
|
168
|
-
'
|
|
169
|
-
{ className: 'dsh-a2a-
|
|
170
|
-
createElement(
|
|
171
|
-
'
|
|
172
|
-
|
|
173
|
-
|
|
324
|
+
'table',
|
|
325
|
+
{ className: 'dsh-a2a-table' },
|
|
326
|
+
createElement('thead', null,
|
|
327
|
+
createElement('tr', null,
|
|
328
|
+
createElement('th', null, '名称 / 端点'),
|
|
329
|
+
createElement('th', null, 'Preset'),
|
|
330
|
+
createElement('th', null, '技能宣告'),
|
|
331
|
+
createElement('th', null, '状态'),
|
|
332
|
+
createElement('th', null, '操作'),
|
|
333
|
+
),
|
|
174
334
|
),
|
|
175
|
-
createElement(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
335
|
+
createElement('tbody', null,
|
|
336
|
+
...inbounds.map((v) =>
|
|
337
|
+
createElement('tr', { key: v.id },
|
|
338
|
+
createElement('td', null,
|
|
339
|
+
createElement('div', null, v.name),
|
|
340
|
+
createElement('div', { className: 'muted' }, `${v.endpointPath} · ${v.cardUrl ?? v.cardPath}`),
|
|
341
|
+
v.authTokenEnv !== undefined && v.authTokenEnv !== ''
|
|
342
|
+
? createElement('div', { className: 'muted' }, `鉴权: $${v.authTokenEnv}`)
|
|
343
|
+
: null,
|
|
344
|
+
),
|
|
345
|
+
createElement('td', null, v.preset ?? createElement('span', { className: 'muted' }, '默认')),
|
|
346
|
+
createElement('td', null,
|
|
347
|
+
createElement('div', null,
|
|
348
|
+
...v.skills.map((s) =>
|
|
349
|
+
createElement('span', { className: 'dsh-a2a-skill', key: s.id, title: s.description ?? '' }, s.name),
|
|
350
|
+
),
|
|
351
|
+
),
|
|
352
|
+
),
|
|
353
|
+
createElement('td', null,
|
|
354
|
+
createElement('span', { className: `dsh-a2a-state ${v.enabled ? 'ok' : 'bad'}` }, v.enabled ? '运行中' : '已停用'),
|
|
355
|
+
),
|
|
356
|
+
createElement('td', null,
|
|
357
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: v.enabled ? 'inbound.disable' : 'inbound.enable', id: v.id }) }, v.enabled ? '停用' : '启用'),
|
|
358
|
+
' ',
|
|
359
|
+
createElement('button', { disabled: busy, onClick: () => beginEditInbound(v) }, '编辑'),
|
|
360
|
+
' ',
|
|
361
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'inbound.remove', id: v.id }) }, '删除'),
|
|
362
|
+
),
|
|
363
|
+
),
|
|
364
|
+
),
|
|
183
365
|
),
|
|
184
366
|
),
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
)
|
|
191
|
-
|
|
367
|
+
createElement('h3', { style: { marginTop: '12px' } }, editingIn !== undefined ? '编辑入站 Server' : '新建入站 Server'),
|
|
368
|
+
createElement(
|
|
369
|
+
'form',
|
|
370
|
+
{
|
|
371
|
+
className: 'dsh-a2a-form',
|
|
372
|
+
onSubmit: (ev: { preventDefault(): void }) => {
|
|
373
|
+
ev.preventDefault()
|
|
374
|
+
submitInbound()
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
createElement('input', { value: inName, placeholder: '名称', onChange: (e: { target: { value: string } }) => setInName(e.target.value) }),
|
|
378
|
+
createElement('input', { value: inVersion, placeholder: '版本', onChange: (e: { target: { value: string } }) => setInVersion(e.target.value), style: { width: '70px' } }),
|
|
379
|
+
createElement('select', { value: inPreset, onChange: (e: { target: { value: string } }) => setInPreset(e.target.value) },
|
|
380
|
+
createElement('option', { value: '' }, 'Preset(默认)'),
|
|
381
|
+
...presets.map((p) => createElement('option', { value: p.id, key: p.id }, `${p.name ?? p.id}${p.isDefault ? '(默认)' : ''}`)),
|
|
382
|
+
),
|
|
383
|
+
createElement('input', { value: inAuthEnv, placeholder: '鉴权 env 变量名(可选)', onChange: (e: { target: { value: string } }) => setInAuthEnv(e.target.value) }),
|
|
384
|
+
),
|
|
385
|
+
createElement(
|
|
386
|
+
'form',
|
|
387
|
+
{
|
|
388
|
+
className: 'dsh-a2a-form',
|
|
389
|
+
onSubmit: (ev: { preventDefault(): void }) => {
|
|
390
|
+
ev.preventDefault()
|
|
391
|
+
submitInbound()
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
createElement('textarea', {
|
|
395
|
+
value: inSkills,
|
|
396
|
+
placeholder: '技能宣告(每行 id|名称|描述,留空默认取 preset 名)',
|
|
397
|
+
onChange: (e: { target: { value: string } }) => setInSkills(e.target.value),
|
|
398
|
+
}),
|
|
399
|
+
),
|
|
400
|
+
createElement(
|
|
401
|
+
'form',
|
|
402
|
+
{
|
|
403
|
+
className: 'dsh-a2a-form',
|
|
404
|
+
onSubmit: (ev: { preventDefault(): void }) => {
|
|
405
|
+
ev.preventDefault()
|
|
406
|
+
submitInbound()
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
createElement('input', { value: inDesc, placeholder: '描述', onChange: (e: { target: { value: string } }) => setInDesc(e.target.value), style: { flex: 1, minWidth: '260px' } }),
|
|
410
|
+
createElement('button', { type: 'submit', className: 'primary', disabled: busy || !inName.trim() }, editingIn !== undefined ? '保存修改' : '创建 Server'),
|
|
411
|
+
editingIn !== undefined ? createElement('button', { type: 'button', disabled: busy, onClick: () => startCreateInbound() }, '取消') : null,
|
|
412
|
+
),
|
|
413
|
+
createElement('div', { className: 'dsh-a2a-hint' }, '技能宣告由创建者输入,默认取所绑定的 preset 展示名(无 preset 时为内置 chat)。'),
|
|
192
414
|
),
|
|
193
415
|
|
|
194
|
-
// ── outbound
|
|
416
|
+
// ── outbound server instances ───────────────────────────────────────
|
|
195
417
|
createElement(
|
|
196
418
|
'div',
|
|
197
419
|
{ className: 'dsh-a2a-card' },
|
|
198
|
-
createElement('h3', null, `出站
|
|
199
|
-
|
|
200
|
-
? createElement('div', { className: 'muted' }, '
|
|
420
|
+
createElement('h3', null, `出站 Servers(${outbounds.length})`),
|
|
421
|
+
outbounds.length === 0
|
|
422
|
+
? createElement('div', { className: 'muted' }, '还没有出站 server。添加一个远端 A2A 连接即可把其技能注册为模型工具。')
|
|
201
423
|
: createElement(
|
|
202
424
|
'table',
|
|
203
425
|
{ className: 'dsh-a2a-table' },
|
|
204
|
-
createElement(
|
|
205
|
-
'thead',
|
|
206
|
-
null,
|
|
426
|
+
createElement('thead', null,
|
|
207
427
|
createElement('tr', null,
|
|
208
|
-
createElement('th', null, '名称'),
|
|
428
|
+
createElement('th', null, '名称 / 远端'),
|
|
429
|
+
createElement('th', null, 'Preset'),
|
|
209
430
|
createElement('th', null, '状态'),
|
|
210
431
|
createElement('th', null, '工具'),
|
|
211
432
|
createElement('th', null, '操作'),
|
|
212
433
|
),
|
|
213
434
|
),
|
|
214
|
-
createElement(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
...agents.map((a) =>
|
|
218
|
-
createElement(
|
|
219
|
-
'tr',
|
|
220
|
-
{ key: a.id },
|
|
435
|
+
createElement('tbody', null,
|
|
436
|
+
...outbounds.map((v) =>
|
|
437
|
+
createElement('tr', { key: v.id },
|
|
221
438
|
createElement('td', null,
|
|
222
|
-
createElement('div', null,
|
|
223
|
-
createElement('div', { className: 'muted' },
|
|
224
|
-
|
|
439
|
+
createElement('div', null, v.name),
|
|
440
|
+
createElement('div', { className: 'muted' }, v.agentCardUrl),
|
|
441
|
+
v.lastError !== undefined ? createElement('div', { className: 'dsh-a2a-error' }, v.lastError) : null,
|
|
225
442
|
),
|
|
443
|
+
createElement('td', null, v.preset ?? createElement('span', { className: 'muted' }, '默认')),
|
|
226
444
|
createElement('td', null,
|
|
227
|
-
createElement('span', { className: `dsh-a2a-state ${
|
|
445
|
+
createElement('span', { className: `dsh-a2a-state ${v.state === 'connected' ? 'ok' : 'bad'}` }, v.state === 'connected' ? '已连接' : v.state),
|
|
228
446
|
),
|
|
229
|
-
createElement('td', null, `${
|
|
447
|
+
createElement('td', null, `${v.toolCount} / ${v.skillCount}`),
|
|
230
448
|
createElement('td', null,
|
|
231
|
-
createElement('button', { disabled: busy, onClick: () => void control({ action:
|
|
449
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: v.enabled ? 'outbound.disable' : 'outbound.enable', id: v.id }) }, v.enabled ? '停用' : '启用'),
|
|
232
450
|
' ',
|
|
233
|
-
createElement('button', { disabled: busy, onClick: () => void control({ action: '
|
|
451
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'outbound.refresh', id: v.id }) }, '刷新'),
|
|
234
452
|
' ',
|
|
235
|
-
createElement('button', { disabled: busy, onClick: () => void control({ action: '
|
|
453
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'outbound.remove', id: v.id }) }, '删除'),
|
|
236
454
|
),
|
|
237
455
|
),
|
|
238
456
|
),
|
|
239
457
|
),
|
|
240
458
|
),
|
|
459
|
+
createElement('h3', { style: { marginTop: '12px' } }, '新建出站 Server'),
|
|
241
460
|
createElement(
|
|
242
461
|
'form',
|
|
243
462
|
{
|
|
244
463
|
className: 'dsh-a2a-form',
|
|
245
464
|
onSubmit: (ev: { preventDefault(): void }) => {
|
|
246
465
|
ev.preventDefault()
|
|
247
|
-
|
|
248
|
-
void control({ action: 'agent.add', name: agentName.trim(), agentCardUrl: agentUrl.trim() }).then(() => {
|
|
249
|
-
setAgentName('')
|
|
250
|
-
setAgentUrl('')
|
|
251
|
-
})
|
|
466
|
+
submitOutbound()
|
|
252
467
|
},
|
|
253
468
|
},
|
|
254
|
-
createElement('input', { value:
|
|
255
|
-
createElement('input', { value:
|
|
256
|
-
createElement('
|
|
469
|
+
createElement('input', { value: outName, placeholder: '名称', onChange: (e: { target: { value: string } }) => setOutName(e.target.value) }),
|
|
470
|
+
createElement('input', { value: outUrl, placeholder: '远端 AgentCard URL', onChange: (e: { target: { value: string } }) => setOutUrl(e.target.value), style: { flex: 1, minWidth: '240px' } }),
|
|
471
|
+
createElement('select', { value: outPreset, onChange: (e: { target: { value: string } }) => setOutPreset(e.target.value) },
|
|
472
|
+
createElement('option', { value: '' }, 'Preset(默认)'),
|
|
473
|
+
...presets.map((p) => createElement('option', { value: p.id, key: p.id }, `${p.name ?? p.id}${p.isDefault ? '(默认)' : ''}`)),
|
|
474
|
+
),
|
|
475
|
+
createElement('input', { value: outTokenEnv, placeholder: 'Bearer env 变量名(可选)', onChange: (e: { target: { value: string } }) => setOutTokenEnv(e.target.value) }),
|
|
476
|
+
createElement('input', { value: outTimeout, placeholder: '超时 ms', onChange: (e: { target: { value: string } }) => setOutTimeout(e.target.value), style: { width: '90px' } }),
|
|
477
|
+
createElement('button', { type: 'submit', className: 'primary', disabled: busy || !outName.trim() || !outUrl.trim() }, '添加出站 Server'),
|
|
257
478
|
),
|
|
258
479
|
),
|
|
259
480
|
|
|
260
|
-
// ──
|
|
481
|
+
// ── inbound peer monitoring ─────────────────────────────────────────
|
|
482
|
+
createElement(
|
|
483
|
+
'div',
|
|
484
|
+
{ className: 'dsh-a2a-card' },
|
|
485
|
+
createElement('h3', null, `入站连接(${peers.length})`),
|
|
486
|
+
peers.length === 0
|
|
487
|
+
? createElement('div', { className: 'muted' }, '当前无远程对端正在调用本服务。')
|
|
488
|
+
: createElement(
|
|
489
|
+
'table',
|
|
490
|
+
{ className: 'dsh-a2a-table' },
|
|
491
|
+
createElement('thead', null,
|
|
492
|
+
createElement('tr', null,
|
|
493
|
+
createElement('th', null, '来源'),
|
|
494
|
+
createElement('th', null, '任务'),
|
|
495
|
+
createElement('th', null, '流式'),
|
|
496
|
+
createElement('th', null, '首次 / 最近'),
|
|
497
|
+
createElement('th', null, '操作'),
|
|
498
|
+
),
|
|
499
|
+
),
|
|
500
|
+
createElement('tbody', null,
|
|
501
|
+
...peers.map((p) =>
|
|
502
|
+
createElement('tr', { key: p.id },
|
|
503
|
+
createElement('td', null, p.source ?? p.label),
|
|
504
|
+
createElement('td', null, `${p.taskCount}(活跃 ${p.activeTaskIds.length})`),
|
|
505
|
+
createElement('td', null, p.streaming ? '●' : '—'),
|
|
506
|
+
createElement('td', null,
|
|
507
|
+
createElement('span', { className: 'muted' }, `${p.firstSeen ?? ''} / ${p.lastSeen ?? ''}`),
|
|
508
|
+
),
|
|
509
|
+
createElement('td', null,
|
|
510
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'inbound.close', id: p.id }) }, '关闭'),
|
|
511
|
+
),
|
|
512
|
+
),
|
|
513
|
+
),
|
|
514
|
+
),
|
|
515
|
+
),
|
|
516
|
+
),
|
|
517
|
+
|
|
518
|
+
// ── tasks ───────────────────────────────────────────────────────────
|
|
261
519
|
createElement(
|
|
262
520
|
'div',
|
|
263
521
|
{ className: 'dsh-a2a-card' },
|