dsh-ntr 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 +4 -2
- package/README.md +4 -2
- package/cordis.patch.yml +56 -28
- package/lib/bridge.js +378 -330
- package/lib/client/acp-ui.js +60 -32
- package/lib/client/index.js +17 -0
- package/lib/client/panel.js +257 -103
- package/lib/client/permission-dock.js +72 -0
- package/lib/client/session-controls.js +4 -4
- package/lib/client/store.js +88 -22
- package/lib/client.js +584 -226
- package/lib/client.js.map +1 -1
- package/lib/impl.js +128 -45
- package/lib/index.js +20 -52
- package/lib/types/bridge.d.ts +164 -214
- package/lib/types/client/panel.d.ts +59 -20
- package/lib/types/client/permission-dock.d.ts +31 -0
- package/lib/types/client/store.d.ts +52 -15
- package/lib/types/impl.d.ts +93 -27
- package/lib/types/protocol.d.ts +137 -52
- package/package.json +1 -1
- package/src/bridge.ts +433 -417
- package/src/client/acp-ui.tsx +60 -32
- package/src/client/index.tsx +18 -0
- package/src/client/panel.tsx +395 -197
- package/src/client/permission-dock.tsx +133 -0
- package/src/client/session-controls.tsx +4 -4
- package/src/client/store.ts +112 -36
- package/src/impl.ts +166 -82
- package/src/index.ts +20 -65
- package/src/protocol.ts +143 -52
package/lib/client/panel.js
CHANGED
|
@@ -3,11 +3,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
* React surface for ACP: the settings configuration section.
|
|
4
4
|
*
|
|
5
5
|
* Scope is deliberately narrow — this is a CONFIGURATION surface. Runtime
|
|
6
|
-
* interaction lives in the conversation: permission
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* interaction lives in the conversation: every permission ask with a chat
|
|
7
|
+
* identity is answered on a card above that conversation's composer
|
|
8
|
+
* (`permission-dock.tsx`). The ledger card here is the cross-session mirror —
|
|
9
|
+
* asks whose conversation is not open, or which have no chat identity at all —
|
|
10
|
+
* and is where those are answered (see `permissionLedgerCard`).
|
|
11
11
|
*
|
|
12
12
|
* Each configured agent is one compact row (status + current model + badges)
|
|
13
13
|
* that expands into the full connection form and model catalog. The row stays
|
|
@@ -42,8 +42,10 @@ const STYLE = `
|
|
|
42
42
|
--ntr-radius: 10px; --ntr-radius-sm: 7px;
|
|
43
43
|
--ntr-font: 13px; --ntr-font-sm: 12px; --ntr-font-xs: 11px;
|
|
44
44
|
--ntr-line: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
|
|
45
|
-
--ntr-muted: var(--dsw-alias-
|
|
46
|
-
--ntr-danger:
|
|
45
|
+
--ntr-muted: var(--dsw-alias-label-secondary, #6b7280);
|
|
46
|
+
--ntr-danger: var(--dsw-alias-state-error-primary, #dc2626);
|
|
47
|
+
--ntr-warn: var(--dsw-alias-state-warn-label, #b45309);
|
|
48
|
+
--ntr-success: color-mix(in srgb, var(--dsw-alias-state-success-primary, #16a34a) 72%, var(--dsw-alias-label-primary, #111827));
|
|
47
49
|
--ntr-accent: var(--dsw-alias-button-primary-fill, #2563eb);
|
|
48
50
|
width: 100%; max-width: 960px; box-sizing: border-box; margin: 0 auto;
|
|
49
51
|
padding: 20px; overflow: auto; font-size: var(--ntr-font); line-height: 1.55;
|
|
@@ -67,25 +69,25 @@ const STYLE = `
|
|
|
67
69
|
}
|
|
68
70
|
.dwa-btn:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); border-color: var(--dsw-alias-border-l1, #9ca3af); }
|
|
69
71
|
.dwa-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
70
|
-
.dwa-btn-primary { background: var(--ntr-accent); color: var(--dsw-alias-
|
|
72
|
+
.dwa-btn-primary { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); border-color: transparent; font-weight: 550; }
|
|
71
73
|
.dwa-btn-primary:hover { background: var(--ntr-accent); border-color: transparent; opacity: 0.88; }
|
|
72
74
|
.dwa-btn-secondary { background: var(--dsw-alias-bg-layer-2, #f3f4f6); }
|
|
73
|
-
.dwa-btn-danger { color: var(--ntr-danger); border-color: #fca5a5; }
|
|
74
|
-
.dwa-btn-danger:hover { background: #fee2e2; }
|
|
75
|
+
.dwa-btn-danger { color: var(--ntr-danger); border-color: var(--dsw-alias-state-error-secondary, #fca5a5); }
|
|
76
|
+
.dwa-btn-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, #fee2e2); }
|
|
75
77
|
.dwa-section { margin-bottom: 24px; }
|
|
76
78
|
.dwa-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
|
|
77
79
|
.dwa-section-title { font-size: 14px; font-weight: 650; margin: 0; display: flex; align-items: center; gap: 8px; }
|
|
78
80
|
.dwa-section-copy { margin: 3px 0 0; font-size: var(--ntr-font-xs); color: var(--ntr-muted); }
|
|
79
81
|
.dwa-card { border: var(--ntr-line); border-radius: var(--ntr-radius); padding: 16px; background: var(--dsw-alias-bg-layer-1, #fff); font-size: var(--ntr-font); line-height: 1.55; }
|
|
80
|
-
.dwa-card-warn { border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-
|
|
82
|
+
.dwa-card-warn { border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-tertiary, #fffbeb); }
|
|
81
83
|
.dwa-mode-banner { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 5px 16px; align-items: start; margin-bottom: 24px; padding: 14px 16px; }
|
|
82
84
|
.dwa-mode-title { display: flex; align-items: center; gap: 8px; font-weight: 650; }
|
|
83
85
|
.dwa-mode-copy { grid-column: 1 / -1; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
|
|
84
86
|
.dwa-mode-source { display: block; margin-top: 5px; overflow-wrap: anywhere; opacity: 0.85; }
|
|
85
87
|
.dwa-badge { font-size: var(--ntr-font-xs); line-height: 1.4; border-radius: 999px; padding: 2px 8px; background: var(--dsw-alias-bg-layer-2, #f3f4f6); color: var(--ntr-muted); font-weight: 550; display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
|
|
86
|
-
.dwa-badge-success { background: #
|
|
87
|
-
.dwa-badge-warn { background: #
|
|
88
|
-
.dwa-badge-info { background: #
|
|
88
|
+
.dwa-badge-success { background: var(--dsw-alias-state-success-tertiary, #dcfce7); color: var(--ntr-success); }
|
|
89
|
+
.dwa-badge-warn { background: var(--dsw-alias-state-warn-tertiary, #fef3c7); color: var(--ntr-warn); }
|
|
90
|
+
.dwa-badge-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
|
|
89
91
|
.dwa-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
|
|
90
92
|
.dwa-agent-list { display: flex; flex-direction: column; gap: 8px; }
|
|
91
93
|
.dwa-agent-card { border: var(--ntr-line); border-radius: var(--ntr-radius); background: var(--dsw-alias-bg-layer-1, #fff); overflow: visible; transition: border-color 0.15s ease; }
|
|
@@ -107,7 +109,7 @@ const STYLE = `
|
|
|
107
109
|
.dwa-subsection-copy { margin: 3px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
|
|
108
110
|
.dwa-form-row { display: flex; gap: var(--ntr-gap-3); margin-bottom: 14px; align-items: flex-start; }
|
|
109
111
|
.dwa-form-col { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
|
110
|
-
.dwa-form-label { font-size: var(--ntr-font-sm); font-weight: 600; color: var(--dsw-alias-
|
|
112
|
+
.dwa-form-label { font-size: var(--ntr-font-sm); font-weight: 600; color: var(--dsw-alias-label-primary, #374151); }
|
|
111
113
|
.dwa-hint { font-size: var(--ntr-font-xs); line-height: 1.55; color: var(--ntr-muted); }
|
|
112
114
|
.dwa-error { font-size: var(--ntr-font-xs); color: var(--ntr-danger); }
|
|
113
115
|
.dwa-input { min-height: 36px; font: inherit; font-size: var(--ntr-font-sm); padding: 7px 10px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); border-radius: var(--ntr-radius-sm); background: var(--dsw-alias-bg-layer-1, #fff); color: inherit; width: 100%; box-sizing: border-box; }
|
|
@@ -132,7 +134,7 @@ const STYLE = `
|
|
|
132
134
|
.dwa-add-content { border-top: var(--ntr-line); padding: 14px 16px 16px; }
|
|
133
135
|
.dwa-chip { min-height: 30px; font: inherit; font-size: var(--ntr-font-sm); border: var(--ntr-line); border-radius: 999px; padding: 4px 12px; background: var(--dsw-alias-bg-layer-1, #fff); cursor: pointer; color: inherit; }
|
|
134
136
|
.dwa-chip:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
|
|
135
|
-
.dwa-chip-active { border-color: var(--ntr-accent); background: #
|
|
137
|
+
.dwa-chip-active { border-color: var(--ntr-accent); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #2563eb) 8%, transparent); color: var(--dsw-alias-state-business-primary, #2563eb); font-weight: 550; }
|
|
136
138
|
.dwa-chip-list { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 12px; }
|
|
137
139
|
.dwa-switch { display: inline-flex; align-items: center; gap: 7px; font-size: var(--ntr-font-sm); cursor: pointer; user-select: none; }
|
|
138
140
|
.dwa-inline-setting { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: var(--ntr-radius-sm); background: var(--dsw-alias-bg-layer-2, #f9fafb); }
|
|
@@ -145,7 +147,7 @@ const STYLE = `
|
|
|
145
147
|
/* Tall enough for a comfortable selection (about ten rows) before scrolling;
|
|
146
148
|
the cap keeps the two stacked panes from pushing the rest of the card away. */
|
|
147
149
|
.dwa-model-pane { min-width: 0; max-height: 360px; display: flex; flex-direction: column; border: var(--ntr-line); border-radius: var(--ntr-radius-sm); background: var(--dsw-alias-bg-layer-1, #fff); overflow: hidden; }
|
|
148
|
-
.dwa-model-pane-head { display: flex; align-items: center; gap: 6px; padding: 7px 9px; border-bottom: var(--ntr-line); font-size: var(--ntr-font-xs); font-weight: 600; color: var(--dsw-alias-
|
|
150
|
+
.dwa-model-pane-head { display: flex; align-items: center; gap: 6px; padding: 7px 9px; border-bottom: var(--ntr-line); font-size: var(--ntr-font-xs); font-weight: 600; color: var(--dsw-alias-label-secondary, #4b5563); }
|
|
149
151
|
.dwa-model-pane-head .dwa-btn { margin-left: auto; min-height: 26px; padding: 2px 8px; font-size: var(--ntr-font-xs); }
|
|
150
152
|
.dwa-model-pane-head .dwa-caret-btn { flex: none; width: 22px; height: 22px; background: transparent; }
|
|
151
153
|
.dwa-model-pane-head .dwa-caret-btn:hover { background: var(--dsw-alias-interactive-bg-active, #e5e7eb); }
|
|
@@ -163,7 +165,7 @@ const STYLE = `
|
|
|
163
165
|
.dwa-model-empty { padding: 6px; }
|
|
164
166
|
.dwa-model-row { display: flex; align-items: center; gap: 6px; width: 100%; border: none; background: none; padding: 5px 6px; border-radius: 5px; cursor: pointer; color: inherit; font: inherit; font-size: var(--ntr-font-sm); text-align: left; }
|
|
165
167
|
.dwa-model-row:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
|
|
166
|
-
.dwa-model-row.is-on { background: #
|
|
168
|
+
.dwa-model-row.is-on { background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #2563eb) 8%, transparent); color: var(--dsw-alias-state-business-primary, #2563eb); }
|
|
167
169
|
.dwa-model-row-mark { width: 12px; flex: none; text-align: center; opacity: 0.75; }
|
|
168
170
|
.dwa-model-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
169
171
|
/* Name and credit keep their room; the id is the only cell that collapses. */
|
|
@@ -185,15 +187,31 @@ const STYLE = `
|
|
|
185
187
|
.dwa-seg > button { min-height: 30px; font: inherit; font-size: var(--ntr-font-sm); color: inherit; border: none; background: var(--dsw-alias-bg-layer-1, #fff); padding: 4px 14px; cursor: pointer; }
|
|
186
188
|
.dwa-seg > button + button { border-left: 1px solid var(--dsw-alias-border-l2, #d1d5db); }
|
|
187
189
|
.dwa-seg > button:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
|
|
188
|
-
.dwa-seg > button.is-on { background: var(--ntr-accent); color: var(--dsw-alias-
|
|
190
|
+
.dwa-seg > button.is-on { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); font-weight: 550; }
|
|
189
191
|
.dwa-preference-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 0; }
|
|
190
192
|
.dwa-preference-row + .dwa-preference-row { border-top: var(--ntr-line); }
|
|
193
|
+
/* A control that needs the whole card: the label sits above it, not beside
|
|
194
|
+
it. A right-aligned segmented control squeezed its labels into two lines
|
|
195
|
+
each (中/止, 自动推/进), which read as broken rather than as choices. */
|
|
196
|
+
.dwa-preference-block { padding: 12px 0; }
|
|
197
|
+
.dwa-preference-block + .dwa-preference-block { border-top: var(--ntr-line); }
|
|
191
198
|
.dwa-preference-label { font-size: var(--ntr-font-sm); font-weight: 600; }
|
|
192
199
|
.dwa-preference-copy { margin-top: 2px; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
|
|
193
|
-
.dwa-
|
|
194
|
-
.dwa-
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
.dwa-seg-wide { display: flex; width: 100%; margin-top: 10px; }
|
|
201
|
+
.dwa-seg-wide > button { flex: 1; }
|
|
202
|
+
/* The wait window, as a slider: the number is the headline, the rungs are the
|
|
203
|
+
scale under the track. */
|
|
204
|
+
.dwa-deadline-value { margin-top: 8px; font-size: 20px; font-weight: 600; }
|
|
205
|
+
.dwa-deadline-range { width: 100%; margin-top: 6px; accent-color: var(--ntr-accent); }
|
|
206
|
+
.dwa-deadline-range:disabled { opacity: 0.4; }
|
|
207
|
+
.dwa-deadline-ticks { display: flex; justify-content: space-between; margin-top: 2px; color: var(--ntr-muted); font-size: 10px; }
|
|
208
|
+
.dwa-deadline-ticks > span { white-space: nowrap; }
|
|
209
|
+
/* The facts that make a number here meaningless, in warning ink rather than
|
|
210
|
+
the muted gray every other hint uses: the value the config file is
|
|
211
|
+
outranking, on the row it belongs to. */
|
|
212
|
+
.dwa-deadline-flag { margin-top: 4px; font-size: var(--ntr-font-xs); font-weight: 550; color: var(--ntr-warn); }
|
|
213
|
+
.dwa-deadline-warn { margin-top: 10px; padding: 9px 12px; border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-secondary, #fffbeb); border-radius: var(--ntr-radius-sm); font-size: var(--ntr-font-xs); line-height: 1.55; }
|
|
214
|
+
.dwa-deadline-reset { display: flex; justify-content: flex-end; margin-top: 8px; }
|
|
197
215
|
@media (max-width: 640px) {
|
|
198
216
|
.dwa-root { padding: 14px; }
|
|
199
217
|
.dwa-page-header { flex-direction: column; }
|
|
@@ -203,6 +221,11 @@ const STYLE = `
|
|
|
203
221
|
.dwa-model-search { flex: 1 1 auto; width: auto; }
|
|
204
222
|
.dwa-subsection-head, .dwa-preference-row { align-items: flex-start; flex-direction: column; }
|
|
205
223
|
.dwa-default-hint { align-items: flex-start; flex-direction: column; }
|
|
224
|
+
/* The slider's scale: up to eight rungs with full unit words ("15 分钟")
|
|
225
|
+
need a smaller face before they can collide on a narrow panel. The rung
|
|
226
|
+
count follows the mode (自动推进 adds 不等待), and the row is a flex
|
|
227
|
+
space-between, so a shorter scale just spreads wider. */
|
|
228
|
+
.dwa-deadline-ticks { font-size: 9px; }
|
|
206
229
|
}
|
|
207
230
|
@media (prefers-reduced-motion: reduce) {
|
|
208
231
|
.dwa-root *, .dwa-root *::before, .dwa-root *::after { transition: none !important; }
|
|
@@ -223,9 +246,9 @@ function useUi(acp) {
|
|
|
223
246
|
}
|
|
224
247
|
function stateVisual(state, acp) {
|
|
225
248
|
switch (state) {
|
|
226
|
-
case 'ready': return { color: '#22c55e', text: acp.label('已连接', 'Connected') };
|
|
227
|
-
case 'starting': return { color: '#eab308', text: acp.label('启动中', 'Starting') };
|
|
228
|
-
case 'error': return { color: '#ef4444', text: acp.label('错误', 'Error') };
|
|
249
|
+
case 'ready': return { color: 'var(--dsw-alias-state-success-primary, #22c55e)', text: acp.label('已连接', 'Connected') };
|
|
250
|
+
case 'starting': return { color: 'var(--dsw-alias-state-warn-primary, #eab308)', text: acp.label('启动中', 'Starting') };
|
|
251
|
+
case 'error': return { color: 'var(--dsw-alias-state-error-primary, #ef4444)', text: acp.label('错误', 'Error') };
|
|
229
252
|
// A configured-but-resting agent is the normal state, not a fault: the
|
|
230
253
|
// process is not kept resident and connects on demand the first time a DSH
|
|
231
254
|
// chat turn uses it. Surfacing a runtime lifecycle here ("stopped"/"idle")
|
|
@@ -233,7 +256,7 @@ function stateVisual(state, acp) {
|
|
|
233
256
|
// which the config-time 测试连接 (agent/test) already answers. So the
|
|
234
257
|
// resting state just says 已配置.
|
|
235
258
|
default: return {
|
|
236
|
-
color: '#9ca3af',
|
|
259
|
+
color: 'var(--dsw-alias-state-idle-primary, #9ca3af)',
|
|
237
260
|
text: acp.label('已配置', 'Configured'),
|
|
238
261
|
title: acp.label('已配置,进程不常驻。在 DSH 聊天中首次使用时自动连接;展开区可用「测试连接」验证命令能否正常起来。', 'Configured; the process is not kept resident. It connects automatically the first time a DSH chat turn uses it — use “Test connection” in the expanded panel to verify the command starts.'),
|
|
239
262
|
};
|
|
@@ -436,80 +459,217 @@ export function payloadEquals(payload, current) {
|
|
|
436
459
|
/**
|
|
437
460
|
* The settings panel's permission-ledger rule.
|
|
438
461
|
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* (`approvalRouted` → a read-only count, no buttons) hid the only answer
|
|
446
|
-
* entry exactly when the native route had degraded to unavailable, and the
|
|
447
|
-
* ask then hung until its timeout.
|
|
448
|
-
*
|
|
449
|
-
* `fellBackFromConversation` is the case the card has to explain: the host
|
|
450
|
-
* can route asks, and this one still did not make it.
|
|
462
|
+
* Pending asks are shown in the conversation card while that conversation is
|
|
463
|
+
* open; this card is the cross-session mirror for the ones it cannot show —
|
|
464
|
+
* asks belonging to another conversation, and `/acp` RPC-only asks with no
|
|
465
|
+
* chat identity at all. It therefore appears whenever ANY ask is pending: a
|
|
466
|
+
* pending entry with no conversation card would otherwise have no answer
|
|
467
|
+
* surface anywhere.
|
|
451
468
|
*/
|
|
452
469
|
export function permissionLedgerCard(ui) {
|
|
453
|
-
|
|
454
|
-
return { visible, fellBackFromConversation: visible && ui.approvalRouted };
|
|
470
|
+
return { visible: ui.permissions.length > 0 };
|
|
455
471
|
}
|
|
456
472
|
/**
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
473
|
+
* The ONE wait window, as the rungs the slider snaps to.
|
|
474
|
+
*
|
|
475
|
+
* Shortest wait first, and `0` (不等待) FIRST: the slider reads left to right
|
|
476
|
+
* as "wait longer", and "do not wait at all" is the shortest wait there is.
|
|
477
|
+
* Never expiring is NOT on this scale — that is the `wait` MODE's business,
|
|
478
|
+
* and having 「一直等待」 in both places made the card say the same thing twice
|
|
479
|
+
* in two ways.
|
|
480
|
+
*
|
|
481
|
+
* A window the config file names that is not on this ladder (45 seconds, say)
|
|
482
|
+
* still runs as configured: the card prints the exact number and parks the
|
|
483
|
+
* thumb on the nearest rung, so the slider never pretends a number the host
|
|
484
|
+
* is not using.
|
|
485
|
+
*/
|
|
486
|
+
const DEADLINE_LADDER = [
|
|
487
|
+
0,
|
|
488
|
+
30_000,
|
|
489
|
+
60_000,
|
|
490
|
+
300_000,
|
|
491
|
+
900_000,
|
|
492
|
+
1_800_000,
|
|
493
|
+
3_600_000,
|
|
494
|
+
21_600_000,
|
|
495
|
+
];
|
|
496
|
+
/**
|
|
497
|
+
* The shortest REAL wait — the first rung above 不等待.
|
|
498
|
+
*
|
|
499
|
+
* Hand-copied from the host half (`APPROVAL_DEADLINE_MIN_WAIT_MS` in
|
|
500
|
+
* `src/impl.ts`): the browser half may not import the host module, so the
|
|
501
|
+
* equivalence is pinned by a test instead (see `tests/acp-view.spec.ts`).
|
|
460
502
|
*/
|
|
461
|
-
const
|
|
462
|
-
/**
|
|
463
|
-
|
|
464
|
-
|
|
503
|
+
export const DEADLINE_MIN_WAIT_MS = 30_000;
|
|
504
|
+
/**
|
|
505
|
+
* The window the card shows under a mode.
|
|
506
|
+
*
|
|
507
|
+
* 不等待 (`0`) is the **auto** mode's rung: it answers with the option the
|
|
508
|
+
* agent's own kinds imply, so the agent CONTINUES. `abort` has no such rung —
|
|
509
|
+
* refusing every ask the instant it arrives stops the agent at every gate it
|
|
510
|
+
* meets, which is not a timeout but a wall. So under `abort` the window floors
|
|
511
|
+
* to the shortest real wait, and the card never shows a zero it cannot run.
|
|
512
|
+
*
|
|
513
|
+
* `undefined` (the `wait` mode) stays undefined: there is no window at all.
|
|
514
|
+
*/
|
|
515
|
+
export function deadlineValueFor(mode, ms) {
|
|
516
|
+
if (ms === undefined)
|
|
517
|
+
return undefined;
|
|
518
|
+
return mode === 'abort' ? Math.max(ms, DEADLINE_MIN_WAIT_MS) : ms;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* The rungs a mode offers.
|
|
522
|
+
*
|
|
523
|
+
* 不等待 (`0`) is the **auto** mode's rung: it answers with the option the
|
|
524
|
+
* agent's own kinds imply, so the agent CONTINUES. `abort` has no such rung —
|
|
525
|
+
* refusing every ask the instant it arrives stops the agent at every gate it
|
|
526
|
+
* meets, which is not a timeout but a wall. So `abort` starts at the shortest
|
|
527
|
+
* real wait.
|
|
528
|
+
*/
|
|
529
|
+
function deadlineLadder(mode) {
|
|
530
|
+
return mode === 'auto' ? DEADLINE_LADDER : DEADLINE_LADDER.filter(ms => ms > 0);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* The rung the slider shows for a window. Exact hits keep their rung; anything
|
|
534
|
+
* else takes the nearest one. A tie goes to the SHORTER wait: the thumb must
|
|
535
|
+
* never promise more patience than the window has.
|
|
536
|
+
*/
|
|
537
|
+
export function deadlineSliderIndex(ms, mode = 'auto') {
|
|
538
|
+
const ladder = deadlineLadder(mode);
|
|
539
|
+
const exact = ladder.indexOf(ms);
|
|
540
|
+
if (exact >= 0)
|
|
541
|
+
return exact;
|
|
542
|
+
let best = 0;
|
|
543
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
544
|
+
for (let i = 0; i < ladder.length; i++) {
|
|
545
|
+
const distance = Math.abs(ms - ladder[i]);
|
|
546
|
+
if (distance < bestDistance) {
|
|
547
|
+
bestDistance = distance;
|
|
548
|
+
best = i;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return best;
|
|
552
|
+
}
|
|
553
|
+
/** The three modes, in the order a user reads them: refuse, answer, wait. */
|
|
554
|
+
const DEADLINE_MODES = ['abort', 'auto', 'wait'];
|
|
555
|
+
/** `5 分钟` / `30 秒` / `不等待` — the readout and the tick label, one formatter. */
|
|
465
556
|
function deadlineText(ms, acp) {
|
|
466
557
|
if (ms <= 0)
|
|
467
|
-
return acp.label('
|
|
558
|
+
return acp.label('不等待', 'No wait');
|
|
468
559
|
const seconds = Math.round(ms / 1000);
|
|
469
560
|
if (seconds < 60)
|
|
470
561
|
return acp.label(`${String(seconds)} 秒`, `${String(seconds)}s`);
|
|
471
562
|
const minutes = Math.round(seconds / 60);
|
|
472
|
-
|
|
563
|
+
if (minutes < 60)
|
|
564
|
+
return acp.label(`${String(minutes)} 分钟`, `${String(minutes)}m`);
|
|
565
|
+
const hours = Math.round(minutes / 60);
|
|
566
|
+
return acp.label(`${String(hours)} 小时`, `${String(hours)}h`);
|
|
567
|
+
}
|
|
568
|
+
/** The mode's label — one formatter, so the picker and the override note agree. */
|
|
569
|
+
function modeText(mode, acp) {
|
|
570
|
+
if (mode === 'abort')
|
|
571
|
+
return acp.label('超时中止', 'Abort');
|
|
572
|
+
if (mode === 'auto')
|
|
573
|
+
return acp.label('自动推进', 'Auto');
|
|
574
|
+
return acp.label('一直等待', 'Wait');
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* The mode block: what happens when nobody answers.
|
|
578
|
+
*
|
|
579
|
+
* Three values and no more — refuse, answer, wait — so the picker is a
|
|
580
|
+
* segmented control, not a dropdown. The buttons stretch across the whole
|
|
581
|
+
* card: a right-aligned segmented control squeezed the labels into two lines
|
|
582
|
+
* each (中/止, 自动推/进), which read as broken rather than as choices.
|
|
583
|
+
*
|
|
584
|
+
* The copy under the buttons says what the SELECTED mode does, in one
|
|
585
|
+
* sentence — the labels alone ("超时中止") cannot. `auto` carries a warning of
|
|
586
|
+
* its own: it approves things nobody looked at.
|
|
587
|
+
*/
|
|
588
|
+
function DeadlineModeBlock(props) {
|
|
589
|
+
const { acp, mode, override } = props;
|
|
590
|
+
const copy = mode === 'wait'
|
|
591
|
+
? acp.label('一直等待应答,不设超时时间。', 'Wait until you answer — no deadline.')
|
|
592
|
+
: mode === 'auto'
|
|
593
|
+
? acp.label('超过设定时间未应答,自动代替你选择一个选项让 agent 继续执行。', 'Past the deadline, an option is chosen for you and the agent continues.')
|
|
594
|
+
: acp.label('超过设定时间未应答,自动拒绝该操作,agent 继续执行。', 'Past the deadline, the operation is refused and the agent continues.');
|
|
595
|
+
return (_jsxs("div", { className: "dwa-preference-block", children: [_jsx("div", { className: "dwa-preference-label", children: acp.label('超时处理方式', 'When nobody answers') }), _jsx("div", { className: "dwa-seg dwa-seg-wide", children: DEADLINE_MODES.map(option => (_jsx("button", { type: "button", className: mode === option ? 'is-on' : '', onClick: () => void acp.setApprovalTimeoutMode(option), children: modeText(option, acp) }, option))) }), _jsx("div", { className: "dwa-preference-copy", children: copy }), mode === 'auto' && (_jsx("div", { className: "dwa-deadline-warn", children: acp.label('请确保你的 agent 值得信任,请谨慎选择。', 'Only pick this if you trust your agent — choose it carefully.') })), override !== undefined && (_jsxs("div", { className: "dwa-deadline-flag", children: ["\u26A0 ", override] }))] }));
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* The duration block: how long to wait before the deadline fires.
|
|
599
|
+
*
|
|
600
|
+
* ONE slider, because there is ONE window: an ask in the conversation and an
|
|
601
|
+
* ask in the settings ledger are the same question, so they get the same
|
|
602
|
+
* answer. The slider's rungs are the windows people actually pick; a window
|
|
603
|
+
* the config file names outside them still runs as configured (the readout
|
|
604
|
+
* prints the exact number and the thumb parks on the nearest rung).
|
|
605
|
+
*
|
|
606
|
+
* `value === undefined` is the `wait` mode: there is no deadline at all, so
|
|
607
|
+
* the scale has nothing to say — the slider is not drawn, and the readout
|
|
608
|
+
* names the state that is actually in force. The stored window is NOT touched
|
|
609
|
+
* by that (the mode write carries only the mode), so switching back restores
|
|
610
|
+
* it.
|
|
611
|
+
*/
|
|
612
|
+
function DeadlineSliderBlock(props) {
|
|
613
|
+
const { acp, value, mode, override } = props;
|
|
614
|
+
// 不等待 belongs to `auto`. Under `abort` the window floors to the shortest
|
|
615
|
+
// real wait, so the card never shows a zero it cannot run.
|
|
616
|
+
const effective = deadlineValueFor(mode, value);
|
|
617
|
+
const ladder = deadlineLadder(mode);
|
|
618
|
+
const index = effective === undefined ? 0 : deadlineSliderIndex(effective, mode);
|
|
619
|
+
const copy = effective === undefined
|
|
620
|
+
? acp.label('选择「一直等待」时不设超时时长。', '“Wait” sets no deadline.')
|
|
621
|
+
: effective <= 0
|
|
622
|
+
? acp.label('不设等待时间,请求到达即按上方所选的方式处理。', 'No wait: the ask is settled on arrival, by whatever you picked above.')
|
|
623
|
+
: acp.label('超过该时长仍未应答,将按上方所选的方式处理。', 'Past this, whatever you picked above is what happens.');
|
|
624
|
+
return (_jsxs("div", { className: "dwa-preference-block", children: [_jsx("div", { className: "dwa-preference-label", children: acp.label('等待时长', 'How long to wait') }), _jsx("div", { className: "dwa-deadline-value", children: effective === undefined ? acp.label('一直等待', 'Always') : deadlineText(effective, acp) }), effective !== undefined && (_jsxs(_Fragment, { children: [_jsx("input", { className: "dwa-deadline-range", type: "range", min: 0, max: ladder.length - 1, step: 1, value: index, "aria-label": acp.label('等待时长', 'How long to wait'), onChange: event => {
|
|
625
|
+
const next = ladder[Number(event.target.value)];
|
|
626
|
+
if (next !== undefined && next !== effective)
|
|
627
|
+
void acp.setApprovalDeadline(next);
|
|
628
|
+
} }), _jsx("div", { className: "dwa-deadline-ticks", children: ladder.map(rung => (_jsx("span", { children: deadlineText(rung, acp) }, rung))) })] })), _jsx("div", { className: "dwa-preference-copy", children: copy }), override !== undefined && (_jsxs("div", { className: "dwa-deadline-flag", children: ["\u26A0 ", override] }))] }));
|
|
473
629
|
}
|
|
474
630
|
/**
|
|
475
|
-
*
|
|
476
|
-
*
|
|
631
|
+
* The facts the card has to show, decided from what the host reported.
|
|
632
|
+
*
|
|
633
|
+
* The card carries TWO controls, and they are orthogonal: one window (how
|
|
634
|
+
* long) and one mode (what happens). An override can sit on either of them,
|
|
635
|
+
* so each is decided on its own.
|
|
477
636
|
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
* shows, so a value the host trims never lingers in the UI.
|
|
637
|
+
* `overridden` is what gates the reset button. A baseline that AGREES with the
|
|
638
|
+
* running value is not an override, and offering a reset for it would be a
|
|
639
|
+
* button that does nothing (observed live).
|
|
482
640
|
*/
|
|
483
|
-
function
|
|
484
|
-
const
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
641
|
+
export function approvalDeadlineFacts(deadlines) {
|
|
642
|
+
const configured = deadlines.configured?.permissionTimeoutMs;
|
|
643
|
+
const baseline = configured === undefined || configured === deadlines.permissionTimeoutMs
|
|
644
|
+
? undefined
|
|
645
|
+
: configured;
|
|
646
|
+
const modeBaseline = deadlines.configuredMode !== undefined && deadlines.configuredMode !== deadlines.mode
|
|
647
|
+
? deadlines.configuredMode
|
|
648
|
+
: undefined;
|
|
649
|
+
return {
|
|
650
|
+
baseline,
|
|
651
|
+
modeBaseline,
|
|
652
|
+
overridden: baseline !== undefined || modeBaseline !== undefined,
|
|
495
653
|
};
|
|
496
|
-
return (_jsxs("div", { className: "dwa-preference-row", children: [_jsxs("div", { children: [_jsx("div", { className: "dwa-preference-label", children: label }), _jsx("div", { className: "dwa-preference-copy", children: copy })] }), _jsxs("div", { className: "dwa-deadline-controls", children: [_jsx("div", { className: "dwa-seg", children: DEADLINE_PRESETS.map(preset => (_jsx("button", { type: "button", className: value === preset ? 'is-on' : '', title: deadlineText(preset, acp), onClick: () => void acp.setApprovalDeadline(field, preset), children: deadlineText(preset, acp) }, preset))) }), _jsx("input", { className: "dwa-input dwa-deadline-input", type: "text", inputMode: "numeric", "aria-label": label, "aria-invalid": parsed === undefined, value: text, onChange: event => setDraft(event.target.value), onBlur: commit, onKeyDown: event => { if (event.key === 'Enter')
|
|
497
|
-
commit(); } }), _jsx("span", { className: "dwa-hint dwa-deadline-unit", children: acp.label('秒', 'sec') })] })] }));
|
|
498
654
|
}
|
|
499
655
|
/**
|
|
500
656
|
* The approval-deadline card.
|
|
501
657
|
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
658
|
+
* TWO controls, and they are orthogonal: one window (how long) and one mode
|
|
659
|
+
* (what happens). One window covers both places an ask can wait — the
|
|
660
|
+
* conversation's approval card and the settings panel's ledger — because they
|
|
661
|
+
* ask the same question and the answer should not differ by where the card
|
|
662
|
+
* happens to be drawn.
|
|
663
|
+
*
|
|
664
|
+
* The two controls are ONE system, and the card says so in a sentence: the
|
|
665
|
+
* window says how long to wait, the mode says what happens when nobody
|
|
666
|
+
* answered. That sentence is the thing the previous three-row layout kept
|
|
667
|
+
* missing.
|
|
509
668
|
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
669
|
+
* The facts that make a number here meaningless are shown, not swallowed: the
|
|
670
|
+
* deployment baseline this card outranks. Each rides on the control it belongs
|
|
671
|
+
* to, and the reset button appears only when something is actually being
|
|
672
|
+
* outranked.
|
|
513
673
|
*/
|
|
514
674
|
function ApprovalDeadlinesCard(props) {
|
|
515
675
|
const { acp, ui } = props;
|
|
@@ -517,23 +677,17 @@ function ApprovalDeadlinesCard(props) {
|
|
|
517
677
|
// Older host half: no card rather than a number this browser invented.
|
|
518
678
|
if (deadlines === undefined)
|
|
519
679
|
return null;
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
};
|
|
532
|
-
// An override is in force exactly when a note says so — a configured baseline
|
|
533
|
-
// that AGREES with the running value is not an override, and offering a reset
|
|
534
|
-
// for it would be a button that does nothing (observed live).
|
|
535
|
-
const overridden = DEADLINE_FIELDS.some(field => overrideNote(field) !== undefined);
|
|
536
|
-
return (_jsxs("section", { className: "dwa-section", children: [_jsx("div", { className: "dwa-section-heading", children: _jsxs("div", { children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('审批时限', 'Approval deadlines') }), _jsx("p", { className: "dwa-section-copy", children: acp.label('没人答复的权限询问多久后由插件接手。两者都不是替你批准:撤回后会改到设置页再问一次,代答前会先发一条说明。', 'How long an unanswered permission ask waits before the plugin takes over. Neither approves on your behalf: a withdrawal re-asks in Settings, and an auto-answer announces itself first.') })] }) }), _jsxs("div", { className: "dwa-card", style: { paddingTop: 4, paddingBottom: 4 }, children: [_jsx(DeadlineRow, { acp: acp, field: "nativeApprovalTimeoutMs", label: acp.label('对话审批撤回窗口', 'Composer approval withdrawal'), copy: acp.label('宿主把询问接进对话后,多久没出现就撤回并改到设置页问。会渲染的接管是立即出现的,所以这个期限只为「收了却不渲染」而响。', 'How long an ask the host took into the composer may stay invisible before it is withdrawn and re-asked in Settings. A takeover that renders does so immediately, so this only fires for one that never showed up.'), value: deadlines.nativeApprovalTimeoutMs }), overrideNote('nativeApprovalTimeoutMs') !== undefined && (_jsx("div", { className: "dwa-hint", style: { paddingBottom: 8 }, children: overrideNote('nativeApprovalTimeoutMs') })), _jsx(DeadlineRow, { acp: acp, field: "permissionFallbackTimeoutMs", label: acp.label('降级询问代答窗口', 'Degraded-ask auto-answer'), copy: acp.label('询问没能进入对话时(审批服务不可用,或已被撤回),多久后答复 cancelled。', 'How long an ask that never reached the conversation (approval service unavailable, or withdrawn above) waits before it is answered cancelled.'), value: deadlines.permissionFallbackTimeoutMs }), overrideNote('permissionFallbackTimeoutMs') !== undefined && (_jsx("div", { className: "dwa-hint", style: { paddingBottom: 8 }, children: overrideNote('permissionFallbackTimeoutMs') })), deadlines.permissionTimeoutMs !== undefined && (_jsx("div", { className: "dwa-hint", style: { paddingBottom: 8 }, children: acp.label(`注意:cordis.patch.yml 里 permissionTimeoutMs = ${deadlineText(deadlines.permissionTimeoutMs, acp)},它一视同仁,覆盖上面两个窗口。`, `Note: cordis.patch.yml sets permissionTimeoutMs = ${deadlineText(deadlines.permissionTimeoutMs, acp)}, which governs every path alike and overrides both windows above.`) })), overridden && (_jsxs("div", { className: "dwa-preference-row", children: [_jsx("div", { children: _jsx("div", { className: "dwa-preference-copy", children: acp.label('设置页的值写在插件自己的状态文件里,重启后仍然有效;重置后回到 cordis.patch.yml 的配置。', 'A value set here lives in the plugin\'s own state file and survives a restart; resetting returns to whatever cordis.patch.yml asks for.') }) }), _jsx("button", { type: "button", className: "dwa-btn", onClick: () => void acp.resetApprovalDeadlines(), children: acp.label('恢复配置文件的值', 'Use config file value') })] }))] })] }));
|
|
680
|
+
const facts = approvalDeadlineFacts(deadlines);
|
|
681
|
+
const baselineNote = facts.baseline === undefined
|
|
682
|
+
? undefined
|
|
683
|
+
: acp.label(`配置文件(cordis.patch.yml)设为 ${deadlineText(facts.baseline, acp)},此处设置优先。`, `The config file (cordis.patch.yml) asks for ${deadlineText(facts.baseline, acp)}; the value set here wins.`);
|
|
684
|
+
const modeNote = facts.modeBaseline === undefined
|
|
685
|
+
? undefined
|
|
686
|
+
: acp.label(`配置文件(cordis.patch.yml)设为「${modeText(facts.modeBaseline, acp)}」,此处设置优先。`, `The config file (cordis.patch.yml) asks for "${modeText(facts.modeBaseline, acp)}"; the value set here wins.`);
|
|
687
|
+
// In `wait` mode there is no deadline at all, so the slider shows the one
|
|
688
|
+
// thing that is true: no deadline.
|
|
689
|
+
const waitMode = deadlines.mode === 'wait';
|
|
690
|
+
return (_jsxs("section", { className: "dwa-section", children: [_jsx("div", { className: "dwa-section-heading", children: _jsxs("div", { children: [_jsx("h3", { className: "dwa-section-title", children: acp.label('审批时限', 'Approval deadlines') }), _jsx("p", { className: "dwa-section-copy", children: acp.label('权限请求的等待时长与超时处理方式。', 'How long to wait for a permission request, and what happens when nobody answers.') })] }) }), _jsxs("div", { className: "dwa-card", style: { paddingTop: 4, paddingBottom: 4 }, children: [_jsx(DeadlineModeBlock, { acp: acp, mode: deadlines.mode, override: modeNote }), _jsx(DeadlineSliderBlock, { acp: acp, value: waitMode ? undefined : deadlines.permissionTimeoutMs, mode: deadlines.mode, override: baselineNote }), facts.overridden && (_jsx("div", { className: "dwa-deadline-reset", children: _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", onClick: () => void acp.resetApprovalDeadlines(), children: acp.label('恢复配置文件的值', 'Use config file value') }) }))] })] }));
|
|
537
691
|
}
|
|
538
692
|
/** Settings configuration section for ACP agents. */
|
|
539
693
|
export function AcpSettingsSection(props) {
|
|
@@ -549,10 +703,10 @@ export function AcpSettingsSection(props) {
|
|
|
549
703
|
const [createNonce, setCreateNonce] = useState(0);
|
|
550
704
|
const { configured, available } = partitionAgents(ui.detectedAgents, ui.agents);
|
|
551
705
|
const selected = addTarget ?? available[0] ?? NEW_AGENT_SEED;
|
|
552
|
-
return (_jsxs("div", { className: "dwa-root", children: [_jsxs("header", { className: "dwa-page-header", children: [_jsxs("div", { className: "dwa-page-heading", children: [_jsx("h2", { className: "dwa-page-title", children: acp.label('NTR 智能体', 'NTR Agents') }), _jsx("p", { className: "dwa-page-description", children: acp.label('管理智能体连接、可用模型与对话显示偏好。', 'Manage agent connections, available models, and conversation display preferences.') }), _jsxs("div", { className: "dwa-page-status", children: [_jsx("span", { className: "dwa-dot", style: { background: ui.stream === 'live' ? '#22c55e' : ui.stream === 'down' ? '#ef4444' : '#9ca3af' }, title: ui.stream }), _jsx("span", { children: streamText(ui.stream, acp) }), ui.detectSummary !== undefined && _jsxs("span", { children: ["\u00B7 ", ui.detectSummary] })] })] }), _jsx("div", { className: "dwa-page-actions", children: _jsx("button", { className: "dwa-btn", onClick: () => {
|
|
706
|
+
return (_jsxs("div", { className: "dwa-root", children: [_jsxs("header", { className: "dwa-page-header", children: [_jsxs("div", { className: "dwa-page-heading", children: [_jsx("h2", { className: "dwa-page-title", children: acp.label('NTR 智能体', 'NTR Agents') }), _jsx("p", { className: "dwa-page-description", children: acp.label('管理智能体连接、可用模型与对话显示偏好。', 'Manage agent connections, available models, and conversation display preferences.') }), _jsxs("div", { className: "dwa-page-status", children: [_jsx("span", { className: "dwa-dot", style: { background: ui.stream === 'live' ? 'var(--dsw-alias-state-success-primary, #22c55e)' : ui.stream === 'down' ? 'var(--dsw-alias-state-error-primary, #ef4444)' : 'var(--dsw-alias-state-idle-primary, #9ca3af)' }, title: ui.stream }), _jsx("span", { children: streamText(ui.stream, acp) }), ui.detectSummary !== undefined && _jsxs("span", { children: ["\u00B7 ", ui.detectSummary] })] })] }), _jsx("div", { className: "dwa-page-actions", children: _jsx("button", { className: "dwa-btn", onClick: () => {
|
|
553
707
|
void acp.resync();
|
|
554
708
|
void acp.detectAgents();
|
|
555
|
-
}, disabled: ui.detecting, children: ui.detecting ? acp.label('检测中...', 'Detecting...') : acp.label('重新检测与刷新', 'Re-detect & Refresh') }) })] }), ledger.visible && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16 }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom:
|
|
709
|
+
}, disabled: ui.detecting, children: ui.detecting ? acp.label('检测中...', 'Detecting...') : acp.label('重新检测与刷新', 'Re-detect & Refresh') }) })] }), ledger.visible && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16 }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 4 }, children: acp.label('待处理权限请求', 'Pending Permission Requests') }), _jsx("div", { className: "dwa-hint", style: { marginBottom: 8 }, children: acp.label('对话打开时在那边的卡片上回答;这里兜底显示其他会话的询问。', 'Answer in the conversation card while that conversation is open; this list covers the asks whose conversation is not.') }), ui.permissions.map(permission => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6, flexWrap: 'wrap' }, children: [_jsxs("span", { style: { flex: 1, minWidth: 160 }, children: [_jsx("span", { style: { fontWeight: 600 }, children: permission.toolName }), permission.reason !== undefined && (_jsxs("span", { style: { color: 'var(--ntr-muted)' }, children: [" \u00B7 ", permission.reason] }))] }), permission.options.map(option => (_jsx("button", { className: "dwa-btn dwa-btn-primary", onClick: () => { void acp.respondPermission(permission.requestId, option.optionId).catch(() => undefined); }, children: option.name }, option.optionId))), _jsx("button", { className: "dwa-btn", onClick: () => { void acp.respondPermission(permission.requestId).catch(() => undefined); }, children: acp.label('拒绝', 'Dismiss') })] }, permission.requestId)))] })), ui.notice !== undefined && (_jsxs("div", { className: "dwa-card dwa-card-warn", style: { marginBottom: 16, display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx("span", { style: { flex: 1 }, children: ui.notice }), _jsx("button", { className: "dwa-btn", onClick: () => acp.clearNotice(), children: "\u2715" })] })), ui.preset !== undefined && (_jsxs("div", { className: "dwa-card dwa-mode-banner", children: [_jsxs("div", { className: "dwa-mode-title", children: [acp.label('NTR 模式', 'NTR mode'), _jsx("span", { className: "dwa-badge", children: ui.preset.id })] }), _jsx("span", { className: `dwa-badge ${ui.preset.installed ? 'dwa-badge-success' : 'dwa-badge-warn'}`, children: ui.preset.installed ? acp.label('已就绪', 'Ready') : acp.label('需检查', 'Needs attention') }), _jsxs("div", { className: "dwa-mode-copy", children: [ui.preset.installed
|
|
556
710
|
? acp.label('已加入宿主模式列表,新建会话时选择「NTR 模式」即可使用。', 'Added to the harness roster. Pick “NTR mode” when creating a session.')
|
|
557
711
|
: acp.label('宿主模式列表中没有可用的「NTR 模式」,请检查预设声明。', 'The harness roster has no usable “NTR mode”. Check the preset declaration.'), _jsx("code", { className: "dwa-mode-source dwa-mono", children: ui.preset.source })] }), ui.preset.problem !== undefined && (_jsx("div", { className: "dwa-hint", style: { gridColumn: '1 / -1', color: 'var(--ntr-warn)' }, children: acp.label(`宿主报告:${ui.preset.problem}`, `Harness reported: ${ui.preset.problem}`) }))] })), _jsxs("section", { className: "dwa-section", children: [_jsx("div", { className: "dwa-section-heading", children: _jsxs("div", { children: [_jsxs("h3", { className: "dwa-section-title", children: [acp.label('已接入的智能体', 'Connected agents'), _jsx("span", { className: "dwa-badge", children: configured.length })] }), _jsx("p", { className: "dwa-section-copy", children: acp.label('展开智能体可管理模型、启动命令与工作区。', 'Expand an agent to manage its models, launch command, and workspace.') })] }) }), configured.length === 0 && (_jsx("div", { className: "dwa-card", style: { textAlign: 'center', color: 'var(--ntr-muted)', padding: 20 }, children: acp.label('尚未启用任何 Agent。从下方「添加智能体」点一个检测到的预设(会展开预填命令的创建卡),再点「保存并启用」;也可以直接新建自定义 Agent。', 'No agents configured yet. Pick a detected preset from "Add agent" below (it opens a creation card prefilled with its command), then click "Save & Enable" — or create a custom one from scratch.') })), _jsx("div", { className: "dwa-agent-list", children: configured.map(({ info, synthesized }) => {
|
|
558
712
|
const activeAgent = ui.agents.find(a => a.id === info.id);
|
|
@@ -797,9 +951,9 @@ function AgentDetails({ acp, detected, form, patch, isConfigured, newCustom, for
|
|
|
797
951
|
? acp.label('小写字母或数字开头,可用 a-z 0-9 . _ -,最长 64 字符。', 'Starts with a lowercase letter or digit; a-z 0-9 . _ - allowed; max 64 chars.')
|
|
798
952
|
: acp.label('ID 格式不合法:小写字母或数字开头,可用 a-z 0-9 . _ -。', 'Invalid id: start with a lowercase letter or digit; only a-z 0-9 . _ - allowed.') })] }) })), _jsx("div", { className: "dwa-form-row", children: _jsxs("div", { className: "dwa-form-col", children: [_jsx("label", { className: "dwa-form-label", children: acp.label('显示名', 'Display Name') }), _jsx("input", { className: "dwa-input", value: form.title, onChange: e => patch({ title: e.target.value }), placeholder: detected.name === '' ? acp.label('我的 Agent', 'My Agent') : detected.name })] }) }), _jsx("div", { className: "dwa-form-row", children: _jsxs("div", { className: "dwa-form-col", children: [_jsx("label", { className: "dwa-form-label", children: acp.label('启动命令(程序 + 参数)', 'Launch Command (program + arguments)') }), _jsx("input", { className: "dwa-input dwa-input-mono", value: form.commandLine, onChange: e => patch({ commandLine: e.target.value }), placeholder: detected.defaultCommand === ''
|
|
799
953
|
? 'e.g. codebuddy --acp'
|
|
800
|
-
: formatCommandLine(detected.defaultCommand, detected.defaultArgs) }), _jsx("span", { className: "dwa-hint", children: commandPreview }), _jsx("span", { className: "dwa-hint", children: acp.label('参数直接跟在程序后面即可;路径里含空格时用引号包住整条路径,例如 "C:\\Program Files\\nodejs\\node.exe" --acp。', 'Arguments go right after the program; quote a whole path that contains spaces, e.g. "C:\\Program Files\\nodejs\\node.exe" --acp.') })] }) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, flexWrap: 'wrap' }, children: [_jsxs("button", { type: "button", className: "dwa-btn dwa-btn-secondary", "aria-expanded": advancedOpen, onClick: () => setAdvancedOpen(value => !value), children: [_jsx("span", { className: "dwa-caret", "aria-hidden": true, children: advancedOpen ? '▾' : '▸' }), " ", acp.label('高级参数', 'Advanced Parameters')] }), !advancedOpen && (cwdInvalid || rootsInvalid) && (_jsx("span", { className: "dwa-error", children: acp.label('里面有非法的工作空间路径', 'Contains an invalid workspace path') })), !advancedOpen && mcpInvalid && (_jsx("span", { className: "dwa-error", children: acp.label('里面的 MCP 服务器配置不是合法 JSON', 'Contains an invalid MCP server list') }))] }), _jsxs("div", { style: advancedOpen ? undefined : { display: 'none' }, children: [!newCustom && (_jsxs("div", { className: "dwa-default-hint", children: [_jsxs("div", { children: [_jsx("span", { style: { fontWeight: 600, marginRight: 6 }, children: acp.label('官方测试默认命令:', 'Tested Default Command:') }), _jsxs("code", { className: "dwa-input-mono", style: { background: '#0000000a', padding: '2px 6px', borderRadius: 4 }, children: [detected.defaultCommand, " ", detected.defaultArgs.join(' ')] })] }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", onClick: onUseDefault, title: acp.label('把命令与参数重置为官方验证值', 'Reset command and arguments to the tested default'), children: acp.label('使用预设值', 'Use Preset') })] })), _jsx("div", { className: "dwa-form-row", children: _jsxs("div", { className: "dwa-form-col", children: [_jsx("label", { className: "dwa-form-label", children: acp.label('npm 包名(PATH 中找不到时自动安装)', 'npm Package (auto-installed when missing from PATH)') }), _jsx("input", { className: "dwa-input dwa-input-mono", value: form.npmPackage, onChange: e => patch({ npmPackage: e.target.value }), placeholder: acp.label('原生支持 ACP 的 agent 无需填写', 'Not needed for agents with native ACP support') })] }) }), _jsx(WorkspaceBlock, { acp: acp, form: form, patch: patch, cwdInvalid: cwdInvalid, rootsInvalid: rootsInvalid }), _jsx("div", { className: "dwa-form-row", children: _jsxs("div", { className: "dwa-form-col", children: [_jsx("label", { className: "dwa-form-label", children: acp.label('空闲释放(毫秒)', 'Idle Release (ms)') }), _jsx("input", { className: "dwa-input dwa-input-mono", value: form.idleStr, onChange: e => patch({ idleStr: e.target.value }), placeholder: acp.label(`留空 = 宿主默认(承载会话 ${String(DEFAULT_IDLE_RELEASE_MS / 60_000)} 分钟 / 探测进程 ${String(DEFAULT_PROBE_RELEASE_MS / 60_000)} 分钟)`, `Blank = host default (${String(DEFAULT_IDLE_RELEASE_MS / 60_000)} min for a chat process / ${String(DEFAULT_PROBE_RELEASE_MS / 60_000)} min for a probe)`) }), _jsx("span", { className: idleInvalid ? 'dwa-error' : 'dwa-hint', children: idleInvalid
|
|
954
|
+
: formatCommandLine(detected.defaultCommand, detected.defaultArgs) }), _jsx("span", { className: "dwa-hint", children: commandPreview }), _jsx("span", { className: "dwa-hint", children: acp.label('参数直接跟在程序后面即可;路径里含空格时用引号包住整条路径,例如 "C:\\Program Files\\nodejs\\node.exe" --acp。', 'Arguments go right after the program; quote a whole path that contains spaces, e.g. "C:\\Program Files\\nodejs\\node.exe" --acp.') })] }) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, flexWrap: 'wrap' }, children: [_jsxs("button", { type: "button", className: "dwa-btn dwa-btn-secondary", "aria-expanded": advancedOpen, onClick: () => setAdvancedOpen(value => !value), children: [_jsx("span", { className: "dwa-caret", "aria-hidden": true, children: advancedOpen ? '▾' : '▸' }), " ", acp.label('高级参数', 'Advanced Parameters')] }), !advancedOpen && (cwdInvalid || rootsInvalid) && (_jsx("span", { className: "dwa-error", children: acp.label('里面有非法的工作空间路径', 'Contains an invalid workspace path') })), !advancedOpen && mcpInvalid && (_jsx("span", { className: "dwa-error", children: acp.label('里面的 MCP 服务器配置不是合法 JSON', 'Contains an invalid MCP server list') }))] }), _jsxs("div", { style: advancedOpen ? undefined : { display: 'none' }, children: [!newCustom && (_jsxs("div", { className: "dwa-default-hint", children: [_jsxs("div", { children: [_jsx("span", { style: { fontWeight: 600, marginRight: 6 }, children: acp.label('官方测试默认命令:', 'Tested Default Command:') }), _jsxs("code", { className: "dwa-input-mono", style: { background: 'var(--dsw-alias-markdown-inline-code, #0000000a)', padding: '2px 6px', borderRadius: 4 }, children: [detected.defaultCommand, " ", detected.defaultArgs.join(' ')] })] }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", onClick: onUseDefault, title: acp.label('把命令与参数重置为官方验证值', 'Reset command and arguments to the tested default'), children: acp.label('使用预设值', 'Use Preset') })] })), _jsx("div", { className: "dwa-form-row", children: _jsxs("div", { className: "dwa-form-col", children: [_jsx("label", { className: "dwa-form-label", children: acp.label('npm 包名(PATH 中找不到时自动安装)', 'npm Package (auto-installed when missing from PATH)') }), _jsx("input", { className: "dwa-input dwa-input-mono", value: form.npmPackage, onChange: e => patch({ npmPackage: e.target.value }), placeholder: acp.label('原生支持 ACP 的 agent 无需填写', 'Not needed for agents with native ACP support') })] }) }), _jsx(WorkspaceBlock, { acp: acp, form: form, patch: patch, cwdInvalid: cwdInvalid, rootsInvalid: rootsInvalid }), _jsx("div", { className: "dwa-form-row", children: _jsxs("div", { className: "dwa-form-col", children: [_jsx("label", { className: "dwa-form-label", children: acp.label('空闲释放(毫秒)', 'Idle Release (ms)') }), _jsx("input", { className: "dwa-input dwa-input-mono", value: form.idleStr, onChange: e => patch({ idleStr: e.target.value }), placeholder: acp.label(`留空 = 宿主默认(承载会话 ${String(DEFAULT_IDLE_RELEASE_MS / 60_000)} 分钟 / 探测进程 ${String(DEFAULT_PROBE_RELEASE_MS / 60_000)} 分钟)`, `Blank = host default (${String(DEFAULT_IDLE_RELEASE_MS / 60_000)} min for a chat process / ${String(DEFAULT_PROBE_RELEASE_MS / 60_000)} min for a probe)`) }), _jsx("span", { className: idleInvalid ? 'dwa-error' : 'dwa-hint', children: idleInvalid
|
|
801
955
|
? acp.label('必须是 0(永不释放)或 [1000, 86400000] 之间的整数毫秒。', 'Must be 0 (never release) or an integer in [1000, 86400000] ms.')
|
|
802
|
-
: acp.label('
|
|
956
|
+
: (_jsxs(_Fragment, { children: [acp.label('空闲超过此时长就挂起进程(会话保留,下一条消息自动恢复)。', 'The process is parked once it has been idle this long (the session is kept and resumes on the next message).'), _jsx("br", {}), acp.label('0 = 永不释放;回合在跑、取消未结算或审批待答时不释放。', '0 = never; a process with a running turn, an unsettled cancel, or a pending approval is never released.'), _jsx("br", {}), acp.label('只对承载过会话的进程生效;探测进程固定 5 分钟释放,填 0 也拦不住。', 'This window applies only to a process that has hosted a session; the model-probe process is always released after 5 minutes, and 0 does not stop it.')] })) })] }) }), _jsxs("div", { className: "dwa-form-row", style: { flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, children: [_jsx("span", { className: "dwa-form-label", children: acp.label('环境变量', 'Environment Variables') }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", onClick: () => patch({ envRows: [...form.envRows, { key: '', value: '' }] }), children: acp.label('添加', 'Add') })] }), form.envRows.map((row, index) => (_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center' }, children: [_jsx("input", { className: "dwa-input dwa-input-mono", style: { flex: 2 }, value: row.key, onChange: e => patch({ envRows: form.envRows.map((r, i) => (i === index ? { ...r, key: e.target.value } : r)) }), placeholder: "KEY" }), _jsx("input", { className: "dwa-input dwa-input-mono", style: { flex: 3 }, value: row.value, onChange: e => patch({ envRows: form.envRows.map((r, i) => (i === index ? { ...r, value: e.target.value } : r)) }), placeholder: "VALUE" }), _jsx("button", { type: "button", className: "dwa-btn", title: acp.label('删除该变量', 'Remove this variable'), onClick: () => patch({ envRows: form.envRows.filter((_, i) => i !== index) }), children: "\u2715" })] }, index))), form.envRows.length === 0 && (_jsx("span", { className: "dwa-hint", children: acp.label('无自定义变量;子进程继承宿主环境。', 'No custom variables; the child inherits the host environment.') }))] }), _jsxs("div", { className: "dwa-form-row", style: { flexDirection: 'column', gap: 6 }, children: [_jsx("span", { className: "dwa-form-label", children: acp.label('MCP 服务器(JSON)', 'MCP Servers (JSON)') }), _jsx("textarea", { className: "dwa-input dwa-input-mono", style: { minHeight: 96, resize: 'vertical' }, value: form.mcpText, onChange: e => patch({ mcpText: e.target.value }), spellCheck: false, placeholder: '[\n { "name": "example", "transport": "stdio", "command": "/abs/path/to/server", "args": ["--mcp"] }\n]' }), mcpInvalid && (_jsx("span", { className: "dwa-error", children: acp.label('必须是一个 JSON 数组;每个条目写明 name 与 transport。', 'Must be a JSON array; every entry needs a name and a transport.') })), _jsx("span", { className: "dwa-hint", children: acp.label('这些 MCP 服务器会在建会话时交给 agent,由 agent 自己拉起并使用(stdio 需填 command,可选 args / env;http 与 sse 需填 url,可选 headers)。agent 自己配置的 MCP 服务器不受影响,这里只是额外追加。', 'These MCP servers are handed to the agent when a session is created; the agent launches and uses them itself (stdio needs command, with optional args / env; http and sse need url, with optional headers). The MCP servers the agent configures for itself are unaffected — this only adds more.') })] })] }), _jsxs("div", { className: "dwa-form-actions", children: [_jsx("button", { className: "dwa-btn", onClick: onTest, disabled: testing || saving || formIncomplete, title: acp.label('用当前表单值拉起一次性进程并完成 ACP 握手,不保存配置', 'Spawns a throwaway process from the form values and completes the ACP handshake; nothing is saved'), children: testing ? acp.label('测试中...', 'Testing...') : acp.label('测试连接', 'Test Connection') }), _jsx("button", { className: "dwa-btn dwa-btn-primary", onClick: onSave, disabled: saving || testing || formIncomplete, children: saving
|
|
803
957
|
? acp.label('保存中...', 'Saving...')
|
|
804
958
|
: savedSuccess
|
|
805
959
|
? acp.label('✓ 已保存', '✓ Saved')
|
|
@@ -810,7 +964,7 @@ function AgentDetails({ acp, detected, form, patch, isConfigured, newCustom, for
|
|
|
810
964
|
fontSize: 12,
|
|
811
965
|
whiteSpace: 'pre-wrap',
|
|
812
966
|
wordBreak: 'break-all',
|
|
813
|
-
color: testResult.ok ? '
|
|
967
|
+
color: testResult.ok ? 'var(--ntr-success)' : 'var(--ntr-danger)',
|
|
814
968
|
}, children: [testResult.ok ? '✓ ' : '✕ ', testResult.text] }))] }));
|
|
815
969
|
}
|
|
816
970
|
/**
|
|
@@ -1067,7 +1221,7 @@ function ModelCatalogBlock({ agentId, detected, acp, active, exposed, onExposedC
|
|
|
1067
1221
|
: acp.label('该 Agent 未暴露模型选择器(category: model)。', 'This agent exposes no model selector (category: model).');
|
|
1068
1222
|
return (_jsxs("div", { children: [!isDefault && (_jsx("div", { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }, children: _jsx("button", { className: "dwa-btn", onClick: () => void acp.setDefaultAgent(agentId), children: acp.label('设为默认智能体', 'Set as default agent') }) })), isDefault && (
|
|
1069
1223
|
// This default belongs to the plugin, not the deployment model route.
|
|
1070
|
-
_jsx("div", { className: "dwa-hint", style: { marginTop: 8 }, children: acp.label('当前为默认智能体:新的 NTR 会话由它服务,不影响其他模式的默认模型。', "This is the default agent for new NTR sessions; other modes' default models are untouched.") })), _jsxs("div", { className: "dwa-inline-setting", style: { marginTop: 12 }, children: [_jsxs("label", { className: "dwa-switch", title: acp.label('Agent 新发布的模型会自动加入右侧已选列表;首次检测到的目录只作为基线,不会一次性全选。', 'Models the agent newly publishes are added to the selected list; the first observed catalog is only a baseline and is not selected wholesale.'), children: [_jsx("input", { type: "checkbox", checked: autoDiscover, onChange: e => void toggleAutoDiscover(e.target.checked), style: { margin: 0, accentColor: '#2563eb' } }), acp.label('自动发现新模型', 'Auto-discover new models')] }), _jsx("span", { className: "dwa-setting-copy", children: acp.label('新增模型自动加入已选列表,现有勾选不受影响。', 'New models join the selected list automatically; existing picks are untouched.') })] }), saveError !== undefined && _jsx("div", { className: "dwa-error", style: { marginTop: 4 }, children: saveError }), probeError !== undefined && (_jsxs("div", { className: "dwa-error", style: { marginTop: 8, wordBreak: 'break-word' }, children: ["\u26A0 ", acp.label('模型检测失败:', 'Model detection failed: '), probeError] })), _jsxs("div", { className: "dwa-model-section", children: [_jsxs("div", { className: "dwa-model-toolbar", children: [_jsxs("span", { className: "dwa-form-label", children: [acp.label('对话中可见的模型', 'Models offered in the conversation'), _jsx("span", { className: "dwa-hint", style: { marginLeft: 6, fontWeight: 400 }, title: acp.label('NTR 模式下的对话只列出这里勾选的模型;未勾选的仍可用——正在生效的那个也会以「· 当前」列出来,其余不列出。', 'The NTR surfaces only list what is checked here; unchecked models still work, and the one currently in force is still listed with a "· current" row — the rest are not offered.'), children: acp.label('(NTR 座位 / 芯片的下拉列表)', '(the NTR seat and chip pickers)') })] }), _jsx("span", { className: "dwa-badge dwa-badge-info", children: selectionBadge }), catalogStatus !== undefined && _jsx("span", { className: "dwa-hint dwa-model-status", children: catalogStatus }), _jsxs("div", { className: "dwa-model-toolbar-actions", children: [_jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", disabled: probing, onClick: () => void detect(), title: acp.label('重新读取 Agent 的模型目录:新增的加入左侧,已下架的从左侧移除;已选列表不受影响。', 'Re-read the agent catalog: newly published models join the left list, withdrawn ones drop out. The selection is untouched.'), children: probing ? acp.label('模型检测中...', 'Probing models...') : acp.label('检测可用模型', 'Detect available models') }), _jsx("input", { className: "dwa-input dwa-model-search", value: search, onChange: e => setSearch(e.target.value), placeholder: acp.label('搜索模型…', 'Search models…') })] })] }), _jsxs("div", { className: "dwa-model-transfer", children: [_jsxs("div", { className: `dwa-model-pane${allCollapsed ? ' is-collapsed' : ''}`, children: [_jsxs("div", { className: "dwa-model-pane-head", children: [_jsx("span", { children: acp.label('全部模型', 'All models') }), result !== undefined && _jsx("span", { className: "dwa-badge", children: modelChoices.length }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", disabled: visibleChoices.length === 0, onClick: addVisible, title: acp.label('把当前搜索范围内的模型全部加入右侧', 'Add every model matching the current search'), children: acp.label('全部加入', 'Add all') }), _jsx("button", { type: "button", className: "dwa-caret-btn", "aria-expanded": !allCollapsed, onClick: () => setAllCollapsed(value => !value), title: allCollapsed ? acp.label('展开全部模型', 'Expand all models') : acp.label('收起全部模型', 'Collapse all models'), children: _jsx("span", { className: "dwa-caret", "aria-hidden": true, children: allCollapsed ? '▸' : '▾' }) })] }), _jsxs("div", { className: "dwa-model-list", children: [visibleChoices.map(choice => {
|
|
1224
|
+
_jsx("div", { className: "dwa-hint", style: { marginTop: 8 }, children: acp.label('当前为默认智能体:新的 NTR 会话由它服务,不影响其他模式的默认模型。', "This is the default agent for new NTR sessions; other modes' default models are untouched.") })), _jsxs("div", { className: "dwa-inline-setting", style: { marginTop: 12 }, children: [_jsxs("label", { className: "dwa-switch", title: acp.label('Agent 新发布的模型会自动加入右侧已选列表;首次检测到的目录只作为基线,不会一次性全选。', 'Models the agent newly publishes are added to the selected list; the first observed catalog is only a baseline and is not selected wholesale.'), children: [_jsx("input", { type: "checkbox", checked: autoDiscover, onChange: e => void toggleAutoDiscover(e.target.checked), style: { margin: 0, accentColor: 'var(--dsw-alias-state-business-primary, #2563eb)' } }), acp.label('自动发现新模型', 'Auto-discover new models')] }), _jsx("span", { className: "dwa-setting-copy", children: acp.label('新增模型自动加入已选列表,现有勾选不受影响。', 'New models join the selected list automatically; existing picks are untouched.') })] }), saveError !== undefined && _jsx("div", { className: "dwa-error", style: { marginTop: 4 }, children: saveError }), probeError !== undefined && (_jsxs("div", { className: "dwa-error", style: { marginTop: 8, wordBreak: 'break-word' }, children: ["\u26A0 ", acp.label('模型检测失败:', 'Model detection failed: '), probeError] })), _jsxs("div", { className: "dwa-model-section", children: [_jsxs("div", { className: "dwa-model-toolbar", children: [_jsxs("span", { className: "dwa-form-label", children: [acp.label('对话中可见的模型', 'Models offered in the conversation'), _jsx("span", { className: "dwa-hint", style: { marginLeft: 6, fontWeight: 400 }, title: acp.label('NTR 模式下的对话只列出这里勾选的模型;未勾选的仍可用——正在生效的那个也会以「· 当前」列出来,其余不列出。', 'The NTR surfaces only list what is checked here; unchecked models still work, and the one currently in force is still listed with a "· current" row — the rest are not offered.'), children: acp.label('(NTR 座位 / 芯片的下拉列表)', '(the NTR seat and chip pickers)') })] }), _jsx("span", { className: "dwa-badge dwa-badge-info", children: selectionBadge }), catalogStatus !== undefined && _jsx("span", { className: "dwa-hint dwa-model-status", children: catalogStatus }), _jsxs("div", { className: "dwa-model-toolbar-actions", children: [_jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", disabled: probing, onClick: () => void detect(), title: acp.label('重新读取 Agent 的模型目录:新增的加入左侧,已下架的从左侧移除;已选列表不受影响。', 'Re-read the agent catalog: newly published models join the left list, withdrawn ones drop out. The selection is untouched.'), children: probing ? acp.label('模型检测中...', 'Probing models...') : acp.label('检测可用模型', 'Detect available models') }), _jsx("input", { className: "dwa-input dwa-model-search", value: search, onChange: e => setSearch(e.target.value), placeholder: acp.label('搜索模型…', 'Search models…') })] })] }), _jsxs("div", { className: "dwa-model-transfer", children: [_jsxs("div", { className: `dwa-model-pane${allCollapsed ? ' is-collapsed' : ''}`, children: [_jsxs("div", { className: "dwa-model-pane-head", children: [_jsx("span", { children: acp.label('全部模型', 'All models') }), result !== undefined && _jsx("span", { className: "dwa-badge", children: modelChoices.length }), _jsx("button", { type: "button", className: "dwa-btn dwa-btn-secondary", disabled: visibleChoices.length === 0, onClick: addVisible, title: acp.label('把当前搜索范围内的模型全部加入右侧', 'Add every model matching the current search'), children: acp.label('全部加入', 'Add all') }), _jsx("button", { type: "button", className: "dwa-caret-btn", "aria-expanded": !allCollapsed, onClick: () => setAllCollapsed(value => !value), title: allCollapsed ? acp.label('展开全部模型', 'Expand all models') : acp.label('收起全部模型', 'Collapse all models'), children: _jsx("span", { className: "dwa-caret", "aria-hidden": true, children: allCollapsed ? '▸' : '▾' }) })] }), _jsxs("div", { className: "dwa-model-list", children: [visibleChoices.map(choice => {
|
|
1071
1225
|
const checked = selected.has(choice.value);
|
|
1072
1226
|
// group 前缀仅来自 agent 返回的 SessionConfigSelectGroup(见 impl.ts
|
|
1073
1227
|
// pushCatalogChoice);供应商可见性兜底靠原样展示 value,严禁从
|