@ryubyte/dsh-a2a 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +41 -19
- package/README.md +27 -13
- package/lib/client.js +601 -443
- package/lib/index.js +185 -14
- package/lib/types/a2a-config.d.ts.map +1 -1
- package/lib/types/client/dashboard.css.d.ts +20 -0
- package/lib/types/client/dashboard.css.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +4 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/dashboard.d.ts +20 -0
- package/lib/types/dashboard.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/server.d.ts +33 -1
- package/lib/types/server.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/a2a-config.ts +15 -2
- package/src/client/dashboard.css.ts +295 -0
- package/src/client/index.ts +669 -265
- package/src/dashboard.ts +39 -0
- package/src/index.ts +138 -17
- package/src/server.ts +67 -1
package/lib/client.js
CHANGED
|
@@ -5,10 +5,304 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
+
//#region src/client/dashboard.css.ts
|
|
9
|
+
/**
|
|
10
|
+
* dsh-a2a — dashboard stylesheet.
|
|
11
|
+
*
|
|
12
|
+
* The browser half is a compiled bundle (not a dynamic cordis plugin), so it
|
|
13
|
+
* injects its own stylesheet the same way first-party DSH client UI packages
|
|
14
|
+
* do: a single idempotent <style> tag, styled entirely with the product's
|
|
15
|
+
* `--dsw-alias-*` design tokens (so light/dark themes and future token
|
|
16
|
+
* changes apply automatically), plus `@media` queries for narrow windows.
|
|
17
|
+
*
|
|
18
|
+
* Class names are prefixed with `a2a-` to avoid collisions with product or
|
|
19
|
+
* third-party styles.
|
|
20
|
+
*/
|
|
21
|
+
const DASHBOARD_CSS_ID = "@ryubyte/dsh-a2a/dashboard.css";
|
|
22
|
+
const css = String.raw`
|
|
23
|
+
/* ── layout ─────────────────────────────────────────────── */
|
|
24
|
+
/*
|
|
25
|
+
* The settings drawer is a fixed-ish narrow column (~560px), unrelated to the
|
|
26
|
+
* browser viewport, so responsive rules key off the container width, not the
|
|
27
|
+
* viewport. .a2a-root is our own wrapper (the host's immediate parent is
|
|
28
|
+
* display:contents and can't be a query container), scoped as an inline-size
|
|
29
|
+
* container so the @container rules below react to the real panel width.
|
|
30
|
+
*/
|
|
31
|
+
.a2a-root { container-type: inline-size; container-name: a2a; }
|
|
32
|
+
.a2a-section { display: flex; flex-direction: column; gap: 20px; transition: gap .2s ease; }
|
|
33
|
+
.a2a-stack { display: flex; flex-direction: column; gap: 16px; }
|
|
34
|
+
.a2a-crew { display: flex; flex-direction: column; gap: 10px; }
|
|
35
|
+
.a2a-card-list { display: flex; flex-direction: column; gap: 8px; }
|
|
36
|
+
.a2a-btn-row { display: flex; gap: 8px; margin-top: 2px; }
|
|
37
|
+
.a2a-intro { color: var(--dsw-alias-label-secondary, #61666b); font-size: 13px; line-height: 1.6; margin: 0; }
|
|
38
|
+
.a2a-h3 { margin: 0; font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); display: flex; align-items: center; gap: 8px; }
|
|
39
|
+
.a2a-count { font-size: 12px; font-weight: 400; color: var(--dsw-alias-label-tertiary, #81858c); background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.1)); border-radius: 10px; padding: 1px 8px; }
|
|
40
|
+
.a2a-empty { color: var(--dsw-alias-label-tertiary, #81858c); font-size: 13px; background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.06)); border-radius: 8px; padding: 12px 14px; }
|
|
41
|
+
.a2a-hint { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); line-height: 1.6; }
|
|
42
|
+
.a2a-mono { font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"); }
|
|
43
|
+
|
|
44
|
+
/* status / error / notice */
|
|
45
|
+
.a2a-notice { padding: 8px 12px; border-radius: 8px; font-size: 13px; background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.08)); color: var(--dsw-alias-label-primary, #0f1115); }
|
|
46
|
+
.a2a-error { color: var(--dsw-alias-state-error-primary, #d92d20); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #d92d20) 9%, transparent); }
|
|
47
|
+
|
|
48
|
+
/* state dot */
|
|
49
|
+
.a2a-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--dsw-alias-label-tertiary, #81858c); flex: none; }
|
|
50
|
+
.a2a-dot[data-state='connected'] { background: var(--dsw-alias-state-success-primary, #12b76a); }
|
|
51
|
+
.a2a-dot[data-state='reconnecting'] { background: var(--dsw-alias-state-warn-primary, #f79009); }
|
|
52
|
+
.a2a-dot[data-state='disabled'] { background: var(--dsw-alias-state-warn-primary, #f79009); }
|
|
53
|
+
|
|
54
|
+
/* ---- buttons ---- */
|
|
55
|
+
.a2a-btn {
|
|
56
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 4px;
|
|
57
|
+
margin-right: 6px; padding: 4px 12px; font-size: 12px; line-height: 18px;
|
|
58
|
+
border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3));
|
|
59
|
+
background: transparent; color: var(--dsw-alias-label-primary, #0f1115);
|
|
60
|
+
cursor: pointer; transition: background .15s, border-color .15s, color .15s, opacity .15s;
|
|
61
|
+
}
|
|
62
|
+
.a2a-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.08)); border-color: var(--dsw-alias-border-l3, rgba(127,127,127,.45)); }
|
|
63
|
+
.a2a-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4a7dff); outline-offset: 1px; }
|
|
64
|
+
.a2a-btn:disabled { opacity: .5; cursor: default; }
|
|
65
|
+
.a2a-btn:last-child { margin-right: 0; }
|
|
66
|
+
.a2a-btn-primary { background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary, #4a7dff)); border-color: transparent; color: #fff; }
|
|
67
|
+
.a2a-btn-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-brand-primary, #4a7dff)); border-color: transparent; }
|
|
68
|
+
|
|
69
|
+
/* ---- copy button ---- */
|
|
70
|
+
.a2a-copy-btn {
|
|
71
|
+
flex: none; margin-left: 8px; padding: 2px 8px; font-size: 11px; line-height: 16px;
|
|
72
|
+
border-radius: 6px; border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3));
|
|
73
|
+
background: transparent; color: var(--dsw-alias-label-tertiary, #81858c);
|
|
74
|
+
cursor: pointer; white-space: nowrap;
|
|
75
|
+
transition: color .15s, border-color .15s, background .15s;
|
|
76
|
+
}
|
|
77
|
+
.a2a-copy-btn:hover:not(:disabled) { color: var(--dsw-alias-label-primary, #0f1115); border-color: var(--dsw-alias-border-l3, rgba(127,127,127,.45)); background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.08)); }
|
|
78
|
+
.a2a-copy-btn:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4a7dff); outline-offset: 1px; }
|
|
79
|
+
.a2a-copy-btn[data-copied='true'] { color: var(--dsw-alias-state-success-primary, #12b76a); border-color: var(--dsw-alias-state-success-primary, #12b76a); background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #12b76a) 10%, transparent); }
|
|
80
|
+
|
|
81
|
+
/* ---- meta rows (label + value + copy) ---- */
|
|
82
|
+
.a2a-meta-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
83
|
+
.a2a-meta-key { flex: none; font-size: 12px; color: var(--dsw-alias-label-secondary, #61666b); }
|
|
84
|
+
.a2a-meta-row .a2a-mono { flex: 1; min-width: 0; word-break: break-all; }
|
|
85
|
+
.a2a-meta-row .a2a-copy-btn { margin-left: auto; flex: none; }
|
|
86
|
+
|
|
87
|
+
/* ---- tabs (underline, matching first-party settings pages) ---- */
|
|
88
|
+
.a2a-tabs { display: flex; align-items: flex-end; gap: 22px; border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.1)); margin-top: 2px; transition: gap .2s ease; }
|
|
89
|
+
.a2a-tab {
|
|
90
|
+
position: relative; padding: 7px 1px 9px; font-size: 13px; line-height: 20px; font-weight: 400;
|
|
91
|
+
color: var(--dsw-alias-label-tertiary, #81858c); background: transparent; border: none;
|
|
92
|
+
cursor: pointer; transition: color .15s;
|
|
93
|
+
}
|
|
94
|
+
.a2a-tab:hover { color: var(--dsw-alias-label-primary, #0f1115); }
|
|
95
|
+
.a2a-tab:focus-visible { outline: none; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
96
|
+
.a2a-tab[data-active='true'] { color: var(--dsw-alias-label-primary, #0f1115); }
|
|
97
|
+
.a2a-tab[data-active='true']::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: 1px; background: var(--dsw-alias-label-primary, #0f1115); }
|
|
98
|
+
.a2a-tab-count { margin-left: 6px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
99
|
+
|
|
100
|
+
/* ---- inputs ---- */
|
|
101
|
+
.a2a-field-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
102
|
+
.a2a-field-row .a2a-input { flex: 1; min-width: 260px; }
|
|
103
|
+
.a2a-input {
|
|
104
|
+
padding: 5px 10px; font-size: 13px; line-height: 20px; border-radius: 8px; min-width: 0;
|
|
105
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3));
|
|
106
|
+
background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); color: var(--dsw-alias-label-primary, #0f1115);
|
|
107
|
+
transition: border-color .15s, box-shadow .15s, background .15s;
|
|
108
|
+
}
|
|
109
|
+
.a2a-input::placeholder { color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
110
|
+
.a2a-input:hover { border-color: var(--dsw-alias-border-l3, rgba(127,127,127,.45)); }
|
|
111
|
+
.a2a-input:focus-visible { outline: none; border-color: var(--dsw-alias-brand-primary, #4a7dff); box-shadow: 0 0 0 2px color-mix(in srgb, var(--dsw-alias-brand-primary, #4a7dff) 20%, transparent); }
|
|
112
|
+
|
|
113
|
+
/* ---- edit forms (serve identity + per-agent advanced) ---- */
|
|
114
|
+
.a2a-form-row { display: flex; align-items: center; gap: 10px; }
|
|
115
|
+
.a2a-form-row .a2a-input { flex: 1; min-width: 0; }
|
|
116
|
+
.a2a-form-label { flex: none; font-size: 12.5px; font-weight: 600; color: var(--dsw-alias-label-secondary, #61666b); min-width: 76px; }
|
|
117
|
+
.a2a-check-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--dsw-alias-label-primary, #0f1115); cursor: pointer; }
|
|
118
|
+
.a2a-check-row input { flex: none; }
|
|
119
|
+
.a2a-adv { display: flex; flex-direction: column; gap: 10px; margin-top: 2px; padding: 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); }
|
|
120
|
+
|
|
121
|
+
/* skills editor */
|
|
122
|
+
.a2a-skill-edit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
|
|
123
|
+
.a2a-skill-edit-list { display: flex; flex-direction: column; gap: 10px; }
|
|
124
|
+
.a2a-skill-edit-row { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border-radius: 8px; background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); }
|
|
125
|
+
.a2a-skill-edit-grid { display: flex; gap: 6px; }
|
|
126
|
+
.a2a-skill-edit-grid .a2a-input { flex: 1; min-width: 0; }
|
|
127
|
+
.a2a-skill-edit-foot { display: flex; align-items: center; gap: 6px; }
|
|
128
|
+
.a2a-skill-edit-foot .a2a-input { flex: 1; min-width: 0; }
|
|
129
|
+
|
|
130
|
+
/* ---- panels & cards ---- */
|
|
131
|
+
.a2a-panel {
|
|
132
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
133
|
+
background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.05));
|
|
134
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12));
|
|
135
|
+
border-radius: 12px; padding: 14px 16px;
|
|
136
|
+
}
|
|
137
|
+
.a2a-panel-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
138
|
+
|
|
139
|
+
.a2a-card {
|
|
140
|
+
display: flex; flex-direction: column; gap: 8px;
|
|
141
|
+
background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.05));
|
|
142
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12));
|
|
143
|
+
border-radius: 12px; padding: 12px 14px;
|
|
144
|
+
transition: border-color .15s, background .15s;
|
|
145
|
+
}
|
|
146
|
+
.a2a-card:hover { border-color: var(--dsw-alias-border-l2, rgba(127,127,127,.3)); }
|
|
147
|
+
.a2a-card-disabled { opacity: .72; }
|
|
148
|
+
.a2a-card-header { display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; flex-wrap: wrap; transition: gap .2s ease; }
|
|
149
|
+
.a2a-card-chevron { font-size: 11px; color: var(--dsw-alias-label-tertiary, #81858c); width: 14px; display: inline-block; transition: transform .15s; flex: none; }
|
|
150
|
+
.a2a-card-chevron[data-open='true'] { transform: rotate(90deg); }
|
|
151
|
+
.a2a-card-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
152
|
+
.a2a-card-sub { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
153
|
+
.a2a-card-url { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); word-break: break-all; }
|
|
154
|
+
.a2a-card-ops { display: flex; align-items: center; gap: 4px; flex-shrink: 0; margin-left: auto; padding-left: 10px; transition: padding-left .2s ease, margin-left .2s ease; }
|
|
155
|
+
|
|
156
|
+
.a2a-skills { display: flex; flex-direction: column; gap: 6px; background: var(--dsw-alias-bg-layer-1, rgba(0,0,0,.04)); border-radius: 8px; padding: 10px 12px; margin-top: 2px; }
|
|
157
|
+
.a2a-skill-row { display: flex; flex-direction: column; gap: 2px; }
|
|
158
|
+
.a2a-skill-name { display: flex; align-items: baseline; gap: 6px; font-size: 12.5px; font-weight: 600; font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"); color: var(--dsw-alias-label-primary, #0f1115); }
|
|
159
|
+
.a2a-skill-tags { font-size: 11px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
160
|
+
.a2a-skill-desc { font-size: 12px; color: var(--dsw-alias-label-secondary, #61666b); }
|
|
161
|
+
.a2a-skill-none { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
162
|
+
|
|
163
|
+
/* ---- badges ---- */
|
|
164
|
+
.a2a-badge { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 11.5px; line-height: 16px; }
|
|
165
|
+
.a2a-badge-config { color: var(--dsw-alias-label-secondary, #61666b); background: var(--dsw-alias-bg-layer-2, rgba(127,127,127,.12)); }
|
|
166
|
+
.a2a-badge-runtime { color: var(--dsw-alias-brand-primary, #4a7dff); background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4a7dff) 10%, transparent); }
|
|
167
|
+
.a2a-badge-disabled { color: var(--dsw-alias-state-warn-primary, #b26a00); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #b26a00) 10%, transparent); }
|
|
168
|
+
|
|
169
|
+
/* ---- add-agent preview ---- */
|
|
170
|
+
.a2a-preview { display: flex; flex-direction: column; gap: 8px; background: var(--dsw-alias-bg-layer-1, rgba(127,127,127,.06)); border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 8px; padding: 12px 14px; }
|
|
171
|
+
.a2a-preview-head { display: flex; align-items: center; gap: 8px; }
|
|
172
|
+
.a2a-preview-name { font-size: 15px; font-weight: 700; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
173
|
+
.a2a-preview-version { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
174
|
+
.a2a-preview-desc { font-size: 12.5px; color: var(--dsw-alias-label-secondary, #61666b); }
|
|
175
|
+
.a2a-preview-meta { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
176
|
+
|
|
177
|
+
/* ---- serve tab ---- */
|
|
178
|
+
.a2a-serve-head { display: flex; align-items: center; gap: 8px; }
|
|
179
|
+
.a2a-serve-status { font-size: 12.5px; }
|
|
180
|
+
.a2a-serve-status[data-on='true'] { color: var(--dsw-alias-state-success-primary, #12b76a); }
|
|
181
|
+
.a2a-serve-status[data-on='false'] { color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
182
|
+
.a2a-serve-ops { margin-left: auto; }
|
|
183
|
+
.a2a-serve-meta { display: flex; flex-direction: column; gap: 6px; }
|
|
184
|
+
.a2a-serve-name { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
185
|
+
.a2a-serve-version { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, #81858c); margin-left: 8px; }
|
|
186
|
+
.a2a-serve-desc { font-size: 12.5px; color: var(--dsw-alias-label-secondary, #61666b); }
|
|
187
|
+
.a2a-serve-endpoint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
188
|
+
.a2a-skill-list { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
189
|
+
.a2a-skill-list-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-secondary, #61666b); }
|
|
190
|
+
|
|
191
|
+
.a2a-note { font-size: 12px; color: var(--dsw-alias-label-secondary, #61666b); background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4a7dff) 8%, transparent); border-radius: 6px; padding: 6px 10px; }
|
|
192
|
+
.a2a-note-warn { color: var(--dsw-alias-state-warn-primary, #b26a00); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #b26a00) 8%, transparent); }
|
|
193
|
+
|
|
194
|
+
.a2a-auth-row { display: flex; align-items: center; gap: 8px; }
|
|
195
|
+
.a2a-auth { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
|
|
196
|
+
.a2a-auth-label { font-size: 12.5px; font-weight: 600; color: var(--dsw-alias-label-primary, #0f1115); }
|
|
197
|
+
.a2a-auth-state { font-size: 12px; }
|
|
198
|
+
.a2a-auth-state[data-on='true'] { color: var(--dsw-alias-state-success-primary, #12b76a); }
|
|
199
|
+
.a2a-auth-state[data-on='false'] { color: var(--dsw-alias-label-tertiary, #81858c); }
|
|
200
|
+
.a2a-auth-input-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
201
|
+
.a2a-auth-input-row .a2a-input { flex: 1; min-width: 220px; }
|
|
202
|
+
|
|
203
|
+
/* ---- inbound table ---- */
|
|
204
|
+
.a2a-table-wrap { overflow-x: auto; border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 10px; }
|
|
205
|
+
.a2a-table { border-collapse: collapse; width: 100%; font-size: 13px; }
|
|
206
|
+
.a2a-table th { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.18)); color: var(--dsw-alias-label-secondary, #61666b); font-weight: 600; white-space: nowrap; }
|
|
207
|
+
.a2a-table td { padding: 8px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.1)); color: var(--dsw-alias-label-primary, #0f1115); vertical-align: top; font-size: 12.5px; }
|
|
208
|
+
.a2a-table tr:last-child td { border-bottom: none; }
|
|
209
|
+
.a2a-table tbody tr:hover td { background: var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.06)); }
|
|
210
|
+
.a2a-cell-mono { font-family: var(--ds-font-family-code, "SF Mono", "JetBrains Mono", "Fira Code", Consolas, "Liberation Mono", Menlo, Courier, "PingFang SC", "Microsoft YaHei"); font-size: 12px; }
|
|
211
|
+
|
|
212
|
+
/* ---- footer ---- */
|
|
213
|
+
.a2a-footer { color: var(--dsw-alias-label-tertiary, #81858c); font-size: 12px; }
|
|
214
|
+
|
|
215
|
+
/* ── responsive: keyed off the panel's own width via @container ──────────
|
|
216
|
+
* The settings drawer width is independent of the viewport, so these use
|
|
217
|
+
* container queries against .a2a-root (container-name: a2a). Transitions on
|
|
218
|
+
* the affected properties keep the reflow smooth as the panel is resized.
|
|
219
|
+
*/
|
|
220
|
+
/* The settings drawer sits around ~560px, so this (max-width:760px) band is
|
|
221
|
+
* effectively the *default* layout here — keep it to genuinely helpful reflow
|
|
222
|
+
* (tighter gaps, card header/ops that would otherwise overflow). Rules that
|
|
223
|
+
* force inputs/values onto their own line belong in the much-narrower band
|
|
224
|
+
* below, or they needlessly break "input + button" / "value + copy" onto two
|
|
225
|
+
* lines at a width that comfortably fits them on one. */
|
|
226
|
+
@container a2a (max-width: 760px) {
|
|
227
|
+
.a2a-section { gap: 16px; }
|
|
228
|
+
/* Header stacks: title row on top, meta + ops flow below, left-aligned —
|
|
229
|
+
avoids the "orphan buttons pinned to the far right" look when wrapping. */
|
|
230
|
+
.a2a-card-header { flex-wrap: wrap; }
|
|
231
|
+
.a2a-card-sub { white-space: normal; }
|
|
232
|
+
.a2a-card-ops { margin-left: 0; width: 100%; justify-content: flex-start; padding-left: 0; }
|
|
233
|
+
.a2a-serve-ops { margin-left: auto; }
|
|
234
|
+
}
|
|
235
|
+
/* Only when the panel is genuinely narrow (< 440px) do inputs/values take a
|
|
236
|
+
* full line — at the normal ~560px width they stay inline with their button. */
|
|
237
|
+
@container a2a (max-width: 440px) {
|
|
238
|
+
.a2a-field-row .a2a-input { min-width: 100%; }
|
|
239
|
+
.a2a-auth-input-row .a2a-input { min-width: 100%; }
|
|
240
|
+
.a2a-meta-row { flex-wrap: wrap; }
|
|
241
|
+
.a2a-meta-row .a2a-mono { flex-basis: 100%; }
|
|
242
|
+
.a2a-meta-row .a2a-copy-btn { margin-left: auto; }
|
|
243
|
+
.a2a-form-row { flex-wrap: wrap; gap: 4px; }
|
|
244
|
+
.a2a-form-row .a2a-input { min-width: 100%; }
|
|
245
|
+
.a2a-skill-edit-grid { flex-direction: column; }
|
|
246
|
+
}
|
|
247
|
+
/* inbound table → stacked cards below 640px (instead of horizontal squeeze) */
|
|
248
|
+
@container a2a (max-width: 640px) {
|
|
249
|
+
.a2a-table-wrap { border: none; border-radius: 0; background: transparent; overflow-x: visible; }
|
|
250
|
+
.a2a-table, .a2a-table thead, .a2a-table tbody, .a2a-table tr, .a2a-table td { display: block; width: auto; }
|
|
251
|
+
.a2a-table thead { display: none; }
|
|
252
|
+
.a2a-table tbody { display: flex; flex-direction: column; gap: 8px; }
|
|
253
|
+
.a2a-table tr { border: 1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.12)); border-radius: 10px; padding: 8px 12px; margin-bottom: 0; }
|
|
254
|
+
.a2a-table tr:hover td { background: transparent; }
|
|
255
|
+
.a2a-table td { border: none; padding: 3px 0; display: flex; justify-content: space-between; align-items: center; gap: 12px; font-size: 12.5px; }
|
|
256
|
+
.a2a-table td::before { content: attr(data-label); flex: none; color: var(--dsw-alias-label-secondary, #61666b); font-weight: 600; font-size: 12px; }
|
|
257
|
+
.a2a-table td:last-child { justify-content: flex-end; }
|
|
258
|
+
}
|
|
259
|
+
@container a2a (max-width: 560px) {
|
|
260
|
+
.a2a-tabs { gap: 14px; }
|
|
261
|
+
.a2a-card-header { gap: 8px; }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Graceful fallback: if @container isn't supported, key off the viewport so
|
|
265
|
+
the narrow layout still appears in a genuinely small window. */
|
|
266
|
+
@supports not (container-type: inline-size) {
|
|
267
|
+
@media (max-width: 760px) {
|
|
268
|
+
.a2a-section { gap: 16px; }
|
|
269
|
+
.a2a-card-header { flex-wrap: wrap; }
|
|
270
|
+
.a2a-card-ops { margin-left: 0; width: 100%; justify-content: flex-start; padding-left: 0; }
|
|
271
|
+
.a2a-meta-row { flex-wrap: wrap; }
|
|
272
|
+
.a2a-meta-row .a2a-mono { flex-basis: 100%; }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
let injected = false;
|
|
277
|
+
/**
|
|
278
|
+
* Idempotently inject the dashboard stylesheet into <head>. Returns a
|
|
279
|
+
* disposer that removes the tag; safe to call repeatedly (a second call is a
|
|
280
|
+
* no-op and returns a no-op disposer).
|
|
281
|
+
*/
|
|
282
|
+
function injectDashboardStyles() {
|
|
283
|
+
if (typeof document === "undefined") return () => {};
|
|
284
|
+
if (injected) return () => {};
|
|
285
|
+
injected = true;
|
|
286
|
+
if (document.querySelector(`style[data-plugin-css=${JSON.stringify("@ryubyte/dsh-a2a/dashboard.css")}]`)) return () => {};
|
|
287
|
+
const tag = document.createElement("style");
|
|
288
|
+
tag.dataset.plugin = "@ryubyte/dsh-a2a";
|
|
289
|
+
tag.dataset.pluginCss = DASHBOARD_CSS_ID;
|
|
290
|
+
tag.textContent = css;
|
|
291
|
+
document.head.appendChild(tag);
|
|
292
|
+
return () => {
|
|
293
|
+
tag.remove();
|
|
294
|
+
injected = false;
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
8
298
|
//#region src/client/index.ts
|
|
9
299
|
/** Services this plugin needs from the client runtime. */
|
|
10
300
|
const inject = ["slots"];
|
|
11
301
|
function apply(ctx) {
|
|
302
|
+
ctx.effect(() => {
|
|
303
|
+
const dispose = injectDashboardStyles();
|
|
304
|
+
return () => dispose();
|
|
305
|
+
}, "dsh-a2a: dashboard styles");
|
|
12
306
|
ctx.effect(() => {
|
|
13
307
|
try {
|
|
14
308
|
const off = ctx.slots.register({
|
|
@@ -41,6 +335,44 @@ window.__ModuleLoader__.load({
|
|
|
41
335
|
})
|
|
42
336
|
})).json();
|
|
43
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Sensible starter values for the first-time guided setup, so a new user edits
|
|
340
|
+
* a ready-to-go config instead of filling every field from scratch — they can
|
|
341
|
+
* accept it as-is and click "创建并上线", or tweak what they want. baseUrl /
|
|
342
|
+
* endpointPath stay blank (auto-inferred). The seed skill is a placeholder the
|
|
343
|
+
* user renames or removes.
|
|
344
|
+
*/
|
|
345
|
+
function guidedServeDraft() {
|
|
346
|
+
return {
|
|
347
|
+
agentName: "DSH Agent",
|
|
348
|
+
agentDescription: "基于 DeepSeek Harness 的 A2A Agent",
|
|
349
|
+
agentVersion: "0.1.0",
|
|
350
|
+
baseUrl: "",
|
|
351
|
+
endpointPath: "",
|
|
352
|
+
skills: [{
|
|
353
|
+
id: "coding",
|
|
354
|
+
name: "Coding",
|
|
355
|
+
description: "在 DSH 工作区中执行编码与命令行任务",
|
|
356
|
+
tags: "coding, shell"
|
|
357
|
+
}]
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/** Build a serve draft from the live serve status (prefill the edit form). */
|
|
361
|
+
function serveDraftFrom(serve) {
|
|
362
|
+
return {
|
|
363
|
+
agentName: serve.agentName ?? "",
|
|
364
|
+
agentDescription: serve.agentDescription ?? "",
|
|
365
|
+
agentVersion: serve.agentVersion ?? "",
|
|
366
|
+
baseUrl: serve.baseUrl ?? "",
|
|
367
|
+
endpointPath: serve.endpointPath ?? "",
|
|
368
|
+
skills: (serve.skills ?? []).map((s) => ({
|
|
369
|
+
id: s.id,
|
|
370
|
+
name: s.name,
|
|
371
|
+
description: s.description ?? "",
|
|
372
|
+
tags: (s.tags ?? []).join(", ")
|
|
373
|
+
}))
|
|
374
|
+
};
|
|
375
|
+
}
|
|
44
376
|
/** The A2A connection dashboard settings section. */
|
|
45
377
|
function DashboardSection(_props) {
|
|
46
378
|
const [snap, setSnap] = (0, react.useState)(null);
|
|
@@ -51,13 +383,25 @@ window.__ModuleLoader__.load({
|
|
|
51
383
|
const [addUrl, setAddUrl] = (0, react.useState)("");
|
|
52
384
|
const [discovering, setDiscovering] = (0, react.useState)(false);
|
|
53
385
|
const [discovered, setDiscovered] = (0, react.useState)(null);
|
|
54
|
-
const [tab, setTab] = (0, react.useState)("
|
|
386
|
+
const [tab, setTab] = (0, react.useState)("client");
|
|
55
387
|
/** connection ids whose skill list is expanded in the outbound tab. */
|
|
56
388
|
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
57
389
|
/** Optional bearer token for the add-agent form (outbound). */
|
|
58
390
|
const [addToken, setAddToken] = (0, react.useState)("");
|
|
59
391
|
/** Inbound authToken input (serve tab); empty string means "clear". */
|
|
60
392
|
const [authInput, setAuthInput] = (0, react.useState)("");
|
|
393
|
+
/** Serve-identity edit mode + form draft (server tab). All hooks stay at the
|
|
394
|
+
* top level in fixed order; a single shared draft avoids per-card hooks. */
|
|
395
|
+
const [editingServe, setEditingServe] = (0, react.useState)(false);
|
|
396
|
+
const [serveDraft, setServeDraft] = (0, react.useState)(guidedServeDraft());
|
|
397
|
+
/** connectionId of the outbound agent whose advanced-settings form is open
|
|
398
|
+
* (null = none). One shared draft keeps agentCard() a pure, hookless fn. */
|
|
399
|
+
const [editingAgent, setEditingAgent] = (0, react.useState)(null);
|
|
400
|
+
const [agentDraft, setAgentDraft] = (0, react.useState)({
|
|
401
|
+
timeoutMs: "",
|
|
402
|
+
mapSkills: true,
|
|
403
|
+
bearerToken: ""
|
|
404
|
+
});
|
|
61
405
|
const refresh = async () => {
|
|
62
406
|
try {
|
|
63
407
|
setError(null);
|
|
@@ -133,206 +477,71 @@ window.__ModuleLoader__.load({
|
|
|
133
477
|
setDiscovered(null);
|
|
134
478
|
}
|
|
135
479
|
};
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
borderRadius: 6,
|
|
140
|
-
border: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3))",
|
|
141
|
-
background: "var(--dsw-alias-fill-l1, rgba(127,127,127,.06))",
|
|
142
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
480
|
+
const startEditServe = () => {
|
|
481
|
+
if (serve) setServeDraft(serveDraftFrom(serve));
|
|
482
|
+
setEditingServe(true);
|
|
143
483
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
} },
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
} }, (0, react.createElement)("div", {
|
|
188
|
-
|
|
189
|
-
fontWeight: 600,
|
|
190
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
191
|
-
} }, "添加 Agent"), (0, react.createElement)("div", { style: {
|
|
192
|
-
display: "flex",
|
|
193
|
-
alignItems: "center",
|
|
194
|
-
gap: 8,
|
|
195
|
-
flexWrap: "wrap"
|
|
196
|
-
} }, (0, react.createElement)("input", {
|
|
484
|
+
const saveServeIdentity = async (also) => {
|
|
485
|
+
const skills = serveDraft.skills.map((s) => ({
|
|
486
|
+
id: s.id.trim(),
|
|
487
|
+
name: s.name.trim(),
|
|
488
|
+
description: s.description.trim(),
|
|
489
|
+
tags: s.tags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
490
|
+
})).filter((s) => s.id && s.name);
|
|
491
|
+
await act("set-server-identity", "", {
|
|
492
|
+
agentName: serveDraft.agentName.trim(),
|
|
493
|
+
agentDescription: serveDraft.agentDescription.trim(),
|
|
494
|
+
agentVersion: serveDraft.agentVersion.trim(),
|
|
495
|
+
baseUrl: serveDraft.baseUrl.trim(),
|
|
496
|
+
endpointPath: serveDraft.endpointPath.trim() || void 0,
|
|
497
|
+
skills
|
|
498
|
+
});
|
|
499
|
+
if (also === "enable") await act("server-enable", "");
|
|
500
|
+
setEditingServe(false);
|
|
501
|
+
};
|
|
502
|
+
const startEditAgent = (a) => {
|
|
503
|
+
const target = a.connectionId ?? a.id;
|
|
504
|
+
setAgentDraft({
|
|
505
|
+
timeoutMs: "",
|
|
506
|
+
mapSkills: true,
|
|
507
|
+
bearerToken: ""
|
|
508
|
+
});
|
|
509
|
+
setEditingAgent(target);
|
|
510
|
+
};
|
|
511
|
+
const saveAgentAdvanced = async (target) => {
|
|
512
|
+
const t = agentDraft.timeoutMs.trim();
|
|
513
|
+
await act("update-agent", target, {
|
|
514
|
+
timeoutMs: t ? Number(t) : void 0,
|
|
515
|
+
mapSkills: agentDraft.mapSkills,
|
|
516
|
+
bearerToken: agentDraft.bearerToken.trim() || void 0
|
|
517
|
+
});
|
|
518
|
+
setEditingAgent(null);
|
|
519
|
+
};
|
|
520
|
+
return (0, react.createElement)("div", { className: "a2a-root" }, (0, react.createElement)("div", { className: "a2a-section" }, (0, react.createElement)("p", { className: "a2a-intro" }, "管理本 DSH 的 A2A 连接:在「连接的 Agent」里连接并调用远程 Agent,在「对外服务」里把本 DSH 作为 Agent 发布、并查看谁在调用它。"), error ? (0, react.createElement)("div", { className: "a2a-notice a2a-error" }, `加载失败:${error}`) : null, notice ? (0, react.createElement)("div", { className: "a2a-notice" }, notice) : null, tabRail([tabBtn("连接的 Agent", outbound.length, tab === "client", () => setTab("client")), tabBtn("对外服务", inbound.length, tab === "server", () => setTab("server"))]), tab === "client" ? (0, react.createElement)("div", { className: "a2a-stack" }, (0, react.createElement)("div", { className: "a2a-crew" }, sec("已连接 Agent", outbound.length), outbound.length === 0 ? emptyRow("暂无已连接的 Agent。使用下方\"添加 Agent\"通过 Agent Card URL 连接远程 A2A Agent。") : (0, react.createElement)("div", { className: "a2a-card-list" }, ...outbound.map((a) => agentCard(a, busy, act, expanded, setExpanded, {
|
|
521
|
+
editingAgent,
|
|
522
|
+
agentDraft,
|
|
523
|
+
setAgentDraft,
|
|
524
|
+
startEditAgent,
|
|
525
|
+
saveAgentAdvanced: (t) => void saveAgentAdvanced(t),
|
|
526
|
+
cancelEditAgent: () => setEditingAgent(null)
|
|
527
|
+
})))), (0, react.createElement)("div", { className: "a2a-panel" }, (0, react.createElement)("div", { className: "a2a-panel-title" }, "添加 Agent"), (0, react.createElement)("div", { className: "a2a-field-row" }, (0, react.createElement)("input", {
|
|
528
|
+
className: "a2a-input",
|
|
197
529
|
placeholder: "Agent Card URL(https://…/.well-known/agent-card.json)",
|
|
198
530
|
value: addUrl,
|
|
199
531
|
onChange: (e) => {
|
|
200
532
|
setAddUrl(e.target.value);
|
|
201
533
|
setDiscovered(null);
|
|
202
|
-
},
|
|
203
|
-
style: {
|
|
204
|
-
...inputStyle,
|
|
205
|
-
flex: 1,
|
|
206
|
-
minWidth: 260
|
|
207
534
|
}
|
|
208
|
-
}), actionBtn("导入", false, () => void doDiscover(), discovering || !addUrl.trim())), (0, react.createElement)("div", {
|
|
209
|
-
|
|
210
|
-
alignItems: "center",
|
|
211
|
-
gap: 8,
|
|
212
|
-
flexWrap: "wrap"
|
|
213
|
-
} }, (0, react.createElement)("input", {
|
|
535
|
+
}), actionBtn("导入", false, () => void doDiscover(), discovering || !addUrl.trim())), (0, react.createElement)("div", { className: "a2a-field-row" }, (0, react.createElement)("input", {
|
|
536
|
+
className: "a2a-input",
|
|
214
537
|
type: "password",
|
|
215
538
|
placeholder: "Bearer Token(可选,发送为 Authorization: Bearer …)",
|
|
216
539
|
value: addToken,
|
|
217
|
-
onChange: (e) => setAddToken(e.target.value)
|
|
218
|
-
|
|
219
|
-
...inputStyle,
|
|
220
|
-
flex: 1,
|
|
221
|
-
minWidth: 260
|
|
222
|
-
}
|
|
223
|
-
})), (0, react.createElement)("div", { style: {
|
|
224
|
-
fontSize: 11.5,
|
|
225
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
226
|
-
} }, "若远程 Agent 需要鉴权,填入 token;导入与后续调用都会带上。token 会明文存入 a2a.json。"), discovering ? (0, react.createElement)("div", { style: {
|
|
227
|
-
fontSize: 13,
|
|
228
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
229
|
-
} }, "正在读取 Agent Card…") : discovered ? (0, react.createElement)("div", { style: {
|
|
230
|
-
display: "flex",
|
|
231
|
-
flexDirection: "column",
|
|
232
|
-
gap: 8,
|
|
233
|
-
background: "var(--dsw-alias-fill-l1, rgba(127,127,127,.06))",
|
|
234
|
-
borderRadius: 8,
|
|
235
|
-
padding: "12px 14px"
|
|
236
|
-
} }, (0, react.createElement)("div", { style: {
|
|
237
|
-
display: "flex",
|
|
238
|
-
alignItems: "center",
|
|
239
|
-
gap: 8
|
|
240
|
-
} }, (0, react.createElement)("span", { style: {
|
|
241
|
-
fontSize: 15,
|
|
242
|
-
fontWeight: 700,
|
|
243
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
244
|
-
} }, discovered.name), discovered.version ? (0, react.createElement)("span", { style: {
|
|
245
|
-
fontSize: 11.5,
|
|
246
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
247
|
-
fontFamily: "monospace"
|
|
248
|
-
} }, `v${discovered.version}`) : null, stateDot("connected")), discovered.description ? (0, react.createElement)("div", { style: {
|
|
249
|
-
fontSize: 12.5,
|
|
250
|
-
color: "var(--dsw-alias-label-secondary, #666)"
|
|
251
|
-
} }, discovered.description) : null, (discovered.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { style: {
|
|
252
|
-
display: "flex",
|
|
253
|
-
flexDirection: "column",
|
|
254
|
-
gap: 4
|
|
255
|
-
} }, (0, react.createElement)("div", { style: {
|
|
256
|
-
fontSize: 12,
|
|
257
|
-
fontWeight: 600,
|
|
258
|
-
color: "var(--dsw-alias-label-secondary, #666)"
|
|
259
|
-
} }, "Skills"), ...(discovered.skills ?? []).map((s) => (0, react.createElement)("div", {
|
|
260
|
-
key: s.id,
|
|
261
|
-
style: {
|
|
262
|
-
fontSize: 12.5,
|
|
263
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
264
|
-
}
|
|
265
|
-
}, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null, discovered.endpoint ? (0, react.createElement)("div", { style: {
|
|
266
|
-
fontSize: 11.5,
|
|
267
|
-
fontFamily: "monospace",
|
|
268
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
269
|
-
} }, `端点 ${discovered.endpoint}`) : null, (0, react.createElement)("div", { style: {
|
|
270
|
-
display: "flex",
|
|
271
|
-
gap: 8,
|
|
272
|
-
marginTop: 2
|
|
273
|
-
} }, actionBtn("连接", true, () => void doConnect(), busy?.startsWith("add-agent:") ?? false), actionBtn("取消", false, () => {
|
|
540
|
+
onChange: (e) => setAddToken(e.target.value)
|
|
541
|
+
})), (0, react.createElement)("div", { className: "a2a-hint" }, "若远程 Agent 需要鉴权,填入 token;导入与后续调用都会带上。token 会明文存入 a2a.json。"), discovering ? (0, react.createElement)("div", { className: "a2a-hint" }, "正在读取 Agent Card…") : discovered ? (0, react.createElement)("div", { className: "a2a-preview" }, (0, react.createElement)("div", { className: "a2a-preview-head" }, (0, react.createElement)("span", { className: "a2a-preview-name" }, discovered.name), discovered.version ? (0, react.createElement)("span", { className: "a2a-preview-version a2a-mono" }, `v${discovered.version}`) : null, stateDot("connected")), discovered.description ? (0, react.createElement)("div", { className: "a2a-preview-desc" }, discovered.description) : null, (discovered.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { className: "a2a-skill-list" }, (0, react.createElement)("div", { className: "a2a-skill-list-title" }, "Skills"), ...(discovered.skills ?? []).map((s) => (0, react.createElement)("div", { key: s.id }, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null, discovered.endpoint ? (0, react.createElement)("div", { className: "a2a-preview-meta a2a-mono" }, `端点 ${discovered.endpoint}`) : null, (0, react.createElement)("div", { className: "a2a-btn-row" }, actionBtn("连接", true, () => void doConnect(), busy?.startsWith("add-agent:") ?? false), actionBtn("取消", false, () => {
|
|
274
542
|
setDiscovered(null);
|
|
275
543
|
setAddUrl("");
|
|
276
|
-
}, false))) : (0, react.createElement)("div", {
|
|
277
|
-
fontSize: 12,
|
|
278
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
279
|
-
} }, "输入远程 Agent 的 Agent Card URL 并点击\"导入\",将自动读取其名称、描述、技能与能力,确认后建立连接。"))) : null, tab === "serve" && serve ? (0, react.createElement)("div", { style: {
|
|
280
|
-
display: "flex",
|
|
281
|
-
flexDirection: "column",
|
|
282
|
-
gap: 10,
|
|
283
|
-
background: "var(--dsw-alias-fill-l2, rgba(127,127,127,.05))",
|
|
284
|
-
borderRadius: 10,
|
|
285
|
-
padding: "14px 16px"
|
|
286
|
-
} }, (0, react.createElement)("div", { style: {
|
|
287
|
-
display: "flex",
|
|
288
|
-
alignItems: "center",
|
|
289
|
-
gap: 8
|
|
290
|
-
} }, sec("A2A 服务(本 DSH 作为 Agent 对外)", 0), stateDot(serve.enabled ? "connected" : "disconnected"), (0, react.createElement)("span", { style: {
|
|
291
|
-
fontSize: 12.5,
|
|
292
|
-
color: serve.enabled ? "#2e7d32" : "#9e9e9e"
|
|
293
|
-
} }, serve.enabled ? "运行中" : "已下线"), (0, react.createElement)("span", { style: { marginLeft: "auto" } }, serve.enabled ? actionBtn("下线", false, () => void act("server-disable", ""), busy === "server-disable:") : actionBtn("上线", true, () => void act("server-enable", ""), busy === "server-enable:"))), (0, react.createElement)("div", { style: {
|
|
294
|
-
display: "flex",
|
|
295
|
-
flexDirection: "column",
|
|
296
|
-
gap: 6
|
|
297
|
-
} }, serve.agentName ? (0, react.createElement)("div", { style: {
|
|
298
|
-
fontSize: 13,
|
|
299
|
-
fontWeight: 600,
|
|
300
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
301
|
-
} }, serve.agentName, serve.agentVersion ? (0, react.createElement)("span", { style: {
|
|
302
|
-
fontSize: 11.5,
|
|
303
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
304
|
-
fontFamily: "monospace",
|
|
305
|
-
marginLeft: 8
|
|
306
|
-
} }, `v${serve.agentVersion}`) : null) : null, serve.agentDescription ? (0, react.createElement)("div", { style: {
|
|
307
|
-
fontSize: 12.5,
|
|
308
|
-
color: "var(--dsw-alias-label-secondary, #666)"
|
|
309
|
-
} }, serve.agentDescription) : null, serve.endpoint ? (0, react.createElement)("div", { style: {
|
|
310
|
-
fontSize: 12,
|
|
311
|
-
fontFamily: "monospace",
|
|
312
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
313
|
-
} }, `端点 ${serve.endpoint}`) : null, serve.agentCardUrl ? (0, react.createElement)("div", { style: {
|
|
314
|
-
fontSize: 12,
|
|
315
|
-
fontFamily: "monospace",
|
|
316
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
317
|
-
} }, `Agent Card ${serve.agentCardUrl}`) : null, (serve.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { style: {
|
|
318
|
-
display: "flex",
|
|
319
|
-
flexDirection: "column",
|
|
320
|
-
gap: 4
|
|
321
|
-
} }, (0, react.createElement)("div", { style: {
|
|
322
|
-
fontSize: 12,
|
|
323
|
-
fontWeight: 600,
|
|
324
|
-
color: "var(--dsw-alias-label-secondary, #666)"
|
|
325
|
-
} }, "Skills"), ...(serve.skills ?? []).map((s) => (0, react.createElement)("div", {
|
|
326
|
-
key: s.id,
|
|
327
|
-
style: {
|
|
328
|
-
fontSize: 12.5,
|
|
329
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
330
|
-
}
|
|
331
|
-
}, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null), serve.enabled ? executorNote(serve) : null, serve.enabled ? authControl(serve, busy, act, authInput, setAuthInput) : null) : null, tab === "in" ? (0, react.createElement)("div", { style: {
|
|
332
|
-
display: "flex",
|
|
333
|
-
flexDirection: "column",
|
|
334
|
-
gap: 10
|
|
335
|
-
} }, sec("入站连接(谁在连接本 DSH)", inbound.length), inbound.length === 0 ? emptyRow("暂无入站连接。其他 A2A 客户端调用本 DSH 的 JSON-RPC 端点后会显示在这里。") : (0, react.createElement)("table", tblStyle, (0, react.createElement)("thead", null, headRow([
|
|
544
|
+
}, false))) : (0, react.createElement)("div", { className: "a2a-hint" }, "输入远程 Agent 的 Agent Card URL 并点击\"导入\",将自动读取其名称、描述、技能与能力,确认后建立连接。"))) : null, tab === "server" ? (0, react.createElement)("div", { className: "a2a-stack" }, serve ? editingServe ? serveEditPanel(serveDraft, setServeDraft, (also) => void saveServeIdentity(also), () => setEditingServe(false), busy, false) : needsServeSetup(serve) ? serveEditPanel(serveDraft, setServeDraft, (also) => void saveServeIdentity(also), () => setEditingServe(false), busy, true) : serveViewPanel(serve, busy, act, authInput, setAuthInput, startEditServe) : null, (0, react.createElement)("div", { className: "a2a-crew" }, sec("入站连接(谁在连接本 DSH)", inbound.length), inbound.length === 0 ? emptyRow("暂无入站连接。其他 A2A 客户端调用本 DSH 的 JSON-RPC 端点后会显示在这里。") : (0, react.createElement)("div", { className: "a2a-table-wrap" }, (0, react.createElement)("table", { className: "a2a-table" }, (0, react.createElement)("thead", null, headRow([
|
|
336
545
|
"来源",
|
|
337
546
|
"地址",
|
|
338
547
|
"首次连接",
|
|
@@ -340,15 +549,13 @@ window.__ModuleLoader__.load({
|
|
|
340
549
|
"任务",
|
|
341
550
|
"流",
|
|
342
551
|
"操作"
|
|
343
|
-
])), (0, react.createElement)("tbody", null, ...inbound.map((p) => inboundRow(p, busy, act))))) : null, (0, react.createElement)("div", {
|
|
344
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
345
|
-
fontSize: 12
|
|
346
|
-
} }, `快照时间 ${snap ? new Date(snap.at).toLocaleTimeString() : "—"} · 每 3 秒自动刷新`));
|
|
552
|
+
])), (0, react.createElement)("tbody", null, ...inbound.map((p) => inboundRow(p, busy, act))))))) : null, (0, react.createElement)("div", { className: "a2a-footer" }, `快照时间 ${snap ? new Date(snap.at).toLocaleTimeString() : "—"} · 每 3 秒自动刷新`)));
|
|
347
553
|
}
|
|
348
|
-
function agentCard(a, busy, act, expanded, setExpanded) {
|
|
554
|
+
function agentCard(a, busy, act, expanded, setExpanded, edit) {
|
|
349
555
|
const target = a.connectionId ?? a.id;
|
|
350
556
|
const open = expanded.has(target);
|
|
351
557
|
const skills = a.skills ?? [];
|
|
558
|
+
const editingThis = edit.editingAgent === target;
|
|
352
559
|
const toggle = () => {
|
|
353
560
|
const next = new Set(expanded);
|
|
354
561
|
if (next.has(target)) next.delete(target);
|
|
@@ -357,103 +564,58 @@ window.__ModuleLoader__.load({
|
|
|
357
564
|
};
|
|
358
565
|
return (0, react.createElement)("div", {
|
|
359
566
|
key: a.id,
|
|
360
|
-
|
|
361
|
-
display: "flex",
|
|
362
|
-
flexDirection: "column",
|
|
363
|
-
gap: 8,
|
|
364
|
-
background: "var(--dsw-alias-fill-l1, rgba(127,127,127,.05))",
|
|
365
|
-
borderRadius: 10,
|
|
366
|
-
padding: "12px 14px",
|
|
367
|
-
opacity: a.enabled === false ? .72 : 1
|
|
368
|
-
}
|
|
567
|
+
className: a.enabled === false ? "a2a-card a2a-card-disabled" : "a2a-card"
|
|
369
568
|
}, (0, react.createElement)("div", {
|
|
370
|
-
|
|
371
|
-
display: "flex",
|
|
372
|
-
alignItems: "center",
|
|
373
|
-
gap: 10,
|
|
374
|
-
cursor: "pointer",
|
|
375
|
-
userSelect: "none",
|
|
376
|
-
flexWrap: "wrap"
|
|
377
|
-
},
|
|
569
|
+
className: "a2a-card-header",
|
|
378
570
|
onClick: toggle
|
|
379
|
-
}, (0, react.createElement)("span", {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
} }, "▶"), stateDot(a.enabled === false ? "disabled" : a.state), (0, react.createElement)("span", { style: {
|
|
387
|
-
fontSize: 14,
|
|
388
|
-
fontWeight: 600,
|
|
389
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
390
|
-
} }, a.agentName ?? a.name), a.enabled === false ? (0, react.createElement)("span", { style: {
|
|
391
|
-
fontSize: 11.5,
|
|
392
|
-
...sourceBadge("#b26a00", "rgba(178,106,0,.1)", "已禁用")
|
|
393
|
-
} }, "已禁用") : (0, react.createElement)("span", { style: {
|
|
394
|
-
fontSize: 11.5,
|
|
395
|
-
...a.configured ? sourceBadge("#666", "rgba(127,127,127,.12)", "配置") : sourceBadge("#4a7dff", "rgba(74,125,255,.1)", "运行时")
|
|
396
|
-
} }, a.configured ? "配置" : "运行时"), (0, react.createElement)("span", { style: {
|
|
397
|
-
fontSize: 12,
|
|
398
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
399
|
-
whiteSpace: "nowrap",
|
|
400
|
-
overflow: "hidden",
|
|
401
|
-
textOverflow: "ellipsis",
|
|
402
|
-
minWidth: 0
|
|
403
|
-
} }, `${a.skillCount} 技能 · ${a.toolCount} 工具 · ${new Date(a.lastSeen).toLocaleTimeString()}`), (0, react.createElement)("div", { style: {
|
|
404
|
-
display: "flex",
|
|
405
|
-
alignItems: "center",
|
|
406
|
-
gap: 4,
|
|
407
|
-
flexShrink: 0,
|
|
408
|
-
marginLeft: "auto",
|
|
409
|
-
paddingLeft: 10
|
|
410
|
-
} }, a.enabled === false ? actionBtn("启用", true, () => void act("enable-agent", target), busy === `enable-agent:${target}`) : actionBtn("重连", true, () => void act("reconnect-agent", target), busy === `reconnect-agent:${target}`), actionBtn("禁用", false, () => void act("disable-agent", target), busy === `disable-agent:${target}`), actionBtn("删除", false, () => void act("remove-agent", target), busy === `remove-agent:${target}`))), (0, react.createElement)("div", { style: {
|
|
411
|
-
fontSize: 12,
|
|
412
|
-
fontFamily: "monospace",
|
|
413
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
414
|
-
wordBreak: "break-all"
|
|
415
|
-
} }, a.agentCardUrl), open && skills.length > 0 ? (0, react.createElement)("div", { style: {
|
|
416
|
-
display: "flex",
|
|
417
|
-
flexDirection: "column",
|
|
418
|
-
gap: 6,
|
|
419
|
-
background: "var(--dsw-alias-fill-l2, rgba(0,0,0,.04))",
|
|
420
|
-
borderRadius: 8,
|
|
421
|
-
padding: "10px 12px",
|
|
422
|
-
marginTop: 2
|
|
423
|
-
} }, ...skills.map((s) => (0, react.createElement)("div", {
|
|
571
|
+
}, (0, react.createElement)("span", {
|
|
572
|
+
className: "a2a-card-chevron",
|
|
573
|
+
"data-open": String(open)
|
|
574
|
+
}, "▶"), stateDot(a.enabled === false ? "disabled" : a.state), (0, react.createElement)("span", { className: "a2a-card-title" }, a.agentName ?? a.name), a.enabled === false ? badge("disabled", "已禁用") : badge(a.configured ? "config" : "runtime", a.configured ? "配置" : "运行时"), (0, react.createElement)("span", { className: "a2a-card-sub" }, `${a.skillCount} 技能 · ${a.toolCount} 工具 · ${new Date(a.lastSeen).toLocaleTimeString()}`), (0, react.createElement)("div", { className: "a2a-card-ops" }, a.enabled === false ? actionBtn("启用", true, () => void act("enable-agent", target), busy === `enable-agent:${target}`) : actionBtn("重连", true, () => void act("reconnect-agent", target), busy === `reconnect-agent:${target}`), a.configured ? actionBtn("设置", false, () => editingThis ? edit.cancelEditAgent() : edit.startEditAgent(a), false) : null, actionBtn("禁用", false, () => void act("disable-agent", target), busy === `disable-agent:${target}`), actionBtn("删除", false, () => void act("remove-agent", target), busy === `remove-agent:${target}`))), (0, react.createElement)("div", { className: "a2a-meta-row" }, (0, react.createElement)("span", { className: "a2a-card-url a2a-mono" }, a.agentCardUrl), (0, react.createElement)(CopyButton, {
|
|
575
|
+
text: a.agentCardUrl,
|
|
576
|
+
label: "Agent Card URL"
|
|
577
|
+
})), open && skills.length > 0 ? (0, react.createElement)("div", { className: "a2a-skills" }, ...skills.map((s) => (0, react.createElement)("div", {
|
|
424
578
|
key: s.id,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
579
|
+
className: "a2a-skill-row"
|
|
580
|
+
}, (0, react.createElement)("div", { className: "a2a-skill-name" }, s.name, s.tags?.length ? (0, react.createElement)("span", { className: "a2a-skill-tags" }, s.tags.join(", ")) : null), s.description ? (0, react.createElement)("div", { className: "a2a-skill-desc" }, s.description) : null))) : open ? (0, react.createElement)("div", { className: "a2a-skill-none" }, "(该 Agent 未声明技能)") : null, editingThis ? agentAdvancedForm(target, edit, busy) : null);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Per-agent advanced-settings form: timeoutMs / mapSkills / bearerToken. Saving
|
|
584
|
+
* persists to a2a.json and reconnects the agent to apply. Pure — draft lives in
|
|
585
|
+
* the parent section.
|
|
586
|
+
*/
|
|
587
|
+
function agentAdvancedForm(target, edit, busy) {
|
|
588
|
+
const d = edit.agentDraft;
|
|
589
|
+
const saving = busy === `update-agent:${target}`;
|
|
590
|
+
return (0, react.createElement)("div", { className: "a2a-adv" }, (0, react.createElement)("div", { className: "a2a-form-label" }, "高级设置"), (0, react.createElement)("div", { className: "a2a-form-row" }, (0, react.createElement)("label", { className: "a2a-form-label" }, "超时 (ms)"), (0, react.createElement)("input", {
|
|
591
|
+
className: "a2a-input",
|
|
592
|
+
type: "number",
|
|
593
|
+
placeholder: "留空用默认",
|
|
594
|
+
value: d.timeoutMs,
|
|
595
|
+
onChange: (e) => edit.setAgentDraft({
|
|
596
|
+
...d,
|
|
597
|
+
timeoutMs: e.target.value
|
|
598
|
+
})
|
|
599
|
+
})), (0, react.createElement)("label", { className: "a2a-check-row" }, (0, react.createElement)("input", {
|
|
600
|
+
type: "checkbox",
|
|
601
|
+
checked: d.mapSkills,
|
|
602
|
+
onChange: (e) => edit.setAgentDraft({
|
|
603
|
+
...d,
|
|
604
|
+
mapSkills: e.target.checked
|
|
605
|
+
})
|
|
606
|
+
}), (0, react.createElement)("span", null, "按技能拆分为多个工具(关闭则合并为单个工具)")), (0, react.createElement)("div", { className: "a2a-form-row" }, (0, react.createElement)("label", { className: "a2a-form-label" }, "Token"), (0, react.createElement)("input", {
|
|
607
|
+
className: "a2a-input",
|
|
608
|
+
type: "password",
|
|
609
|
+
placeholder: "留空不修改",
|
|
610
|
+
value: d.bearerToken,
|
|
611
|
+
onChange: (e) => edit.setAgentDraft({
|
|
612
|
+
...d,
|
|
613
|
+
bearerToken: e.target.value
|
|
614
|
+
})
|
|
615
|
+
})), (0, react.createElement)("div", { className: "a2a-hint" }, "保存后会以新配置重连该 Agent,使超时/工具拆分/Token 生效。"), (0, react.createElement)("div", { className: "a2a-btn-row" }, actionBtn("保存并重连", true, () => edit.saveAgentAdvanced(target), saving), actionBtn("取消", false, edit.cancelEditAgent, false)));
|
|
616
|
+
}
|
|
617
|
+
function badge(kind, label) {
|
|
618
|
+
return (0, react.createElement)("span", { className: `a2a-badge a2a-badge-${kind}` }, label);
|
|
457
619
|
}
|
|
458
620
|
/** A tab button matching the settings "插件" page's underline tab style. */
|
|
459
621
|
function tabBtn(label, count, active, onClick) {
|
|
@@ -461,124 +623,79 @@ window.__ModuleLoader__.load({
|
|
|
461
623
|
onClick,
|
|
462
624
|
"aria-selected": active,
|
|
463
625
|
role: "tab",
|
|
464
|
-
"data-active": active,
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
fontSize: 13,
|
|
468
|
-
lineHeight: "20px",
|
|
469
|
-
fontWeight: 400,
|
|
470
|
-
color: active ? "var(--dsw-alias-label-primary, #0f1115)" : "var(--dsw-alias-label-tertiary, #81858c)",
|
|
471
|
-
background: "transparent",
|
|
472
|
-
border: "none",
|
|
473
|
-
cursor: "pointer",
|
|
474
|
-
position: "relative",
|
|
475
|
-
boxShadow: active ? "inset 0 -2px 0 var(--dsw-alias-label-primary, #0f1115)" : "none",
|
|
476
|
-
borderRadius: "2px 2px 0 0",
|
|
477
|
-
transition: "color .15s, box-shadow .15s"
|
|
478
|
-
}
|
|
479
|
-
}, label, count > 0 ? (0, react.createElement)("span", { style: {
|
|
480
|
-
fontSize: 12,
|
|
481
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
482
|
-
marginLeft: 6
|
|
483
|
-
} }, String(count)) : null);
|
|
626
|
+
"data-active": String(active),
|
|
627
|
+
className: "a2a-tab"
|
|
628
|
+
}, label, count > 0 ? (0, react.createElement)("span", { className: "a2a-tab-count" }, String(count)) : null);
|
|
484
629
|
}
|
|
485
|
-
/** The tab strip container
|
|
630
|
+
/** The tab strip container (underline rail, like first-party settings pages). */
|
|
486
631
|
function tabRail(children) {
|
|
487
632
|
return (0, react.createElement)("div", {
|
|
488
633
|
role: "tablist",
|
|
489
|
-
|
|
490
|
-
display: "flex",
|
|
491
|
-
alignItems: "flex-end",
|
|
492
|
-
gap: 22,
|
|
493
|
-
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.1))",
|
|
494
|
-
marginTop: 2
|
|
495
|
-
}
|
|
634
|
+
className: "a2a-tabs"
|
|
496
635
|
}, ...children);
|
|
497
636
|
}
|
|
498
637
|
function sec(title, count) {
|
|
499
|
-
return (0, react.createElement)("h3", {
|
|
500
|
-
margin: 0,
|
|
501
|
-
fontSize: 14,
|
|
502
|
-
fontWeight: 600,
|
|
503
|
-
color: "var(--dsw-alias-label-primary, #222)",
|
|
504
|
-
display: "flex",
|
|
505
|
-
alignItems: "center",
|
|
506
|
-
gap: 8
|
|
507
|
-
} }, title, (0, react.createElement)("span", { style: {
|
|
508
|
-
fontSize: 12,
|
|
509
|
-
color: "var(--dsw-alias-label-tertiary, #999)",
|
|
510
|
-
background: "var(--dsw-alias-fill-l2, rgba(127,127,127,.1))",
|
|
511
|
-
borderRadius: 10,
|
|
512
|
-
padding: "1px 8px"
|
|
513
|
-
} }, String(count)));
|
|
638
|
+
return (0, react.createElement)("h3", { className: "a2a-h3" }, title, (0, react.createElement)("span", { className: "a2a-count" }, String(count)));
|
|
514
639
|
}
|
|
515
640
|
function emptyRow(text) {
|
|
516
|
-
return (0, react.createElement)("div", {
|
|
517
|
-
|
|
518
|
-
fontSize: 13,
|
|
519
|
-
background: "var(--dsw-alias-fill-l2, rgba(127,127,127,.06))",
|
|
520
|
-
borderRadius: 8,
|
|
521
|
-
padding: "12px 14px"
|
|
522
|
-
} }, text);
|
|
523
|
-
}
|
|
524
|
-
const tblStyle = {
|
|
525
|
-
borderCollapse: "collapse",
|
|
526
|
-
width: "100%",
|
|
527
|
-
fontSize: 13
|
|
528
|
-
};
|
|
529
|
-
const thStyle = {
|
|
530
|
-
textAlign: "left",
|
|
531
|
-
padding: "6px 8px",
|
|
532
|
-
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.18))",
|
|
533
|
-
color: "var(--dsw-alias-label-secondary, #666)",
|
|
534
|
-
fontWeight: 600,
|
|
535
|
-
whiteSpace: "nowrap"
|
|
536
|
-
};
|
|
537
|
-
const tdStyle = {
|
|
538
|
-
padding: "6px 8px",
|
|
539
|
-
borderBottom: "1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.1))",
|
|
540
|
-
color: "var(--dsw-alias-label-primary, #222)",
|
|
541
|
-
verticalAlign: "top",
|
|
542
|
-
fontSize: 12.5
|
|
543
|
-
};
|
|
641
|
+
return (0, react.createElement)("div", { className: "a2a-empty" }, text);
|
|
642
|
+
}
|
|
544
643
|
function headRow(cols) {
|
|
545
|
-
return (0, react.createElement)("tr", null, ...cols.map((c) => (0, react.createElement)("th",
|
|
546
|
-
}
|
|
547
|
-
const stateColor = {
|
|
548
|
-
connected: "#2e7d32",
|
|
549
|
-
disconnected: "#9e9e9e",
|
|
550
|
-
reconnecting: "#f57c00",
|
|
551
|
-
disabled: "#b26a00"
|
|
552
|
-
};
|
|
644
|
+
return (0, react.createElement)("tr", null, ...cols.map((c) => (0, react.createElement)("th", null, c)));
|
|
645
|
+
}
|
|
553
646
|
function stateDot(state) {
|
|
554
|
-
return (0, react.createElement)("span", {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
borderRadius: 4,
|
|
559
|
-
marginRight: 6,
|
|
560
|
-
background: stateColor[state] ?? "#9e9e9e"
|
|
561
|
-
} });
|
|
647
|
+
return (0, react.createElement)("span", {
|
|
648
|
+
className: "a2a-dot",
|
|
649
|
+
"data-state": state
|
|
650
|
+
});
|
|
562
651
|
}
|
|
563
652
|
function actionBtn(label, primary, onClick, disabled) {
|
|
564
653
|
return (0, react.createElement)("button", {
|
|
565
654
|
onClick,
|
|
566
655
|
disabled,
|
|
567
|
-
|
|
568
|
-
marginRight: 6,
|
|
569
|
-
padding: "3px 10px",
|
|
570
|
-
fontSize: 12,
|
|
571
|
-
lineHeight: "18px",
|
|
572
|
-
borderRadius: 6,
|
|
573
|
-
border: `1px solid ${primary ? "var(--dsw-brand, #4a7dff)" : "var(--dsw-alias-border-l2, rgba(127,127,127,.3))"}`,
|
|
574
|
-
background: primary ? "var(--dsw-brand, #4a7dff)" : "transparent",
|
|
575
|
-
color: primary ? "#fff" : "var(--dsw-alias-label-primary, #222)",
|
|
576
|
-
cursor: disabled ? "default" : "pointer",
|
|
577
|
-
opacity: disabled ? .5 : 1
|
|
578
|
-
}
|
|
656
|
+
className: primary ? "a2a-btn a2a-btn-primary" : "a2a-btn"
|
|
579
657
|
}, label);
|
|
580
658
|
}
|
|
581
659
|
/**
|
|
660
|
+
* One-click copy button with transient "已复制 ✓" feedback. Uses the async
|
|
661
|
+
* Clipboard API when available (secure contexts), falls back to execCommand
|
|
662
|
+
* otherwise. The copied state lives in this component so the section's hook
|
|
663
|
+
* order stays untouched.
|
|
664
|
+
*/
|
|
665
|
+
function CopyButton(props) {
|
|
666
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
667
|
+
(0, react.useEffect)(() => {
|
|
668
|
+
if (!copied) return;
|
|
669
|
+
const t = window.setTimeout(() => setCopied(false), 1500);
|
|
670
|
+
return () => window.clearTimeout(t);
|
|
671
|
+
}, [copied]);
|
|
672
|
+
const onCopy = async () => {
|
|
673
|
+
try {
|
|
674
|
+
await navigator.clipboard.writeText(props.text);
|
|
675
|
+
setCopied(true);
|
|
676
|
+
return;
|
|
677
|
+
} catch {}
|
|
678
|
+
try {
|
|
679
|
+
const ta = document.createElement("textarea");
|
|
680
|
+
ta.value = props.text;
|
|
681
|
+
ta.style.position = "fixed";
|
|
682
|
+
ta.style.opacity = "0";
|
|
683
|
+
document.body.appendChild(ta);
|
|
684
|
+
ta.select();
|
|
685
|
+
document.execCommand("copy");
|
|
686
|
+
document.body.removeChild(ta);
|
|
687
|
+
setCopied(true);
|
|
688
|
+
} catch {}
|
|
689
|
+
};
|
|
690
|
+
return (0, react.createElement)("button", {
|
|
691
|
+
type: "button",
|
|
692
|
+
className: "a2a-copy-btn",
|
|
693
|
+
onClick: () => void onCopy(),
|
|
694
|
+
"data-copied": String(copied),
|
|
695
|
+
"aria-label": `${copied ? "已复制" : "复制"}${props.label ? ` ${props.label}` : ""}`
|
|
696
|
+
}, copied ? "已复制 ✓" : "复制");
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
582
699
|
* A semantically-correct note about how inbound tasks are executed. Dispatches
|
|
583
700
|
* on the `executor` field; falls back to the legacy `customExecutor` boolean
|
|
584
701
|
* for snapshots from an older host that didn't send `executor`.
|
|
@@ -586,81 +703,122 @@ window.__ModuleLoader__.load({
|
|
|
586
703
|
function executorNote(serve) {
|
|
587
704
|
const kind = serve.executor ?? (serve.customExecutor ? "custom" : void 0);
|
|
588
705
|
if (kind === "custom") return null;
|
|
589
|
-
if (kind === "dsh-agent") return (0, react.createElement)("div", {
|
|
590
|
-
|
|
591
|
-
color: "var(--dsw-alias-label-secondary, #666)",
|
|
592
|
-
background: "rgba(74,125,255,.08)",
|
|
593
|
-
borderRadius: 6,
|
|
594
|
-
padding: "6px 10px"
|
|
595
|
-
} }, "ℹ 入站任务由本 DSH 的 agent 会话执行,按 A2A contextId 一个对话一个会话。");
|
|
596
|
-
if (kind === "none") return (0, react.createElement)("div", { style: {
|
|
597
|
-
fontSize: 12,
|
|
598
|
-
color: "var(--dsw-warning, #b26a00)",
|
|
599
|
-
background: "rgba(178,106,0,.08)",
|
|
600
|
-
borderRadius: 6,
|
|
601
|
-
padding: "6px 10px"
|
|
602
|
-
} }, "⚠ 未配置 executor,且未检测到可用的 agent 循环:入站任务会被拒绝。通过配置注入 execute,或加载 agent-loop 插件。");
|
|
706
|
+
if (kind === "dsh-agent") return (0, react.createElement)("div", { className: "a2a-note" }, "ℹ 入站任务由本 DSH 的 agent 会话执行,按 A2A contextId 一个对话一个会话。");
|
|
707
|
+
if (kind === "none") return (0, react.createElement)("div", { className: "a2a-note a2a-note-warn" }, "⚠ 未配置 executor,且未检测到可用的 agent 循环:入站任务会被拒绝。通过配置注入 execute,或加载 agent-loop 插件。");
|
|
603
708
|
return null;
|
|
604
709
|
}
|
|
605
710
|
/**
|
|
711
|
+
* True when the server has never been given an identity — no agentName (or the
|
|
712
|
+
* bare default) and no skills. Drives the guided-setup card for fresh installs.
|
|
713
|
+
*/
|
|
714
|
+
function needsServeSetup(serve) {
|
|
715
|
+
const name = (serve.agentName ?? "").trim();
|
|
716
|
+
const unnamed = name === "" || name === "DSH Agent";
|
|
717
|
+
const noSkills = (serve.skills?.length ?? 0) === 0;
|
|
718
|
+
return unnamed && noSkills;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Read-only serve panel: status + identity + endpoints + executor/auth, with an
|
|
722
|
+
* "编辑" button that flips the section into {@link serveEditPanel}. Pure (no
|
|
723
|
+
* hooks): the edit-mode flag and draft live in the parent section.
|
|
724
|
+
*/
|
|
725
|
+
function serveViewPanel(serve, busy, act, authInput, setAuthInput, onEdit) {
|
|
726
|
+
return (0, react.createElement)("div", { className: "a2a-panel" }, (0, react.createElement)("div", { className: "a2a-serve-head" }, sec("A2A 服务(本 DSH 作为 Agent 对外)", 0), stateDot(serve.enabled ? "connected" : "disconnected"), (0, react.createElement)("span", {
|
|
727
|
+
className: "a2a-serve-status",
|
|
728
|
+
"data-on": String(serve.enabled)
|
|
729
|
+
}, serve.enabled ? "运行中" : "已下线"), (0, react.createElement)("span", { className: "a2a-serve-ops" }, actionBtn("编辑", false, onEdit, false), serve.enabled ? actionBtn("下线", false, () => void act("server-disable", ""), busy === "server-disable:") : actionBtn("上线", true, () => void act("server-enable", ""), busy === "server-enable:"))), (0, react.createElement)("div", { className: "a2a-serve-meta" }, serve.agentName ? (0, react.createElement)("div", { className: "a2a-serve-name" }, serve.agentName, serve.agentVersion ? (0, react.createElement)("span", { className: "a2a-serve-version a2a-mono" }, `v${serve.agentVersion}`) : null) : null, serve.agentDescription ? (0, react.createElement)("div", { className: "a2a-serve-desc" }, serve.agentDescription) : null, serve.endpoint ? (0, react.createElement)("div", { className: "a2a-meta-row" }, (0, react.createElement)("span", { className: "a2a-meta-key" }, "端点"), (0, react.createElement)("span", { className: "a2a-serve-endpoint a2a-mono" }, serve.endpoint), (0, react.createElement)(CopyButton, {
|
|
730
|
+
text: serve.endpoint,
|
|
731
|
+
label: "端点"
|
|
732
|
+
})) : null, serve.agentCardUrl ? (0, react.createElement)("div", { className: "a2a-meta-row" }, (0, react.createElement)("span", { className: "a2a-meta-key" }, "Agent Card"), (0, react.createElement)("span", { className: "a2a-serve-endpoint a2a-mono" }, serve.agentCardUrl), (0, react.createElement)(CopyButton, {
|
|
733
|
+
text: serve.agentCardUrl,
|
|
734
|
+
label: "Agent Card URL"
|
|
735
|
+
})) : null, (serve.skills?.length ?? 0) > 0 ? (0, react.createElement)("div", { className: "a2a-skill-list" }, (0, react.createElement)("div", { className: "a2a-skill-list-title" }, "Skills"), ...(serve.skills ?? []).map((s) => (0, react.createElement)("div", { key: s.id }, `• ${s.name}${s.description ? ` — ${s.description}` : ""}`))) : null), serve.enabled ? executorNote(serve) : null, serve.enabled ? authControl(serve, busy, act, authInput, setAuthInput) : null);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Serve-identity edit form (also the guided-setup card for fresh installs).
|
|
739
|
+
* Pure: the draft + setter live in the parent section. `guided` switches the
|
|
740
|
+
* copy and the primary action (create-and-serve vs save).
|
|
741
|
+
*/
|
|
742
|
+
function serveEditPanel(draft, setDraft, onSave, onCancel, busy, guided) {
|
|
743
|
+
const set = (patch) => setDraft({
|
|
744
|
+
...draft,
|
|
745
|
+
...patch
|
|
746
|
+
});
|
|
747
|
+
const setSkill = (i, patch) => setDraft({
|
|
748
|
+
...draft,
|
|
749
|
+
skills: draft.skills.map((s, j) => j === i ? {
|
|
750
|
+
...s,
|
|
751
|
+
...patch
|
|
752
|
+
} : s)
|
|
753
|
+
});
|
|
754
|
+
const addSkill = () => setDraft({
|
|
755
|
+
...draft,
|
|
756
|
+
skills: [...draft.skills, {
|
|
757
|
+
id: "",
|
|
758
|
+
name: "",
|
|
759
|
+
description: "",
|
|
760
|
+
tags: ""
|
|
761
|
+
}]
|
|
762
|
+
});
|
|
763
|
+
const delSkill = (i) => setDraft({
|
|
764
|
+
...draft,
|
|
765
|
+
skills: draft.skills.filter((_, j) => j !== i)
|
|
766
|
+
});
|
|
767
|
+
const saving = busy === "set-server-identity:" || busy === "server-enable:";
|
|
768
|
+
const field = (label, key, placeholder) => (0, react.createElement)("div", { className: "a2a-form-row" }, (0, react.createElement)("label", { className: "a2a-form-label" }, label), (0, react.createElement)("input", {
|
|
769
|
+
className: "a2a-input",
|
|
770
|
+
placeholder,
|
|
771
|
+
value: draft[key],
|
|
772
|
+
onChange: (e) => set({ [key]: e.target.value })
|
|
773
|
+
}));
|
|
774
|
+
return (0, react.createElement)("div", { className: "a2a-panel" }, (0, react.createElement)("div", { className: "a2a-panel-title" }, guided ? "配置对外服务(首次设置)" : "编辑对外服务"), guided ? (0, react.createElement)("div", { className: "a2a-note" }, "ℹ 填写下面的信息即可把本 DSH 作为 A2A Agent 发布,保存后自动上线。所有配置写入 a2a.json,无需手改文件。") : null, field("名称", "agentName", "如 My DSH Agent"), field("描述", "agentDescription", "一句话说明这个 Agent 能做什么"), field("版本", "agentVersion", "如 0.1.0"), field("Base URL", "baseUrl", "留空自动推断(对外可达地址)"), (0, react.createElement)("div", { className: "a2a-hint" }, "留空则按服务实际监听地址自动推断;仅在有反向代理或公网域名时才需要手动填写。"), field("端点路径", "endpointPath", "留空默认 /a2a"), (0, react.createElement)("div", { className: "a2a-skill-edit-head" }, (0, react.createElement)("span", { className: "a2a-form-label" }, `技能(${draft.skills.length})`), actionBtn("+ 添加技能", false, addSkill, false)), draft.skills.length === 0 ? (0, react.createElement)("div", { className: "a2a-hint" }, "至少添加一个技能,远程调用方才知道本 Agent 能做什么。") : (0, react.createElement)("div", { className: "a2a-skill-edit-list" }, ...draft.skills.map((s, i) => skillEditRow(s, i, setSkill, delSkill))), (0, react.createElement)("div", { className: "a2a-btn-row" }, guided ? actionBtn("创建并上线", true, () => onSave("enable"), saving) : actionBtn("保存", true, () => onSave(), saving), actionBtn("取消", false, onCancel, false)));
|
|
775
|
+
}
|
|
776
|
+
/** One editable skill row (id/name/description + tags) with a delete button. */
|
|
777
|
+
function skillEditRow(s, i, setSkill, delSkill) {
|
|
778
|
+
const inp = (key, placeholder) => (0, react.createElement)("input", {
|
|
779
|
+
className: "a2a-input",
|
|
780
|
+
placeholder,
|
|
781
|
+
value: s[key],
|
|
782
|
+
onChange: (e) => setSkill(i, { [key]: e.target.value })
|
|
783
|
+
});
|
|
784
|
+
return (0, react.createElement)("div", {
|
|
785
|
+
key: String(i),
|
|
786
|
+
className: "a2a-skill-edit-row"
|
|
787
|
+
}, (0, react.createElement)("div", { className: "a2a-skill-edit-grid" }, inp("id", "id(唯一标识,如 coding)"), inp("name", "名称(如 Coding)")), inp("description", "描述"), (0, react.createElement)("div", { className: "a2a-skill-edit-foot" }, (0, react.createElement)("input", {
|
|
788
|
+
className: "a2a-input",
|
|
789
|
+
placeholder: "标签(逗号分隔,可选)",
|
|
790
|
+
value: s.tags,
|
|
791
|
+
onChange: (e) => setSkill(i, { tags: e.target.value })
|
|
792
|
+
}), actionBtn("删除", false, () => delSkill(i), false)));
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
606
795
|
* Inbound bearer-token control for the serve tab: shows whether the endpoint is
|
|
607
796
|
* token-gated, and lets the operator set or clear the shared token. The token
|
|
608
797
|
* value is never read back from the host — only whether one is configured.
|
|
609
798
|
*/
|
|
610
799
|
function authControl(serve, busy, act, authInput, setAuthInput) {
|
|
611
|
-
return (0, react.createElement)("div", {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
} }, (0, react.createElement)("div", { style: {
|
|
617
|
-
display: "flex",
|
|
618
|
-
alignItems: "center",
|
|
619
|
-
gap: 8
|
|
620
|
-
} }, (0, react.createElement)("span", { style: {
|
|
621
|
-
fontSize: 12.5,
|
|
622
|
-
fontWeight: 600,
|
|
623
|
-
color: "var(--dsw-alias-label-primary, #222)"
|
|
624
|
-
} }, "入站鉴权"), stateDot(serve.authConfigured ? "connected" : "disabled"), (0, react.createElement)("span", { style: {
|
|
625
|
-
fontSize: 12,
|
|
626
|
-
color: serve.authConfigured ? "#2e7d32" : "#9e9e9e"
|
|
627
|
-
} }, serve.authConfigured ? "已启用 Bearer 鉴权" : "未鉴权(任何客户端可调用)")), (0, react.createElement)("div", { style: {
|
|
628
|
-
display: "flex",
|
|
629
|
-
alignItems: "center",
|
|
630
|
-
gap: 8,
|
|
631
|
-
flexWrap: "wrap"
|
|
632
|
-
} }, (0, react.createElement)("input", {
|
|
800
|
+
return (0, react.createElement)("div", { className: "a2a-auth" }, (0, react.createElement)("div", { className: "a2a-auth-row" }, (0, react.createElement)("span", { className: "a2a-auth-label" }, "入站鉴权"), stateDot(serve.authConfigured ? "connected" : "disabled"), (0, react.createElement)("span", {
|
|
801
|
+
className: "a2a-auth-state",
|
|
802
|
+
"data-on": String(serve.authConfigured)
|
|
803
|
+
}, serve.authConfigured ? "已启用 Bearer 鉴权" : "未鉴权(任何客户端可调用)")), (0, react.createElement)("div", { className: "a2a-auth-input-row" }, (0, react.createElement)("input", {
|
|
804
|
+
className: "a2a-input",
|
|
633
805
|
type: "password",
|
|
634
806
|
placeholder: serve.authConfigured ? "输入新 token 以替换" : "设置 Bearer Token 以开启鉴权",
|
|
635
807
|
value: authInput,
|
|
636
|
-
onChange: (e) => setAuthInput(e.target.value)
|
|
637
|
-
style: {
|
|
638
|
-
padding: "4px 8px",
|
|
639
|
-
fontSize: 13,
|
|
640
|
-
borderRadius: 6,
|
|
641
|
-
border: "1px solid var(--dsw-alias-border-l2, rgba(127,127,127,.3))",
|
|
642
|
-
background: "var(--dsw-alias-fill-l1, rgba(127,127,127,.06))",
|
|
643
|
-
color: "var(--dsw-alias-label-primary, #222)",
|
|
644
|
-
flex: 1,
|
|
645
|
-
minWidth: 220
|
|
646
|
-
}
|
|
808
|
+
onChange: (e) => setAuthInput(e.target.value)
|
|
647
809
|
}), actionBtn("保存", true, () => {
|
|
648
810
|
act("set-server-auth", "", { authToken: authInput.trim() });
|
|
649
811
|
setAuthInput("");
|
|
650
812
|
}, busy === "set-server-auth:" || !authInput.trim()), serve.authConfigured ? actionBtn("清除", false, () => {
|
|
651
813
|
act("set-server-auth", "", { authToken: "" });
|
|
652
814
|
setAuthInput("");
|
|
653
|
-
}, busy === "set-server-auth:") : null), (0, react.createElement)("div", {
|
|
654
|
-
fontSize: 11.5,
|
|
655
|
-
color: "var(--dsw-alias-label-tertiary, #999)"
|
|
656
|
-
} }, "token 会明文存入 a2a.json,重启后仍生效。设置后,入站 JSON-RPC 请求需带 Authorization: Bearer <token>。"));
|
|
815
|
+
}, busy === "set-server-auth:") : null), (0, react.createElement)("div", { className: "a2a-hint" }, "token 会明文存入 a2a.json,重启后仍生效。设置后,入站 JSON-RPC 请求需带 Authorization: Bearer <token>。"));
|
|
657
816
|
}
|
|
658
817
|
function inboundRow(p, busy, act) {
|
|
659
|
-
return (0, react.createElement)("tr", { key: p.id }, (0, react.createElement)("td", {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
} }, p.source ?? "—"), (0, react.createElement)("td", { style: tdStyle }, new Date(p.firstSeen).toLocaleTimeString()), (0, react.createElement)("td", { style: tdStyle }, new Date(p.lastSeen).toLocaleTimeString()), (0, react.createElement)("td", { style: tdStyle }, String(p.taskCount)), (0, react.createElement)("td", { style: tdStyle }, p.streaming ? "● 流" : "—"), (0, react.createElement)("td", { style: tdStyle }, actionBtn("关闭", false, () => void act("close-peer", p.id), busy === `close-peer:${p.id}`)));
|
|
818
|
+
return (0, react.createElement)("tr", { key: p.id }, (0, react.createElement)("td", { "data-label": "来源" }, p.label), (0, react.createElement)("td", {
|
|
819
|
+
className: "a2a-cell-mono",
|
|
820
|
+
"data-label": "地址"
|
|
821
|
+
}, p.source ?? "—"), (0, react.createElement)("td", { "data-label": "首次连接" }, new Date(p.firstSeen).toLocaleTimeString()), (0, react.createElement)("td", { "data-label": "最近活动" }, new Date(p.lastSeen).toLocaleTimeString()), (0, react.createElement)("td", { "data-label": "任务" }, String(p.taskCount)), (0, react.createElement)("td", { "data-label": "流" }, p.streaming ? "● 流" : "—"), (0, react.createElement)("td", { "data-label": "操作" }, actionBtn("关闭", false, () => void act("close-peer", p.id), busy === `close-peer:${p.id}`)));
|
|
664
822
|
}
|
|
665
823
|
//#endregion
|
|
666
824
|
exports.DashboardSection = DashboardSection;
|