@hanphone/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.md +101 -106
- package/README.zh.md +50 -74
- package/cordis.patch.yml +4 -7
- package/lib/client.js +217 -73
- package/lib/index.js +1258 -867
- package/lib/types/api.d.ts +40 -23
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +58 -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 +173 -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 +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/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 +35 -1
- 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 +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 +106 -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 +67 -53
- package/src/client/index.ts +309 -148
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +199 -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 +5 -2
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +45 -4
- package/src/servers/inbound-manager.ts +385 -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',
|
|
@@ -128,33 +160,58 @@ async function postControl(payload: Record<string, unknown>): Promise<{ readonly
|
|
|
128
160
|
return body
|
|
129
161
|
}
|
|
130
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
|
+
|
|
131
182
|
type SectionProps = PropsRuntime<'settings.section'>
|
|
132
183
|
|
|
133
184
|
export function A2aSection(_props: SectionProps): ReactElement {
|
|
134
185
|
const [snap, setSnap] = useState<ApiSnapshot | undefined>(undefined)
|
|
186
|
+
const [presets, setPresets] = useState<readonly PresetView[]>([])
|
|
135
187
|
const [error, setError] = useState<string | undefined>(undefined)
|
|
136
|
-
const [agentName, setAgentName] = useState('')
|
|
137
|
-
const [agentUrl, setAgentUrl] = useState('')
|
|
138
188
|
const [busy, setBusy] = useState(false)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const [
|
|
142
|
-
const [
|
|
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')
|
|
143
205
|
|
|
144
206
|
useEffect(() => {
|
|
145
207
|
let alive = true
|
|
146
208
|
const tick = async (): Promise<void> => {
|
|
147
209
|
try {
|
|
148
|
-
const next = await fetchSnapshot()
|
|
210
|
+
const [next, presetRows] = await Promise.all([fetchSnapshot(), fetchPresets()])
|
|
149
211
|
if (alive) {
|
|
150
212
|
setSnap(next)
|
|
213
|
+
setPresets(presetRows)
|
|
151
214
|
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
215
|
}
|
|
159
216
|
} catch (err) {
|
|
160
217
|
if (alive) setError((err as Error).message)
|
|
@@ -181,9 +238,74 @@ export function A2aSection(_props: SectionProps): ReactElement {
|
|
|
181
238
|
}
|
|
182
239
|
}
|
|
183
240
|
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
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[]
|
|
187
309
|
const tasks = (snap?.tasks ?? []) as readonly TaskView[]
|
|
188
310
|
|
|
189
311
|
return createElement(
|
|
@@ -191,170 +313,209 @@ export function A2aSection(_props: SectionProps): ReactElement {
|
|
|
191
313
|
{ className: 'dsh-a2a-panel' },
|
|
192
314
|
error !== undefined ? createElement('div', { className: 'dsh-a2a-error' }, error) : null,
|
|
193
315
|
|
|
194
|
-
// ── inbound server
|
|
316
|
+
// ── inbound server instances ────────────────────────────────────────
|
|
195
317
|
createElement(
|
|
196
318
|
'div',
|
|
197
319
|
{ className: 'dsh-a2a-card' },
|
|
198
|
-
createElement('h3', null,
|
|
199
|
-
|
|
200
|
-
? createElement('div', { className: 'muted' }, '
|
|
320
|
+
createElement('h3', null, `入站 Servers(${inbounds.length})`),
|
|
321
|
+
inbounds.length === 0
|
|
322
|
+
? createElement('div', { className: 'muted' }, '还没有入站 server。创建一个即可对外发布 A2A 端点。')
|
|
201
323
|
: createElement(
|
|
202
|
-
'
|
|
203
|
-
{ className: 'dsh-a2a-
|
|
204
|
-
createElement(
|
|
205
|
-
'
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
),
|
|
208
334
|
),
|
|
209
|
-
createElement(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
+
),
|
|
217
365
|
),
|
|
218
366
|
),
|
|
219
|
-
|
|
220
|
-
? createElement(
|
|
221
|
-
'div',
|
|
222
|
-
{ className: 'dsh-a2a-row muted' },
|
|
223
|
-
`AgentCard: ${server.cardUrl ?? '(n/a)'} · 技能: ${server.skills.join(', ') || '(chat)'}`,
|
|
224
|
-
)
|
|
225
|
-
: null,
|
|
226
|
-
),
|
|
227
|
-
|
|
228
|
-
// ── service identity (guided first-run + edit) ───────────────────────
|
|
229
|
-
createElement(
|
|
230
|
-
'div',
|
|
231
|
-
{ className: 'dsh-a2a-card' },
|
|
232
|
-
server === undefined || (server as { configured?: boolean }).configured === false
|
|
233
|
-
? createElement('h3', null, '服务身份(首次配置)')
|
|
234
|
-
: createElement('h3', null, '服务身份'),
|
|
367
|
+
createElement('h3', { style: { marginTop: '12px' } }, editingIn !== undefined ? '编辑入站 Server' : '新建入站 Server'),
|
|
235
368
|
createElement(
|
|
236
369
|
'form',
|
|
237
370
|
{
|
|
238
371
|
className: 'dsh-a2a-form',
|
|
239
372
|
onSubmit: (ev: { preventDefault(): void }) => {
|
|
240
373
|
ev.preventDefault()
|
|
241
|
-
|
|
242
|
-
setEditing(false)
|
|
243
|
-
})
|
|
374
|
+
submitInbound()
|
|
244
375
|
},
|
|
245
376
|
},
|
|
246
|
-
createElement('input', { value:
|
|
247
|
-
createElement('input', { value:
|
|
248
|
-
createElement('
|
|
249
|
-
|
|
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) }),
|
|
250
384
|
),
|
|
251
|
-
createElement(
|
|
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)。'),
|
|
252
414
|
),
|
|
253
415
|
|
|
254
|
-
// ──
|
|
416
|
+
// ── outbound server instances ───────────────────────────────────────
|
|
255
417
|
createElement(
|
|
256
418
|
'div',
|
|
257
419
|
{ className: 'dsh-a2a-card' },
|
|
258
|
-
createElement('h3', null,
|
|
259
|
-
|
|
260
|
-
? createElement('div', { className: 'muted' }, '
|
|
420
|
+
createElement('h3', null, `出站 Servers(${outbounds.length})`),
|
|
421
|
+
outbounds.length === 0
|
|
422
|
+
? createElement('div', { className: 'muted' }, '还没有出站 server。添加一个远端 A2A 连接即可把其技能注册为模型工具。')
|
|
261
423
|
: createElement(
|
|
262
424
|
'table',
|
|
263
425
|
{ className: 'dsh-a2a-table' },
|
|
264
426
|
createElement('thead', null,
|
|
265
427
|
createElement('tr', null,
|
|
266
|
-
createElement('th', null, '
|
|
267
|
-
createElement('th', null, '
|
|
268
|
-
createElement('th', null, '
|
|
269
|
-
createElement('th', null, '
|
|
428
|
+
createElement('th', null, '名称 / 远端'),
|
|
429
|
+
createElement('th', null, 'Preset'),
|
|
430
|
+
createElement('th', null, '状态'),
|
|
431
|
+
createElement('th', null, '工具'),
|
|
270
432
|
createElement('th', null, '操作'),
|
|
271
433
|
),
|
|
272
434
|
),
|
|
273
435
|
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 ? '●' : '—'),
|
|
436
|
+
...outbounds.map((v) =>
|
|
437
|
+
createElement('tr', { key: v.id },
|
|
279
438
|
createElement('td', null,
|
|
280
|
-
createElement('
|
|
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,
|
|
281
442
|
),
|
|
443
|
+
createElement('td', null, v.preset ?? createElement('span', { className: 'muted' }, '默认')),
|
|
282
444
|
createElement('td', null,
|
|
283
|
-
createElement('
|
|
445
|
+
createElement('span', { className: `dsh-a2a-state ${v.state === 'connected' ? 'ok' : 'bad'}` }, v.state === 'connected' ? '已连接' : v.state),
|
|
446
|
+
),
|
|
447
|
+
createElement('td', null, `${v.toolCount} / ${v.skillCount}`),
|
|
448
|
+
createElement('td', null,
|
|
449
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: v.enabled ? 'outbound.disable' : 'outbound.enable', id: v.id }) }, v.enabled ? '停用' : '启用'),
|
|
450
|
+
' ',
|
|
451
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'outbound.refresh', id: v.id }) }, '刷新'),
|
|
452
|
+
' ',
|
|
453
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'outbound.remove', id: v.id }) }, '删除'),
|
|
284
454
|
),
|
|
285
455
|
),
|
|
286
456
|
),
|
|
287
457
|
),
|
|
288
458
|
),
|
|
459
|
+
createElement('h3', { style: { marginTop: '12px' } }, '新建出站 Server'),
|
|
460
|
+
createElement(
|
|
461
|
+
'form',
|
|
462
|
+
{
|
|
463
|
+
className: 'dsh-a2a-form',
|
|
464
|
+
onSubmit: (ev: { preventDefault(): void }) => {
|
|
465
|
+
ev.preventDefault()
|
|
466
|
+
submitOutbound()
|
|
467
|
+
},
|
|
468
|
+
},
|
|
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'),
|
|
478
|
+
),
|
|
289
479
|
),
|
|
290
480
|
|
|
291
|
-
// ──
|
|
481
|
+
// ── inbound peer monitoring ─────────────────────────────────────────
|
|
292
482
|
createElement(
|
|
293
483
|
'div',
|
|
294
484
|
{ className: 'dsh-a2a-card' },
|
|
295
|
-
createElement('h3', null,
|
|
296
|
-
|
|
297
|
-
? createElement('div', { className: 'muted' }, '
|
|
485
|
+
createElement('h3', null, `入站连接(${peers.length})`),
|
|
486
|
+
peers.length === 0
|
|
487
|
+
? createElement('div', { className: 'muted' }, '当前无远程对端正在调用本服务。')
|
|
298
488
|
: createElement(
|
|
299
489
|
'table',
|
|
300
490
|
{ className: 'dsh-a2a-table' },
|
|
301
|
-
createElement(
|
|
302
|
-
'thead',
|
|
303
|
-
null,
|
|
491
|
+
createElement('thead', null,
|
|
304
492
|
createElement('tr', null,
|
|
305
|
-
createElement('th', null, '
|
|
306
|
-
createElement('th', null, '
|
|
307
|
-
createElement('th', null, '
|
|
493
|
+
createElement('th', null, '来源'),
|
|
494
|
+
createElement('th', null, '任务'),
|
|
495
|
+
createElement('th', null, '流式'),
|
|
496
|
+
createElement('th', null, '首次 / 最近'),
|
|
308
497
|
createElement('th', null, '操作'),
|
|
309
498
|
),
|
|
310
499
|
),
|
|
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
|
-
),
|
|
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 ? '●' : '—'),
|
|
323
506
|
createElement('td', null,
|
|
324
|
-
createElement('span', { className:
|
|
507
|
+
createElement('span', { className: 'muted' }, `${p.firstSeen ?? ''} / ${p.lastSeen ?? ''}`),
|
|
325
508
|
),
|
|
326
|
-
createElement('td', null, `${a.toolCount} / ${a.skillCount}`),
|
|
327
509
|
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 }) }, '删除'),
|
|
510
|
+
createElement('button', { disabled: busy, onClick: () => void control({ action: 'inbound.close', id: p.id }) }, '关闭'),
|
|
333
511
|
),
|
|
334
512
|
),
|
|
335
513
|
),
|
|
336
514
|
),
|
|
337
515
|
),
|
|
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
516
|
),
|
|
356
517
|
|
|
357
|
-
// ── tasks
|
|
518
|
+
// ── tasks ───────────────────────────────────────────────────────────
|
|
358
519
|
createElement(
|
|
359
520
|
'div',
|
|
360
521
|
{ className: 'dsh-a2a-card' },
|