@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/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)("out");
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 inputStyle = {
137
- padding: "4px 8px",
138
- fontSize: 13,
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
- return (0, react.createElement)("div", { style: {
145
- display: "flex",
146
- flexDirection: "column",
147
- gap: 20
148
- } }, (0, react.createElement)("div", { style: {
149
- color: "var(--dsw-alias-label-secondary, #666)",
150
- fontSize: 13,
151
- lineHeight: 1.6
152
- } }, "管理本 DSH 的 A2A Agent 连接:连接远程 Agent(出站),查看谁在调用本 DSH 的 A2A 服务(入站)。"), error ? (0, react.createElement)("div", { style: {
153
- color: "var(--dsw-danger, #c62828)",
154
- fontSize: 13,
155
- background: "var(--dsw-alias-fill-l2, rgba(127,127,127,.08))",
156
- borderRadius: 8,
157
- padding: "8px 12px"
158
- } }, `加载失败:${error}`) : null, notice ? (0, react.createElement)("div", { style: {
159
- color: "var(--dsw-alias-label-primary, #222)",
160
- fontSize: 13,
161
- background: "var(--dsw-alias-fill-l2, rgba(127,127,127,.08))",
162
- borderRadius: 8,
163
- padding: "8px 12px"
164
- } }, notice) : null, tabRail([
165
- tabBtn("出站 Agent", outbound.length, tab === "out", () => setTab("out")),
166
- tabBtn("入站连接", inbound.length, tab === "in", () => setTab("in")),
167
- serve ? tabBtn("A2A 服务", 0, tab === "serve", () => setTab("serve")) : null
168
- ]), tab === "out" ? (0, react.createElement)("div", { style: {
169
- display: "flex",
170
- flexDirection: "column",
171
- gap: 16
172
- } }, (0, react.createElement)("div", { style: {
173
- display: "flex",
174
- flexDirection: "column",
175
- gap: 10
176
- } }, sec("已连接 Agent", outbound.length), outbound.length === 0 ? emptyRow("暂无已连接的 Agent。使用下方\"添加 Agent\"通过 Agent Card URL 连接远程 A2A Agent。") : (0, react.createElement)("div", { style: {
177
- display: "flex",
178
- flexDirection: "column",
179
- gap: 8
180
- } }, ...outbound.map((a) => agentCard(a, busy, act, expanded, setExpanded)))), (0, react.createElement)("div", { style: {
181
- display: "flex",
182
- flexDirection: "column",
183
- gap: 10,
184
- background: "var(--dsw-alias-fill-l2, rgba(127,127,127,.05))",
185
- borderRadius: 10,
186
- padding: "14px 16px"
187
- } }, (0, react.createElement)("div", { style: {
188
- fontSize: 13,
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", { style: {
209
- display: "flex",
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
- style: {
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", { style: {
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", { style: {
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
- style: {
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
- style: {
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", { style: {
380
- fontSize: 11,
381
- color: "var(--dsw-alias-label-tertiary, #999)",
382
- width: 14,
383
- display: "inline-block",
384
- transition: "transform .15s",
385
- transform: open ? "rotate(90deg)" : "none"
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
- style: {
426
- display: "flex",
427
- flexDirection: "column",
428
- gap: 2
429
- }
430
- }, (0, react.createElement)("div", { style: {
431
- display: "flex",
432
- alignItems: "baseline",
433
- gap: 6
434
- } }, (0, react.createElement)("span", { style: {
435
- fontSize: 12.5,
436
- fontWeight: 600,
437
- color: "var(--dsw-alias-label-primary, #222)",
438
- fontFamily: "monospace"
439
- } }, s.name), s.tags?.length ? (0, react.createElement)("span", { style: {
440
- fontSize: 11,
441
- color: "var(--dsw-alias-label-tertiary, #999)"
442
- } }, s.tags.join(", ")) : null), s.description ? (0, react.createElement)("div", { style: {
443
- fontSize: 12,
444
- color: "var(--dsw-alias-label-secondary, #666)"
445
- } }, s.description) : null))) : open ? (0, react.createElement)("div", { style: {
446
- fontSize: 12,
447
- color: "var(--dsw-alias-label-tertiary, #999)"
448
- } }, "(该 Agent 未声明技能)") : null);
449
- }
450
- function sourceBadge(color, bg, _label) {
451
- return {
452
- color,
453
- background: bg,
454
- borderRadius: 4,
455
- padding: "1px 6px"
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
- style: {
466
- padding: "7px 1px 9px",
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 replicating `.pbvGtq_tabs` (border-bottom rail). */
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
- style: {
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", { style: {
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", { style: {
517
- color: "var(--dsw-alias-label-tertiary, #999)",
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", { style: thStyle }, c)));
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", { style: {
555
- display: "inline-block",
556
- width: 8,
557
- height: 8,
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
- style: {
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", { style: {
590
- fontSize: 12,
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", { style: {
612
- display: "flex",
613
- flexDirection: "column",
614
- gap: 6,
615
- marginTop: 4
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", { style: {
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", { style: tdStyle }, p.label), (0, react.createElement)("td", { style: {
660
- ...tdStyle,
661
- fontFamily: "monospace",
662
- fontSize: 12
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;