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.
@@ -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 asks are answered in the
7
- * chat composer through the host approval chain. The ledger card answers
8
- * whatever did NOT reach the conversation — a host without the chain, a
9
- * session with no chat identity behind it, or an ask whose native route
10
- * degraded to unavailable (see `permissionLedgerCard`).
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-text-secondary, #6b7280);
46
- --ntr-danger: #dc2626; --ntr-warn: #b45309;
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-button-primary-invert, #fff); border-color: transparent; font-weight: 550; }
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-secondary, #fffbeb); }
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: #22c55e1c; color: #15803d; }
87
- .dwa-badge-warn { background: #f59e0b1f; color: #b45309; }
88
- .dwa-badge-info { background: #3b82f61a; color: #2563eb; }
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-text-primary, #374151); }
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: #3b82f614; color: #2563eb; font-weight: 550; }
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-text-secondary, #4b5563); }
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: #3b82f614; color: #2563eb; }
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-button-primary-invert, #fff); font-weight: 550; }
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-deadline-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
194
- .dwa-deadline-input { width: 76px; min-height: 30px; padding: 4px 8px; text-align: right; }
195
- .dwa-deadline-input[aria-invalid="true"] { border-color: var(--ntr-danger); }
196
- .dwa-deadline-unit { white-space: nowrap; }
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
- * A pending ask ALWAYS gets an answerable card here, whatever `approvalRouted`
440
- * says. That flag reports that the host HAS an approval service — it does not
441
- * promise THIS ask reached the conversation. An ask lands in `ui.permissions`
442
- * only on the ledger path (`AgentBridge.handlePermissionRequest` broadcasts
443
- * `permission/request` there and nowhere else), so a pending entry is by
444
- * definition a question the conversation is not showing. The previous rule
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
- const visible = ui.permissions.length > 0;
454
- return { visible, fellBackFromConversation: visible && ui.approvalRouted };
470
+ return { visible: ui.permissions.length > 0 };
455
471
  }
456
472
  /**
457
- * One-click windows for an approval deadline, in ms. `0` is spelled out rather
458
- * than hidden behind a big number because "never" is the value a cautious user
459
- * picks first, and a preset row that cannot express it would make them type.
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 DEADLINE_PRESETS = [0, 30_000, 60_000, 300_000, 900_000];
462
- /** Both editable windows, for the loops that treat them alike. */
463
- const DEADLINE_FIELDS = ['nativeApprovalTimeoutMs', 'permissionFallbackTimeoutMs'];
464
- /** `5 分钟` / `30 秒` / `关` — the readout and the preset label, one formatter. */
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('关', 'Off');
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
- return acp.label(`${String(minutes)} 分`, `${String(minutes)}m`);
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
- * One editable deadline: presets for the windows people actually pick, plus a
476
- * free-form seconds field for everything else.
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
- * The field is a DRAFT, not the value: it only commits a parseable whole number
479
- * of seconds (0 allowed), and an unparseable draft reverts on blur instead of
480
- * writing garbage the host would refuse. The host answer is what the row then
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 DeadlineRow(props) {
484
- const { acp, field, label, copy, value } = props;
485
- const [draft, setDraft] = useState(undefined);
486
- const text = draft ?? String(Math.round(value / 1000));
487
- const parsed = /^\d+$/.test(text.trim()) ? Number(text.trim()) * 1000 : undefined;
488
- const commit = () => {
489
- const next = parsed;
490
- setDraft(undefined);
491
- // A no-op write is still a write: it is what pins the value into the
492
- // persisted override, which is what makes it survive a restart.
493
- if (next !== undefined && next !== value)
494
- void acp.setApprovalDeadline(field, next);
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
- * Both windows are about the SAME failure — an ask nobody answered — reached
503
- * from two different routes, which is exactly why they need an explanation
504
- * rather than two bare sliders: the first bounds a host that took the question
505
- * into the composer and never rendered it, the second bounds an ask that never
506
- * reached the conversation at all. Neither is an answer on the user's behalf
507
- * being hidden from them: withdrawing re-asks on the ledger, and the fallback
508
- * says what it did in a notice.
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
- * `configured` and `permissionTimeoutMs` are reported by the host only when
511
- * they are actually in play, and both are shown here rather than swallowed:
512
- * a user tuning a number that something else is overriding deserves to be told.
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 configured = deadlines.configured ?? {};
521
- const inForce = {
522
- nativeApprovalTimeoutMs: deadlines.nativeApprovalTimeoutMs,
523
- permissionFallbackTimeoutMs: deadlines.permissionFallbackTimeoutMs,
524
- };
525
- const overrideNote = (field) => {
526
- const baseline = configured[field];
527
- // Equal (or unconfigured) means nothing is being outranked: stay silent.
528
- if (baseline === undefined || baseline === inForce[field])
529
- return undefined;
530
- return acp.label(`cordis.patch.yml 里是 ${deadlineText(baseline, acp)},设置页的值优先。`, `cordis.patch.yml asks for ${deadlineText(baseline, acp)}; the value set here wins.`);
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: 8 }, children: acp.label('待处理权限请求', 'Pending Permission Requests') }), ledger.fellBackFromConversation && (_jsx("div", { className: "dwa-hint", style: { marginBottom: 8 }, children: acp.label('这些询问没有进入对话(审批服务本次不可用、问题从未到达对话,或会话没有对话身份),请在此回答。', 'These asks did not reach the conversation (the approval service was unavailable, the question never reached the conversation, or the session has no chat identity) — answer them here.') })), ui.permissions.map(permission => (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6, flexWrap: 'wrap' }, children: [_jsx("span", { style: { flex: 1, minWidth: 160 }, children: permission.title }), permission.options.map(option => (_jsx("button", { className: "dwa-btn dwa-btn-primary", onClick: () => void acp.respondPermission(permission.requestId, option.optionId), children: option.name }, option.optionId))), _jsx("button", { className: "dwa-btn", onClick: () => void acp.respondPermission(permission.requestId), 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
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('没有进行中的回合或待答审批时,空闲超过此时长就挂起进程(会话保留,下一条消息自动重启并恢复);0 = 永不释放;有回合在跑、取消尚未结算或审批待答的进程不会被释放。此窗口只对承载过会话的进程生效:保存后拉起的模型探测进程(面板显示「已连接」的那个)固定 5 分钟释放,填 0 也拦不住。', 'With no turn in flight or approval awaiting an answer, the process is parked once it has been idle this long (sessions are kept and resume on the next message); 0 = never; a process with a running turn, an unsettled cancel, or a pending approval is never released. This window applies only to a process that has hosted a session: the model-probe process (the one the panel shows as "connected" after a save) 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
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 ? '#16a34a' : 'var(--ntr-danger)',
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,严禁从