dsh-ntr 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -49,6 +49,12 @@ window.__ModuleLoader__.load({
49
49
  const MAX_WATCHED = 6;
50
50
  /** Raw JSON budget for one event block; the cut is marked. */
51
51
  const RAW_MAX = 256e3;
52
+ /**
53
+ * How long a fetched update state stays fresh. The settings panel asks on every
54
+ * mount; without this, every open would hit the registry. A forced check (the
55
+ * card's own button) always goes out.
56
+ */
57
+ const PLUGIN_UPDATE_TTL_MS = 6e5;
52
58
  /** Pretty-printed JSON of an agent message (never throws, cuts only at {@link RAW_MAX} with a marker). */
53
59
  function jsonText(value) {
54
60
  let text;
@@ -262,6 +268,14 @@ window.__ModuleLoader__.load({
262
268
  defaultCommand: "opencode",
263
269
  defaultArgs: ["acp"]
264
270
  },
271
+ {
272
+ id: "cursor",
273
+ name: "Cursor CLI",
274
+ description: "Cursor CLI — run as `agent acp` over stdio (legacy alias `cursor-agent acp`)",
275
+ detected: false,
276
+ defaultCommand: "agent",
277
+ defaultArgs: ["acp"]
278
+ },
265
279
  {
266
280
  id: "qwen",
267
281
  name: "Qwen Code",
@@ -377,6 +391,22 @@ window.__ModuleLoader__.load({
377
391
  cachedModelReads = /* @__PURE__ */ new Set();
378
392
  /** Counts behind the last successful explicit re-detect, so a locale switch can re-render its summary. */
379
393
  detectCounts;
394
+ /** When {@link UiState.pluginUpdate} was last read from the host (epoch ms). */
395
+ pluginUpdateFetchedAt = 0;
396
+ /**
397
+ * The shared in-flight `plugin/update-check`. The settings section mounts
398
+ * twice (both entries) and the update popover asks too; without this, two
399
+ * mounts in the same tick each fire their own registry read.
400
+ */
401
+ pluginUpdateInflight;
402
+ /** Re-entry guard for `plugin/update-apply` (a double click is one install). */
403
+ pluginUpdateApplying = false;
404
+ /**
405
+ * Update read generation. An apply bumps it so a check that was already in
406
+ * flight (started before the install) cannot land afterwards and overwrite
407
+ * the install's verdict with the pre-install `available`.
408
+ */
409
+ pluginUpdateGeneration = 0;
380
410
  constructor(rpc, locale) {
381
411
  this.rpc = rpc;
382
412
  this.locale = locale;
@@ -499,14 +529,15 @@ window.__ModuleLoader__.load({
499
529
  /** Snapshot refresh used on panel open and after a stream outage. */
500
530
  async resync() {
501
531
  try {
502
- const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult] = await Promise.all([
532
+ const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult, versionResult] = await Promise.all([
503
533
  this.call("agents/list"),
504
534
  this.call("sessions/list"),
505
535
  this.call("default/agent").catch(() => ({})),
506
536
  this.call("agents/detect").catch(() => null),
507
537
  this.call("preset/status").catch(() => null),
508
538
  this.call("ui/tag-halo").catch(() => ({ halo: { ...DEFAULT_TAG_HALO } })),
509
- this.call("ui/approval-deadlines").catch(() => ({ deadlines: void 0 }))
539
+ this.call("ui/approval-deadlines").catch(() => ({ deadlines: void 0 })),
540
+ this.call("plugin/version").catch(() => null)
510
541
  ]);
511
542
  const detectedAgents = detectResult && detectResult.agents.length > 0 ? detectResult.agents : this.state.detectedAgents.length > 0 ? this.state.detectedAgents : [...DEFAULT_KNOWN_PRESETS];
512
543
  this.mutate({
@@ -517,7 +548,8 @@ window.__ModuleLoader__.load({
517
548
  detectedAgents,
518
549
  preset: presetResult?.preset ?? void 0,
519
550
  tagHalo: tagHaloResult?.halo ?? { ...DEFAULT_TAG_HALO },
520
- approvalDeadlines: deadlinesResult.deadlines
551
+ approvalDeadlines: deadlinesResult.deadlines,
552
+ pluginVersion: versionResult?.version !== void 0 && versionResult.version !== "" ? versionResult.version : void 0
521
553
  });
522
554
  const active = this.state.activeSessionId ?? sessions[0]?.sessionId;
523
555
  if (active !== void 0) await this.loadTranscript(active);
@@ -525,6 +557,89 @@ window.__ModuleLoader__.load({
525
557
  this.mutate({ notice: String(error instanceof Error ? error.message : error) });
526
558
  }
527
559
  }
560
+ /**
561
+ * Read the plugin's update state (`plugin/update-check`). Throttled to ten
562
+ * minutes unless `force`: the settings panel asks on mount, and every
563
+ * remount must not hit the registry.
564
+ *
565
+ * An older host serves no such endpoint (`unknown endpoint`). That is a
566
+ * normal degradation, not an error worth a notice: the answer is `undefined`
567
+ * and the panel simply keeps its update entry hidden.
568
+ */
569
+ async checkPluginUpdate(force = false) {
570
+ if (this.pluginUpdateInflight !== void 0) return this.pluginUpdateInflight;
571
+ const current = this.state.pluginUpdate;
572
+ if (!force && current !== void 0 && Date.now() - this.pluginUpdateFetchedAt < PLUGIN_UPDATE_TTL_MS) return current;
573
+ const generation = this.pluginUpdateGeneration;
574
+ const inflight = this.readPluginUpdate(generation);
575
+ this.pluginUpdateInflight = inflight;
576
+ try {
577
+ return await inflight;
578
+ } finally {
579
+ this.pluginUpdateInflight = void 0;
580
+ }
581
+ }
582
+ async readPluginUpdate(generation) {
583
+ try {
584
+ const pluginUpdate = await this.call("plugin/update-check");
585
+ if (generation !== this.pluginUpdateGeneration) return void 0;
586
+ this.pluginUpdateFetchedAt = Date.now();
587
+ this.mutate({ pluginUpdate });
588
+ return pluginUpdate;
589
+ } catch {
590
+ return;
591
+ }
592
+ }
593
+ /**
594
+ * Install one exact version (`plugin/update-apply`). The status flips to
595
+ * `applying` first so the badge answers the click; the host's answer then
596
+ * becomes the state.
597
+ *
598
+ * A failed call does NOT prove the install did not happen: on an HMR host
599
+ * the update uninstalls the plugin before reinstalling it, which can cut the
600
+ * response. Whatever the error, the disk may already carry the new version,
601
+ * so the state becomes `restart-required` (the one neutral verdict for "the
602
+ * running code is not the disk's") with `installed` dropped (unknown, not a
603
+ * fact to display) and the notice asks for a reload rather than restoring
604
+ * `available` and pretending nothing changed.
605
+ */
606
+ async applyPluginUpdate(version) {
607
+ if (this.pluginUpdateApplying) return void 0;
608
+ this.pluginUpdateApplying = true;
609
+ this.pluginUpdateGeneration += 1;
610
+ const previous = this.state.pluginUpdate;
611
+ if (previous !== void 0) this.mutate({ pluginUpdate: {
612
+ ...previous,
613
+ status: "applying"
614
+ } });
615
+ try {
616
+ const pluginUpdate = await this.call("plugin/update-apply", { version });
617
+ this.pluginUpdateFetchedAt = Date.now();
618
+ this.mutate({ pluginUpdate });
619
+ return pluginUpdate;
620
+ } catch (error) {
621
+ const message = error instanceof Error ? error.message : String(error);
622
+ const interrupted = previous !== void 0 ? {
623
+ ...previous,
624
+ status: "restart-required",
625
+ installed: void 0,
626
+ message
627
+ } : {
628
+ status: "restart-required",
629
+ current: this.state.pluginVersion ?? "?",
630
+ message
631
+ };
632
+ this.pluginUpdateFetchedAt = 0;
633
+ this.mutate({
634
+ pluginUpdate: interrupted,
635
+ notice: this.locale("更新请求已中断;请刷新页面确认当前版本。", "The update request was interrupted. Reload the page to confirm the installed version.")
636
+ });
637
+ return;
638
+ } finally {
639
+ this.pluginUpdateGeneration += 1;
640
+ this.pluginUpdateApplying = false;
641
+ }
642
+ }
528
643
  async detectAgents() {
529
644
  this.mutate({ detecting: true });
530
645
  try {
@@ -2612,18 +2727,34 @@ window.__ModuleLoader__.load({
2612
2727
  --ntr-success: color-mix(in srgb, var(--dsw-alias-state-success-primary, #16a34a) 72%, var(--dsw-alias-label-primary, #111827));
2613
2728
  --ntr-accent: var(--dsw-alias-button-primary-fill, #2563eb);
2614
2729
  width: 100%; max-width: 960px; box-sizing: border-box; margin: 0 auto;
2615
- padding: 20px; overflow: auto; font-size: var(--ntr-font); line-height: 1.55;
2730
+ padding: 28px 32px; min-width: 0; container-type: inline-size; font-size: var(--ntr-font); line-height: 1.55;
2616
2731
  }
2617
- .dwa-root button, .dwa-root input, .dwa-root select { font-family: inherit; }
2732
+ .dwa-root [hidden] { display: none !important; }
2733
+ .dwa-root button, .dwa-root input, .dwa-root select, .dwa-root textarea { font-family: inherit; }
2734
+ .dwa-root input, .dwa-root textarea { min-width: 0; }
2735
+ .dwa-tabs { display: flex; gap: 24px; flex-wrap: wrap; border-bottom: var(--ntr-line); margin-bottom: 24px; }
2736
+ .dwa-tabs > button { font: inherit; color: var(--ntr-muted); background: transparent; border: 0; border-bottom: 2px solid transparent; padding: 0 0 12px; min-height: 40px; white-space: nowrap; cursor: pointer; }
2737
+ .dwa-tabs > button:hover { color: var(--dsw-alias-label-primary, #111827); }
2738
+ .dwa-tabs > button[aria-pressed="true"] { color: var(--dsw-alias-label-primary, #111827); border-bottom-color: currentColor; font-weight: 550; }
2739
+ .dwa-detail-tabs { margin: 4px 0 20px; gap: 20px; }
2740
+ .dwa-detail-tabs > button { font-size: var(--ntr-font-sm); }
2741
+ .dwa-row-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
2742
+ .dwa-notice { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
2743
+ .dwa-notice > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
2744
+ .dwa-empty { text-align: center; padding: 40px 16px; color: var(--ntr-muted); }
2745
+ .dwa-empty h3 { font-size: 16px; font-weight: 550; color: var(--dsw-alias-label-primary, #111827); margin: 0 0 8px; }
2746
+ .dwa-empty .dwa-btn { margin-top: 20px; }
2747
+ .dwa-health { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 24px; color: var(--ntr-muted); font-size: var(--ntr-font-sm); flex-wrap: wrap; }
2748
+ .dwa-health-status { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
2749
+ .dwa-health-status > span:last-child:not(:nth-child(2)) { padding-left: 12px; border-left: var(--ntr-line); }
2750
+ .dwa-back { margin-bottom: 16px; }
2618
2751
  .dwa-root button:focus-visible, .dwa-root input:focus-visible, .dwa-root select:focus-visible {
2619
2752
  outline: 2px solid var(--ntr-accent); outline-offset: 2px;
2620
2753
  }
2621
- .dwa-page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
2754
+ .dwa-page-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
2622
2755
  .dwa-page-heading { min-width: 0; }
2623
- .dwa-page-title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 650; letter-spacing: -0.02em; }
2756
+ .dwa-page-title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 550; letter-spacing: -0.02em; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
2624
2757
  .dwa-page-description { margin: 5px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-sm); }
2625
- .dwa-page-status { display: flex; align-items: center; gap: 8px; margin-top: 10px; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2626
- .dwa-page-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
2627
2758
  .dwa-btn {
2628
2759
  min-height: 32px; box-sizing: border-box; font: inherit; border-radius: var(--ntr-radius-sm);
2629
2760
  border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff);
@@ -2636,14 +2767,16 @@ window.__ModuleLoader__.load({
2636
2767
  .dwa-btn-primary { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); border-color: transparent; font-weight: 550; }
2637
2768
  .dwa-btn-primary:hover { background: var(--ntr-accent); border-color: transparent; opacity: 0.88; }
2638
2769
  .dwa-btn-secondary { background: var(--dsw-alias-bg-layer-2, #f3f4f6); }
2770
+ .dwa-btn-quiet { background: transparent; border-color: transparent; color: var(--ntr-muted); }
2771
+ .dwa-btn-quiet:hover { border-color: transparent; color: var(--dsw-alias-label-primary, #111827); }
2639
2772
  .dwa-btn-danger { color: var(--ntr-danger); border-color: var(--dsw-alias-state-error-secondary, #fca5a5); }
2640
2773
  .dwa-btn-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, #fee2e2); }
2641
2774
  .dwa-section { margin-bottom: 24px; }
2642
2775
  .dwa-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
2643
- .dwa-section-title { font-size: 14px; font-weight: 650; margin: 0; display: flex; align-items: center; gap: 8px; }
2776
+ .dwa-section-title { font-size: 13px; font-weight: 550; margin: 0; display: flex; align-items: center; gap: 8px; }
2644
2777
  .dwa-section-copy { margin: 3px 0 0; font-size: var(--ntr-font-xs); color: var(--ntr-muted); }
2645
2778
  .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; }
2646
- .dwa-card-warn { border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-tertiary, #fffbeb); }
2779
+ .dwa-card-warn { border-color: color-mix(in srgb, var(--ntr-warn) 30%, var(--dsw-alias-border-l2, #e5e7eb)); background: var(--dsw-alias-state-warn-tertiary, #fffbeb); }
2647
2780
  .dwa-mode-banner { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 5px 16px; align-items: start; margin-bottom: 24px; padding: 14px 16px; }
2648
2781
  .dwa-mode-title { display: flex; align-items: center; gap: 8px; font-weight: 650; }
2649
2782
  .dwa-mode-copy { grid-column: 1 / -1; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
@@ -2652,26 +2785,36 @@ window.__ModuleLoader__.load({
2652
2785
  .dwa-badge-success { background: var(--dsw-alias-state-success-tertiary, #dcfce7); color: var(--ntr-success); }
2653
2786
  .dwa-badge-warn { background: var(--dsw-alias-state-warn-tertiary, #fef3c7); color: var(--ntr-warn); }
2654
2787
  .dwa-badge-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
2788
+ /* The update badge is a button (it expands the update card): strip the
2789
+ browser's default button chrome, keep the badge look. */
2790
+ .dwa-update-badge { border: 0; font-family: inherit; cursor: pointer; }
2791
+ .dwa-update-badge:hover { opacity: 0.85; }
2792
+ /* The version badge doubles as the update entry: same badge look, no chrome. */
2793
+ .dwa-version-btn { border: 0; font-family: inherit; cursor: pointer; }
2794
+ .dwa-version-btn:hover { opacity: 0.85; }
2795
+ .dwa-version { font-family: var(--ds-font-family-code, monospace); }
2796
+ /* The update card sits between the page header and the permission mirror. */
2797
+ .dwa-update-card { margin-bottom: 16px; }
2798
+ .dwa-update-summary { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-weight: 550; }
2799
+ .dwa-update-copy { margin: 6px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2800
+ .dwa-update-message { margin: 6px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); white-space: pre-wrap; overflow-wrap: anywhere; }
2801
+ .dwa-update-actions { margin-top: 10px; }
2655
2802
  .dwa-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
2656
- .dwa-agent-list { display: flex; flex-direction: column; gap: 8px; }
2657
- .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; }
2658
- .dwa-agent-card.is-open { border-color: var(--dsw-alias-border-l1, #c7ccd1); }
2659
- .dwa-agent-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 12px 14px; }
2803
+ .dwa-agent-list { display: flex; flex-direction: column; border-top: var(--ntr-line); }
2804
+ .dwa-agent-card { border-bottom: var(--ntr-line); min-width: 0; }
2805
+ .dwa-agent-row { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; align-items: center; gap: 14px; padding: 16px 0; }
2806
+ .dwa-agent-avatar { display: grid; place-items: center; width: 38px; height: 38px; border: var(--ntr-line); border-radius: 9px; background: var(--dsw-alias-bg-layer-2, #f3f4f6); color: var(--ntr-muted); font: 12px var(--ds-font-family-code, monospace); }
2660
2807
  .dwa-caret { font-size: 14px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; width: 1em; }
2661
2808
  .dwa-caret-btn { font: inherit; border: none; background: var(--dsw-alias-bg-layer-2, #f3f4f6); width: 28px; height: 28px; padding: 0; border-radius: var(--ntr-radius-sm); display: inline-flex; align-items: center; justify-content: center; color: var(--ntr-muted); cursor: pointer; }
2662
2809
  .dwa-caret-btn:hover { background: var(--dsw-alias-interactive-bg-active, #e5e7eb); color: inherit; }
2663
2810
  .dwa-agent-summary { min-width: 0; }
2664
2811
  .dwa-agent-titleline { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
2665
- .dwa-agent-name { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2812
+ .dwa-agent-name { font-size: 13px; font-weight: 550; overflow-wrap: anywhere; }
2666
2813
  .dwa-agent-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 4px; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2667
2814
  .dwa-agent-meta-separator { opacity: 0.45; }
2668
- .dwa-agent-body { border-top: var(--ntr-line); padding: 0 16px 16px; }
2669
- .dwa-subsection { padding-top: 16px; }
2670
- .dwa-subsection + .dwa-subsection { margin-top: 16px; border-top: var(--ntr-line); }
2671
- .dwa-subsection-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
2672
- .dwa-subsection-title { margin: 0; font-size: 13px; font-weight: 650; }
2673
- .dwa-subsection-copy { margin: 3px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2674
- .dwa-form-row { display: flex; gap: var(--ntr-gap-3); margin-bottom: 14px; align-items: flex-start; }
2815
+ .dwa-agent-body { padding: 0 0 24px 52px; }
2816
+ .dwa-subsection { min-width: 0; }
2817
+ .dwa-form-row { display: flex; gap: var(--ntr-gap-3); margin-bottom: 14px; align-items: stretch; }
2675
2818
  .dwa-form-col { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
2676
2819
  .dwa-form-label { font-size: var(--ntr-font-sm); font-weight: 600; color: var(--dsw-alias-label-primary, #374151); }
2677
2820
  .dwa-hint { font-size: var(--ntr-font-xs); line-height: 1.55; color: var(--ntr-muted); }
@@ -2680,11 +2823,13 @@ window.__ModuleLoader__.load({
2680
2823
  .dwa-select { min-height: 32px; font: inherit; font-size: var(--ntr-font-sm); padding: 5px 8px; 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; }
2681
2824
  .dwa-input:hover, .dwa-select:hover { border-color: var(--dsw-alias-border-l1, #9ca3af); }
2682
2825
  .dwa-input:focus, .dwa-select:focus { border-color: var(--ntr-accent); }
2683
- .dwa-input-mono, .dwa-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
2826
+ .dwa-input-mono, .dwa-mono { font-family: var(--ds-font-family-code, ui-monospace), SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
2684
2827
  .dwa-stderr { max-height: 120px; overflow: auto; font-family: monospace; font-size: var(--ntr-font-xs); white-space: pre-wrap; color: var(--ntr-warn); margin: 12px 0 0; }
2685
2828
  .dwa-default-hint { background: var(--dsw-alias-bg-layer-2, #f9fafb); border: var(--ntr-line); border-radius: var(--ntr-radius-sm); padding: 10px 12px; font-size: var(--ntr-font-sm); display: flex; align-items: center; justify-content: space-between; margin: 12px 0 14px; gap: 12px; }
2686
2829
  .dwa-form-actions { display: flex; align-items: center; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: var(--ntr-line); flex-wrap: wrap; }
2687
2830
  .dwa-form-actions .dwa-btn-primary { min-width: 88px; }
2831
+ .dwa-form-actions > .dwa-hint:first-child { margin-right: auto; }
2832
+ .dwa-form-actions > .dwa-hint:last-child:not(:first-child) { width: 100%; }
2688
2833
  .dwa-menu-wrap { position: relative; }
2689
2834
  .dwa-menu-wrap > .dwa-btn { width: 32px; padding: 0; }
2690
2835
  .dwa-menu { position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; min-width: 190px; border: var(--ntr-line); border-radius: var(--ntr-radius-sm); background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); padding: 4px; display: flex; flex-direction: column; }
@@ -2692,10 +2837,10 @@ window.__ModuleLoader__.load({
2692
2837
  .dwa-menu-item:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2693
2838
  .dwa-menu-item-danger { color: var(--ntr-danger); }
2694
2839
  .dwa-menu-confirm { display: flex; align-items: center; gap: 4px; padding: 4px; }
2695
- .dwa-add-panel { border: var(--ntr-line); border-radius: var(--ntr-radius); background: var(--dsw-alias-bg-layer-1, #fff); }
2696
- .dwa-add-toggle { width: 100%; border: 0; background: transparent; color: inherit; padding: 13px 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; text-align: left; cursor: pointer; border-radius: var(--ntr-radius); }
2697
- .dwa-add-toggle:hover { background: var(--dsw-alias-interactive-bg-active, #f9fafb); }
2698
- .dwa-add-content { border-top: var(--ntr-line); padding: 14px 16px 16px; }
2840
+ .dwa-add-panel .dwa-agent-row { display: none; }
2841
+ .dwa-add-panel .dwa-agent-body { padding: 0 0 24px; }
2842
+ .dwa-add-panel .dwa-agent-card { border: 0; }
2843
+ .dwa-add-content { padding-top: 10px; }
2699
2844
  .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; }
2700
2845
  .dwa-chip:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2701
2846
  .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; }
@@ -2727,13 +2872,13 @@ window.__ModuleLoader__.load({
2727
2872
  .dwa-model-status { margin-left: auto; white-space: nowrap; }
2728
2873
  .dwa-model-search { width: 200px; min-height: 30px; }
2729
2874
  .dwa-model-empty { padding: 6px; }
2730
- .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; }
2875
+ .dwa-model-row { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 38px; border: none; background: none; padding: 7px 8px; border-radius: 5px; cursor: pointer; color: inherit; font: inherit; font-size: var(--ntr-font-sm); text-align: left; }
2731
2876
  .dwa-model-row:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2732
2877
  .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); }
2733
2878
  .dwa-model-row-mark { width: 12px; flex: none; text-align: center; opacity: 0.75; }
2734
2879
  .dwa-model-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2735
2880
  /* Name and credit keep their room; the id is the only cell that collapses. */
2736
- .dwa-model-row-desc { flex: 0 0 auto; max-width: 45%; opacity: 0.75; font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2881
+ .dwa-model-row-desc { flex: 0 0 auto; max-width: 45%; opacity: 0.75; font-size: var(--ntr-font-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2737
2882
  /* The trailing 14px is reserved for the fade mask: while the id fits, the
2738
2883
  gradient only covers empty padding; once it overflows, its tail dissolves
2739
2884
  instead of ending in a hard ellipsis. armModelIdMarquee reads the same
@@ -2747,12 +2892,12 @@ window.__ModuleLoader__.load({
2747
2892
  }
2748
2893
  .dwa-model-row-remove { flex: none; opacity: 0.5; padding: 0 2px; }
2749
2894
  .dwa-model-row:hover .dwa-model-row-remove { opacity: 1; }
2750
- .dwa-seg { display: inline-flex; border-radius: var(--ntr-radius-sm); border: 1px solid var(--dsw-alias-border-l2, #d1d5db); overflow: hidden; vertical-align: middle; }
2751
- .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; }
2752
- .dwa-seg > button + button { border-left: 1px solid var(--dsw-alias-border-l2, #d1d5db); }
2895
+ .dwa-seg { display: inline-flex; gap: 4px; padding: 4px; box-sizing: border-box; border-radius: var(--ntr-radius-sm); border: var(--ntr-line); background: var(--dsw-alias-bg-layer-2, #f3f4f6); vertical-align: middle; }
2896
+ .dwa-seg > button { min-height: 32px; font: inherit; font-size: var(--ntr-font-sm); color: var(--ntr-muted); border: 1px solid transparent; border-radius: 4px; background: transparent; padding: 4px 12px; cursor: pointer; white-space: nowrap; }
2753
2897
  .dwa-seg > button:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2754
- .dwa-seg > button.is-on { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); font-weight: 550; }
2755
- .dwa-preference-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 0; }
2898
+ .dwa-seg > button.is-on { background: var(--dsw-alias-bg-layer-1, #fff); border: var(--ntr-line); color: var(--dsw-alias-label-primary, #111827); font-weight: 550; }
2899
+ .dwa-preferences { border-top: var(--ntr-line); }
2900
+ .dwa-preference-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 20px 0; }
2756
2901
  .dwa-preference-row + .dwa-preference-row { border-top: var(--ntr-line); }
2757
2902
  /* A control that needs the whole card: the label sits above it, not beside
2758
2903
  it. A right-aligned segmented control squeezed its labels into two lines
@@ -2777,13 +2922,20 @@ window.__ModuleLoader__.load({
2777
2922
  .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; }
2778
2923
  .dwa-deadline-reset { display: flex; justify-content: flex-end; margin-top: 8px; }
2779
2924
  @media (max-width: 640px) {
2780
- .dwa-root { padding: 14px; }
2781
- .dwa-page-header { flex-direction: column; }
2782
- .dwa-page-actions { width: 100%; }
2783
- .dwa-page-actions .dwa-btn { width: 100%; }
2925
+ .dwa-root { padding: 20px 16px; }
2926
+ }
2927
+ @container (max-width: 640px) {
2928
+ .dwa-page-header { align-items: flex-start; flex-wrap: wrap; }
2929
+ .dwa-agent-body { padding-left: 0; }
2930
+ .dwa-agent-row { grid-template-columns: 32px minmax(0, 1fr) auto; gap: 10px; }
2931
+ .dwa-agent-avatar { width: 32px; height: 32px; }
2932
+ .dwa-section-heading { flex-wrap: wrap; }
2933
+ .dwa-tabs { gap: 20px; }
2934
+ .dwa-notice { flex-wrap: wrap; }
2935
+ .dwa-seg-wide > button { padding-inline: 5px; }
2784
2936
  .dwa-model-toolbar-actions { width: 100%; }
2785
2937
  .dwa-model-search { flex: 1 1 auto; width: auto; }
2786
- .dwa-subsection-head, .dwa-preference-row { align-items: flex-start; flex-direction: column; }
2938
+ .dwa-preference-row { align-items: flex-start; flex-direction: column; }
2787
2939
  .dwa-default-hint { align-items: flex-start; flex-direction: column; }
2788
2940
  /* The slider's scale: up to eight rungs with full unit words ("15 分钟")
2789
2941
  need a smaller face before they can collide on a narrow panel. The rung
@@ -3271,11 +3423,7 @@ window.__ModuleLoader__.load({
3271
3423
  children: acp.label("权限请求的等待时长与超时处理方式。", "How long to wait for a permission request, and what happens when nobody answers.")
3272
3424
  })] })
3273
3425
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3274
- className: "dwa-card",
3275
- style: {
3276
- paddingTop: 4,
3277
- paddingBottom: 4
3278
- },
3426
+ className: "dwa-preferences",
3279
3427
  children: [
3280
3428
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeadlineModeBlock, {
3281
3429
  acp,
@@ -3301,17 +3449,161 @@ window.__ModuleLoader__.load({
3301
3449
  })]
3302
3450
  });
3303
3451
  }
3304
- /** Settings configuration section for ACP agents. */
3452
+ /** The states that carry an update badge next to the version badge. */
3453
+ function updateBadgeText(update, acp) {
3454
+ if (update.status === "available") return acp.label(`可更新 v${update.latest ?? update.current}`, `Update v${update.latest ?? update.current}`);
3455
+ if (update.status === "applying") return acp.label("更新中…", "Updating…");
3456
+ if (update.status === "applied") {
3457
+ const installed = update.installed ?? update.latest ?? update.current;
3458
+ return acp.label(`已更新 v${installed}`, `Updated v${installed}`);
3459
+ }
3460
+ if (update.status === "restart-required") {
3461
+ if (update.installed === void 0) return void 0;
3462
+ return acp.label(`待重启 v${update.installed}`, `Restart for v${update.installed}`);
3463
+ }
3464
+ }
3465
+ /**
3466
+ * The version badge's update companion. Every badge is a button, `applying`
3467
+ * included: the card is where the install's outcome is read, so the running
3468
+ * install must be able to open it too. States without anything to act on get
3469
+ * no badge at all — the version badge itself still opens the card.
3470
+ */
3471
+ function PluginUpdateBadge(props) {
3472
+ const { acp, update, open, onToggle } = props;
3473
+ const text = updateBadgeText(update, acp);
3474
+ if (text === void 0) return null;
3475
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3476
+ type: "button",
3477
+ className: "dwa-badge dwa-badge-info dwa-update-badge",
3478
+ "aria-expanded": open,
3479
+ "aria-controls": "ntr-plugin-update-card",
3480
+ onClick: onToggle,
3481
+ children: text
3482
+ });
3483
+ }
3484
+ /**
3485
+ * The update card. In-page, no dialog: the settings page already owns the one
3486
+ * action (install the exact version the host reported), and a modal on top of
3487
+ * a settings section would hide the version it is updating.
3488
+ *
3489
+ * The copy stays to one line per state (constraint 33); the host's raw error
3490
+ * (`message`) is the only long text, and it wraps on its own line so a stack
3491
+ * trace or a registry error is readable without stretching the card.
3492
+ */
3493
+ function PluginUpdateCard(props) {
3494
+ const { acp, update, onClose } = props;
3495
+ const applying = update.status === "applying";
3496
+ const latest = update.latest ?? update.current;
3497
+ const onDisk = update.installed !== void 0 && update.installed !== update.current ? update.installed : void 0;
3498
+ const published = update.publishedAt !== void 0 && !Number.isNaN(new Date(update.publishedAt).getTime()) ? new Date(update.publishedAt).toLocaleDateString() : void 0;
3499
+ const copy = update.status === "available" ? acp.label("更新会卸载并重装插件,之后可能需要重启 DSH。", "Updating uninstalls and reinstalls the plugin, and may require restarting DSH.") : update.status === "applied" ? acp.label("已应用,刷新页面即可使用新版本。", "Applied. Reload the page to use the new version.") : update.status === "restart-required" ? update.installed !== void 0 ? acp.label(`磁盘已是 v${update.installed},重启 DSH 后生效(桌面 App 退出重开;命令行重启 dsh web)。`, `On disk v${update.installed}. Restart DSH to apply (quit and reopen the app, or restart dsh web).`) : acp.label("更新可能已完成,请刷新页面确认当前版本。", "The update may already be complete. Refresh the page to confirm the current version.") : update.status === "busy" ? acp.label("有进行中的对话,结束后才能更新。", "Finish the running conversation before updating.") : update.status === "failed" ? acp.label("更新失败。", "Update failed.") : update.status === "current" ? acp.label("已是最新版本。", "Up to date.") : update.status === "source-install" ? acp.label("源码或本地安装,无法通过 registry 更新。", "Source or local install; update it through its own checkout.") : update.status === "managed" ? acp.label("此安装由宿主管理,不能在这里更新。", "This install is managed by the host and cannot be updated here.") : void 0;
3500
+ const showMessage = (update.status === "failed" || update.status === "busy" || update.status === "managed") && update.message !== void 0;
3501
+ const apply = async () => {
3502
+ if (update.latest === void 0) return;
3503
+ try {
3504
+ await acp.applyPluginUpdate(update.latest);
3505
+ } catch {}
3506
+ };
3507
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3508
+ id: "ntr-plugin-update-card",
3509
+ className: "dwa-card dwa-update-card",
3510
+ role: "group",
3511
+ "aria-label": acp.label("插件更新", "Plugin update"),
3512
+ children: [
3513
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3514
+ className: "dwa-update-summary",
3515
+ children: [
3516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label(`当前 v${update.current}`, `Current v${update.current}`) }),
3517
+ onDisk !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3518
+ className: "dwa-hint",
3519
+ children: acp.label(`磁盘 v${onDisk}`, `On disk v${onDisk}`)
3520
+ }),
3521
+ update.latest !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3522
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3523
+ "aria-hidden": "true",
3524
+ children: "→"
3525
+ }),
3526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label(`最新 v${latest}`, `Latest v${latest}`) }),
3527
+ published !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3528
+ className: "dwa-hint",
3529
+ children: published
3530
+ })
3531
+ ] })
3532
+ ]
3533
+ }),
3534
+ copy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3535
+ className: "dwa-update-copy",
3536
+ children: copy
3537
+ }),
3538
+ showMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3539
+ className: "dwa-update-message",
3540
+ children: update.message
3541
+ }),
3542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3543
+ className: "dwa-row-actions dwa-update-actions",
3544
+ children: [
3545
+ update.status === "available" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3546
+ type: "button",
3547
+ className: "dwa-btn dwa-btn-primary",
3548
+ onClick: () => {
3549
+ apply();
3550
+ },
3551
+ children: acp.label("更新", "Update")
3552
+ }),
3553
+ applying && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3554
+ type: "button",
3555
+ className: "dwa-btn dwa-btn-primary",
3556
+ disabled: true,
3557
+ children: acp.label("正在更新…", "Updating…")
3558
+ }),
3559
+ update.status !== "applying" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3560
+ type: "button",
3561
+ className: "dwa-btn",
3562
+ onClick: onClose,
3563
+ children: acp.label("关闭", "Close")
3564
+ }),
3565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3566
+ type: "button",
3567
+ className: "dwa-btn dwa-btn-quiet",
3568
+ disabled: applying,
3569
+ onClick: () => void acp.checkPluginUpdate(true),
3570
+ children: acp.label("重新检查", "Check again")
3571
+ })
3572
+ ]
3573
+ })
3574
+ ]
3575
+ });
3576
+ }
3577
+ /**
3578
+ * The card is drawn for every state a host can report about this install,
3579
+ * except the two where there is no install to talk about at all.
3580
+ */
3581
+ function updateCardVisible(update, open) {
3582
+ if (update === void 0 || !open) return false;
3583
+ return update.status !== "unsupported" && update.status !== "not-installed";
3584
+ }
3305
3585
  function AcpSettingsSection(props) {
3306
3586
  const acp = props.acp;
3307
3587
  ensureStyle();
3308
3588
  const ui = useUi$1(acp);
3309
3589
  const ledger = permissionLedgerCard(ui);
3310
- const [addOpen, setAddOpen] = (0, react.useState)(true);
3590
+ const [page, setPage] = (0, react.useState)("agents");
3591
+ const [addOpen, setAddOpen] = (0, react.useState)(false);
3311
3592
  const [addTarget, setAddTarget] = (0, react.useState)(void 0);
3312
- const [createNonce, setCreateNonce] = (0, react.useState)(0);
3593
+ const [updateOpen, setUpdateOpen] = (0, react.useState)(false);
3594
+ (0, react.useEffect)(() => {
3595
+ acp.checkPluginUpdate();
3596
+ }, [acp]);
3313
3597
  const { configured, available } = partitionAgents(ui.detectedAgents, ui.agents);
3314
3598
  const selected = addTarget ?? available[0] ?? NEW_AGENT_SEED;
3599
+ const openAdd = () => {
3600
+ setAddOpen(true);
3601
+ setPage("add");
3602
+ };
3603
+ const presetProblem = ui.preset !== void 0 && (!ui.preset.installed || ui.preset.problem !== void 0);
3604
+ const update = ui.pluginUpdate;
3605
+ const versionTitle = update?.status === "source-install" ? acp.label("源码或本地安装,无法通过 registry 更新。", "Source or local install; update it through its own checkout.") : void 0;
3606
+ const versionOpensUpdate = update !== void 0 && update.status !== "unsupported" && update.status !== "not-installed";
3315
3607
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3316
3608
  className: "dwa-root",
3317
3609
  children: [
@@ -3319,41 +3611,47 @@ window.__ModuleLoader__.load({
3319
3611
  className: "dwa-page-header",
3320
3612
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3321
3613
  className: "dwa-page-heading",
3322
- children: [
3323
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3324
- className: "dwa-page-title",
3325
- children: acp.label("NTR 智能体", "NTR Agents")
3326
- }),
3327
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3328
- className: "dwa-page-description",
3329
- children: acp.label("管理智能体连接、可用模型与对话显示偏好。", "Manage agent connections, available models, and conversation display preferences.")
3330
- }),
3331
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3332
- className: "dwa-page-status",
3333
- children: [
3334
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3335
- className: "dwa-dot",
3336
- 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)" },
3337
- title: ui.stream
3338
- }),
3339
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: streamText(ui.stream, acp) }),
3340
- ui.detectSummary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", ui.detectSummary] })
3341
- ]
3342
- })
3343
- ]
3344
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3345
- className: "dwa-page-actions",
3346
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3347
- className: "dwa-btn",
3348
- onClick: () => {
3349
- acp.resync();
3350
- acp.detectAgents();
3351
- },
3352
- disabled: ui.detecting,
3353
- children: ui.detecting ? acp.label("检测中...", "Detecting...") : acp.label("重新检测与刷新", "Re-detect & Refresh")
3354
- })
3614
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
3615
+ className: "dwa-page-title",
3616
+ "aria-label": acp.label("NTR 设置", "NTR settings"),
3617
+ children: [
3618
+ acp.label("NTR 设置", "NTR settings"),
3619
+ ui.pluginVersion !== void 0 && (versionOpensUpdate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3620
+ type: "button",
3621
+ className: "dwa-badge dwa-version dwa-version-btn",
3622
+ title: versionTitle,
3623
+ "aria-expanded": updateOpen,
3624
+ "aria-controls": "ntr-plugin-update-card",
3625
+ onClick: () => setUpdateOpen((open) => !open),
3626
+ children: `v${ui.pluginVersion}`
3627
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3628
+ className: "dwa-badge dwa-version",
3629
+ title: versionTitle,
3630
+ children: `v${ui.pluginVersion}`
3631
+ })),
3632
+ update !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginUpdateBadge, {
3633
+ acp,
3634
+ update,
3635
+ open: updateOpen,
3636
+ onToggle: () => setUpdateOpen((open) => !open)
3637
+ })
3638
+ ]
3639
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3640
+ className: "dwa-page-description",
3641
+ children: acp.label("连接智能体,设置你的对话偏好。", "Connect agents and set your conversation preferences.")
3642
+ })]
3643
+ }), page === "agents" && configured.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3644
+ type: "button",
3645
+ className: "dwa-btn dwa-btn-primary",
3646
+ onClick: openAdd,
3647
+ children: acp.label("添加智能体", "Add agent")
3355
3648
  })]
3356
3649
  }),
3650
+ update !== void 0 && updateCardVisible(update, updateOpen) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginUpdateCard, {
3651
+ acp,
3652
+ update,
3653
+ onClose: () => setUpdateOpen(false)
3654
+ }),
3357
3655
  ledger.visible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3358
3656
  className: "dwa-card dwa-card-warn",
3359
3657
  style: { marginBottom: 16 },
@@ -3393,18 +3691,20 @@ window.__ModuleLoader__.load({
3393
3691
  })]
3394
3692
  }),
3395
3693
  permission.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3396
- className: "dwa-btn dwa-btn-primary",
3694
+ type: "button",
3695
+ className: "dwa-btn",
3397
3696
  onClick: () => {
3398
3697
  acp.respondPermission(permission.requestId, option.optionId).catch(() => void 0);
3399
3698
  },
3400
3699
  children: option.name
3401
3700
  }, option.optionId)),
3402
3701
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3403
- className: "dwa-btn",
3702
+ type: "button",
3703
+ className: "dwa-btn dwa-btn-quiet",
3404
3704
  onClick: () => {
3405
3705
  acp.respondPermission(permission.requestId).catch(() => void 0);
3406
3706
  },
3407
- children: acp.label("拒绝", "Dismiss")
3707
+ children: acp.label("取消", "Cancel")
3408
3708
  })
3409
3709
  ]
3410
3710
  }, permission.requestId))
@@ -3422,12 +3722,81 @@ window.__ModuleLoader__.load({
3422
3722
  style: { flex: 1 },
3423
3723
  children: ui.notice
3424
3724
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3425
- className: "dwa-btn",
3725
+ type: "button",
3726
+ className: "dwa-btn dwa-btn-quiet",
3727
+ "aria-label": acp.label("关闭通知", "Dismiss notice"),
3426
3728
  onClick: () => acp.clearNotice(),
3427
3729
  children: "✕"
3428
3730
  })]
3429
3731
  }),
3430
- ui.preset !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3732
+ presetProblem && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3733
+ className: "dwa-card dwa-card-warn dwa-notice",
3734
+ role: "status",
3735
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label("NTR 模式需要检查,当前可能无法开始对话。", "NTR mode needs attention; new conversations may be unavailable.") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3736
+ type: "button",
3737
+ className: "dwa-btn",
3738
+ onClick: () => setPage("diagnostics"),
3739
+ children: acp.label("查看诊断", "View diagnostics")
3740
+ })]
3741
+ }),
3742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
3743
+ className: "dwa-tabs",
3744
+ "aria-label": acp.label("NTR 设置分类", "NTR settings categories"),
3745
+ children: [
3746
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3747
+ type: "button",
3748
+ "aria-pressed": page === "agents",
3749
+ onClick: () => setPage("agents"),
3750
+ children: acp.label("智能体", "Agents")
3751
+ }),
3752
+ ui.approvalDeadlines !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3753
+ type: "button",
3754
+ "aria-pressed": page === "approvals",
3755
+ onClick: () => setPage("approvals"),
3756
+ children: acp.label("审批时限", "Approval deadlines")
3757
+ }),
3758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3759
+ type: "button",
3760
+ "aria-pressed": page === "appearance",
3761
+ onClick: () => setPage("appearance"),
3762
+ children: acp.label("对话外观", "Appearance")
3763
+ })
3764
+ ]
3765
+ }),
3766
+ page === "diagnostics" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3767
+ className: "dwa-section",
3768
+ children: [
3769
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3770
+ className: "dwa-section-title",
3771
+ children: acp.label("连接诊断", "Connection diagnostics")
3772
+ }),
3773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3774
+ className: "dwa-preference-row",
3775
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label("宿主事件流", "Host event stream") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3776
+ className: "dwa-hint",
3777
+ children: streamText(ui.stream, acp)
3778
+ })]
3779
+ }),
3780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3781
+ className: "dwa-row-actions",
3782
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3783
+ type: "button",
3784
+ className: "dwa-btn",
3785
+ onClick: () => {
3786
+ acp.resync();
3787
+ acp.detectAgents();
3788
+ },
3789
+ disabled: ui.detecting,
3790
+ children: ui.detecting ? acp.label("检测中...", "Detecting...") : acp.label("重新检测与刷新", "Re-detect & Refresh")
3791
+ }), ui.detectSummary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3792
+ className: "dwa-hint",
3793
+ role: "status",
3794
+ children: ui.detectSummary
3795
+ })]
3796
+ })
3797
+ ]
3798
+ }),
3799
+ page === "diagnostics" && ui.preset !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3431
3800
  className: "dwa-card dwa-mode-banner",
3432
3801
  children: [
3433
3802
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3460,28 +3829,42 @@ window.__ModuleLoader__.load({
3460
3829
  }),
3461
3830
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3462
3831
  className: "dwa-section",
3832
+ hidden: page !== "agents",
3833
+ "aria-label": acp.label("已接入的智能体", "Connected agents"),
3463
3834
  children: [
3464
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3465
3836
  className: "dwa-section-heading",
3466
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
3837
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
3467
3838
  className: "dwa-section-title",
3468
- children: [acp.label("已接入的智能体", "Connected agents"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3839
+ children: [acp.label("已接入的智能体", "Connected agents"), ui.agentsLoaded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3469
3840
  className: "dwa-badge",
3470
3841
  children: configured.length
3471
3842
  })]
3472
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3473
- className: "dwa-section-copy",
3474
- children: acp.label("展开智能体可管理模型、启动命令与工作区。", "Expand an agent to manage its models, launch command, and workspace.")
3475
- })] })
3843
+ }) }), configured.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3844
+ className: "dwa-hint",
3845
+ children: acp.label("默认项用于新的 NTR 会话", "The default serves new NTR conversations")
3846
+ })]
3476
3847
  }),
3477
- configured.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3478
- className: "dwa-card",
3479
- style: {
3480
- textAlign: "center",
3481
- color: "var(--ntr-muted)",
3482
- padding: 20
3483
- },
3484
- 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.")
3848
+ !ui.agentsLoaded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3849
+ className: "dwa-empty",
3850
+ role: "status",
3851
+ children: acp.label("正在读取智能体…", "Loading agents…")
3852
+ }),
3853
+ ui.agentsLoaded && configured.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3854
+ className: "dwa-empty",
3855
+ children: [
3856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: acp.label("接入第一个智能体", "Connect your first agent") }),
3857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3858
+ className: "dwa-section-copy",
3859
+ children: acp.label("选择已有预设,或填写自己的 ACP 启动命令。", "Choose a preset or enter your own ACP launch command.")
3860
+ }),
3861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3862
+ type: "button",
3863
+ className: "dwa-btn dwa-btn-primary",
3864
+ onClick: openAdd,
3865
+ children: acp.label("添加智能体", "Add agent")
3866
+ })
3867
+ ]
3485
3868
  }),
3486
3869
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3487
3870
  className: "dwa-agent-list",
@@ -3492,36 +3875,33 @@ window.__ModuleLoader__.load({
3492
3875
  activeAgent,
3493
3876
  acp,
3494
3877
  isDefault: ui.defaultAgent === info.id,
3495
- synthesized
3878
+ synthesized,
3879
+ active: page === "agents"
3496
3880
  }, info.id);
3497
3881
  })
3498
3882
  })
3499
3883
  ]
3500
3884
  }),
3501
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3502
3886
  className: "dwa-section",
3503
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3887
+ hidden: page !== "add",
3888
+ "aria-label": acp.label("添加智能体", "Add agent"),
3889
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3890
+ type: "button",
3891
+ className: "dwa-btn dwa-btn-quiet dwa-back",
3892
+ onClick: () => setPage("agents"),
3893
+ children: acp.label("返回智能体列表", "Back to agents")
3894
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3504
3895
  className: "dwa-add-panel",
3505
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3506
- type: "button",
3507
- className: "dwa-add-toggle",
3508
- "aria-expanded": addOpen,
3509
- onClick: () => setAddOpen((open) => !open),
3510
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3896
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3897
+ className: "dwa-section-heading",
3898
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3511
3899
  className: "dwa-section-title",
3512
3900
  children: acp.label("添加智能体", "Add agent")
3513
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3901
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3514
3902
  className: "dwa-section-copy",
3515
- style: { display: "block" },
3516
3903
  children: acp.label("从预设快速启用,或创建自定义连接。", "Enable a preset or create a custom connection.")
3517
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3518
- className: "dwa-caret-btn",
3519
- "aria-hidden": true,
3520
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3521
- className: "dwa-caret",
3522
- children: addOpen ? "▾" : "▸"
3523
- })
3524
- })]
3904
+ })] })
3525
3905
  }), addOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3526
3906
  className: "dwa-add-content",
3527
3907
  children: [
@@ -3551,21 +3931,28 @@ window.__ModuleLoader__.load({
3551
3931
  isDefault: false,
3552
3932
  synthesized: false,
3553
3933
  newCustom: selected.id === "",
3934
+ active: page === "add",
3554
3935
  onCreated: () => {
3555
3936
  setAddTarget(void 0);
3556
- setCreateNonce((nonce) => nonce + 1);
3937
+ setAddOpen(false);
3938
+ setPage("agents");
3557
3939
  }
3558
- }, selected.id === "" ? `custom-${createNonce}` : selected.id)
3940
+ }, selected.id)
3559
3941
  ]
3560
3942
  })]
3561
- })
3943
+ })]
3562
3944
  }),
3563
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApprovalDeadlinesCard, {
3564
- acp,
3565
- ui
3945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3946
+ hidden: page !== "approvals",
3947
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApprovalDeadlinesCard, {
3948
+ acp,
3949
+ ui
3950
+ })
3566
3951
  }),
3567
3952
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3568
3953
  className: "dwa-section",
3954
+ hidden: page !== "appearance",
3955
+ "aria-label": acp.label("对话外观", "Appearance"),
3569
3956
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3570
3957
  className: "dwa-section-heading",
3571
3958
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -3576,11 +3963,7 @@ window.__ModuleLoader__.load({
3576
3963
  children: acp.label("设置对话中 NTR 挂牌的进程存活提示。", "Choose how the NTR tag indicates a running agent process.")
3577
3964
  })] })
3578
3965
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3579
- className: "dwa-card",
3580
- style: {
3581
- paddingTop: 4,
3582
- paddingBottom: 4
3583
- },
3966
+ className: "dwa-preferences",
3584
3967
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3585
3968
  className: "dwa-preference-row",
3586
3969
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -3627,15 +4010,35 @@ window.__ModuleLoader__.load({
3627
4010
  })]
3628
4011
  })]
3629
4012
  })]
4013
+ }),
4014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
4015
+ className: "dwa-health",
4016
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4017
+ className: "dwa-health-status",
4018
+ children: [
4019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4020
+ className: "dwa-dot",
4021
+ style: { background: ui.stream === "live" ? "var(--ntr-success)" : ui.stream === "down" ? "var(--ntr-danger)" : "var(--ntr-muted)" }
4022
+ }),
4023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: streamText(ui.stream, acp) }),
4024
+ ui.preset !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: ui.preset.installed && !presetProblem ? acp.label("NTR 模式已就绪", "NTR mode ready") : acp.label("NTR 模式需检查", "NTR mode needs attention") })
4025
+ ]
4026
+ }), page !== "diagnostics" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4027
+ type: "button",
4028
+ className: "dwa-btn dwa-btn-quiet",
4029
+ onClick: () => setPage("diagnostics"),
4030
+ children: acp.label("查看诊断", "View diagnostics")
4031
+ })]
3630
4032
  })
3631
4033
  ]
3632
4034
  });
3633
4035
  }
3634
4036
  /** One row per agent: compact when collapsed, the full form when expanded. */
3635
- function AgentRow({ detected, activeAgent, acp, isDefault, synthesized, newCustom = false, onCreated }) {
4037
+ function AgentRow({ detected, activeAgent, acp, isDefault, synthesized, active, newCustom = false, onCreated }) {
3636
4038
  const ui = useUi$1(acp);
3637
4039
  const configured = activeAgent !== void 0 || detected.currentConfig !== void 0;
3638
4040
  const [expanded, setExpanded] = (0, react.useState)(!configured);
4041
+ const [detailPage, setDetailPage] = (0, react.useState)(configured ? "models" : "connection");
3639
4042
  const [form, setForm] = (0, react.useState)(() => initialForm(detected, activeAgent));
3640
4043
  const [saving, setSaving] = (0, react.useState)(false);
3641
4044
  const [savedSuccess, setSavedSuccess] = (0, react.useState)(false);
@@ -3671,7 +4074,9 @@ window.__ModuleLoader__.load({
3671
4074
  const formIncomplete = splitCommandLine(form.commandLine).command === "" || !idValid || idTaken || cwdInvalid || rootsInvalid || idleInvalid || mcpInvalid;
3672
4075
  const payload = buildPayload(form, detected, newCustom);
3673
4076
  const current = persistedDef(detected, activeAgent);
3674
- const showSaveWarning = current !== void 0 && !payloadEquals(payload, current) && activeAgent !== void 0 && activeAgent.state !== "stopped";
4077
+ const clearingIdleOverride = payload.idleTimeoutMs === void 0 && detected.currentConfig?.idleTimeoutMs !== void 0;
4078
+ const dirty = current !== void 0 && (!payloadEquals(payload, current) || clearingIdleOverride);
4079
+ const showSaveWarning = dirty && activeAgent !== void 0 && activeAgent.state !== "stopped";
3675
4080
  const handleUseDefault = () => patch({ commandLine: formatCommandLine(detected.defaultCommand, detected.defaultArgs) });
3676
4081
  const handleSave = async () => {
3677
4082
  setSaving(true);
@@ -3711,7 +4116,7 @@ window.__ModuleLoader__.load({
3711
4116
  const displayName = newCustom ? acp.label("新建自定义 Agent", "New Custom Agent") : activeAgent?.title ?? detected.currentConfig?.title ?? detected.name;
3712
4117
  const stderr = activeAgent?.state === "error" ? activeAgent.stderrTail ?? [] : [];
3713
4118
  const errorSummary = stderr.length > 0 ? stderr[stderr.length - 1] : void 0;
3714
- const currentModel = ui.modelCatalogs[detected.id]?.catalog.model?.currentValue;
4119
+ const selectedModelCount = form.exposed?.length ?? 0;
3715
4120
  const isPreset = PRESET_IDS.has(detected.id);
3716
4121
  const open = expanded || activeAgent?.state === "error" && configured;
3717
4122
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3719,17 +4124,10 @@ window.__ModuleLoader__.load({
3719
4124
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3720
4125
  className: "dwa-agent-row",
3721
4126
  children: [
3722
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3723
- type: "button",
3724
- className: "dwa-caret-btn",
3725
- "aria-expanded": open,
3726
- onClick: () => setExpanded((value) => !value),
3727
- title: open ? acp.label("收起", "Collapse") : acp.label("展开配置与模型", "Expand configuration and models"),
3728
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3729
- className: "dwa-caret",
3730
- "aria-hidden": true,
3731
- children: open ? "▾" : "▸"
3732
- })
4127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4128
+ className: "dwa-agent-avatar",
4129
+ "aria-hidden": true,
4130
+ children: displayName.slice(0, 2).toUpperCase()
3733
4131
  }),
3734
4132
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3735
4133
  className: "dwa-agent-summary",
@@ -3743,7 +4141,7 @@ window.__ModuleLoader__.load({
3743
4141
  children: displayName
3744
4142
  }),
3745
4143
  isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3746
- className: "dwa-badge dwa-badge-success",
4144
+ className: "dwa-badge dwa-badge-info",
3747
4145
  children: acp.label("默认", "Default")
3748
4146
  }),
3749
4147
  !isPreset && !newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3770,7 +4168,7 @@ window.__ModuleLoader__.load({
3770
4168
  configured && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3771
4169
  className: "dwa-agent-meta-separator",
3772
4170
  children: "·"
3773
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: currentModel === void 0 ? acp.label("未探测模型", "Model not probed") : `${acp.label("模型", "Model")}: ${currentModel}` })] }),
4171
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label(`${String(selectedModelCount)} 个已选模型`, `${String(selectedModelCount)} selected models`) })] }),
3774
4172
  !synthesized && !newCustom && !detected.detected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3775
4173
  style: { color: "var(--ntr-warn)" },
3776
4174
  title: detected.description,
@@ -3789,24 +4187,31 @@ window.__ModuleLoader__.load({
3789
4187
  ]
3790
4188
  }),
3791
4189
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3792
- style: {
3793
- display: "flex",
3794
- alignItems: "center",
3795
- gap: 6
3796
- },
3797
- children: [activeAgent?.state === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3798
- className: "dwa-btn",
3799
- disabled: testing,
3800
- onClick: () => void handleTest(),
3801
- children: testing ? acp.label("测试中...", "Testing...") : acp.label("测试", "Test")
3802
- }), configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowMenu, {
3803
- acp,
3804
- canStop: activeAgent !== void 0 && activeAgent.state !== "stopped",
3805
- onStop: () => void acp.stopAgent(detected.id),
3806
- onRemove: async () => {
3807
- await acp.deleteAgent(detected.id).catch(() => {});
3808
- }
3809
- })]
4190
+ className: "dwa-row-actions",
4191
+ children: [
4192
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4193
+ type: "button",
4194
+ className: "dwa-btn dwa-btn-quiet",
4195
+ "aria-expanded": open,
4196
+ "aria-label": acp.label(`${open ? "收起" : "管理"} ${displayName}`, `${open ? "Collapse" : "Manage"} ${displayName}`),
4197
+ onClick: () => setExpanded((value) => !value),
4198
+ children: open ? acp.label("收起", "Collapse") : acp.label("管理", "Manage")
4199
+ }),
4200
+ activeAgent?.state === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4201
+ className: "dwa-btn",
4202
+ disabled: testing,
4203
+ onClick: () => void handleTest(),
4204
+ children: testing ? acp.label("测试中...", "Testing...") : acp.label("测试", "Test")
4205
+ }),
4206
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowMenu, {
4207
+ acp,
4208
+ canStop: activeAgent !== void 0 && activeAgent.state !== "stopped",
4209
+ onStop: () => void acp.stopAgent(detected.id),
4210
+ onRemove: async () => {
4211
+ await acp.deleteAgent(detected.id).catch(() => {});
4212
+ }
4213
+ })
4214
+ ]
3810
4215
  })
3811
4216
  ]
3812
4217
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3817,42 +4222,52 @@ window.__ModuleLoader__.load({
3817
4222
  className: "dwa-stderr",
3818
4223
  children: stderr.join("\n")
3819
4224
  }),
3820
- configured && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
4226
+ className: "dwa-tabs dwa-detail-tabs",
4227
+ "aria-label": acp.label(`${displayName} 配置`, `${displayName} settings`),
4228
+ children: [
4229
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4230
+ type: "button",
4231
+ "aria-pressed": detailPage === "models",
4232
+ onClick: () => setDetailPage("models"),
4233
+ children: acp.label("模型", "Models")
4234
+ }),
4235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4236
+ type: "button",
4237
+ "aria-pressed": detailPage === "connection",
4238
+ onClick: () => setDetailPage("connection"),
4239
+ children: acp.label("连接", "Connection")
4240
+ }),
4241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4242
+ type: "button",
4243
+ "aria-pressed": detailPage === "advanced",
4244
+ onClick: () => setDetailPage("advanced"),
4245
+ children: acp.label("高级", "Advanced")
4246
+ })
4247
+ ]
4248
+ }),
4249
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3821
4250
  className: "dwa-subsection",
3822
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3823
- className: "dwa-subsection-head",
3824
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3825
- className: "dwa-subsection-title",
3826
- children: acp.label("模型与默认值", "Models & defaults")
3827
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3828
- className: "dwa-subsection-copy",
3829
- children: acp.label("检测模型目录,并控制对话中可选择的模型。", "Detect the model catalog and choose what appears in conversations.")
3830
- })] })
3831
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogBlock, {
4251
+ hidden: detailPage !== "models",
4252
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogBlock, {
3832
4253
  agentId: detected.id,
3833
4254
  detected,
3834
4255
  acp,
3835
- active: open,
4256
+ active: active && open && detailPage === "models",
3836
4257
  exposed: form.exposed,
3837
4258
  onExposedChange: (exposed) => patch({ exposed }),
3838
4259
  autoDiscover: form.autoDiscoverModels,
3839
4260
  onAutoDiscoverChange: (autoDiscoverModels) => patch({ autoDiscoverModels }),
3840
4261
  isDefault
3841
- })]
4262
+ })
3842
4263
  }),
3843
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3844
4265
  className: "dwa-subsection",
3845
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3846
- className: "dwa-subsection-head",
3847
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3848
- className: "dwa-subsection-title",
3849
- children: acp.label("连接配置", "Connection")
3850
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3851
- className: "dwa-subsection-copy",
3852
- children: acp.label("设置显示名与启动命令,高级选项按需展开。", "Set the display name and launch command; expand advanced options only when needed.")
3853
- })] })
3854
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentDetails, {
4266
+ hidden: detailPage === "models",
4267
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentDetails, {
3855
4268
  acp,
4269
+ advancedOpen: detailPage === "advanced",
4270
+ dirty,
3856
4271
  detected,
3857
4272
  form,
3858
4273
  patch,
@@ -3873,7 +4288,7 @@ window.__ModuleLoader__.load({
3873
4288
  onUseDefault: handleUseDefault,
3874
4289
  onSave: () => void handleSave(),
3875
4290
  onTest: () => void handleTest()
3876
- })]
4291
+ })
3877
4292
  })
3878
4293
  ]
3879
4294
  })]
@@ -3976,111 +4391,90 @@ window.__ModuleLoader__.load({
3976
4391
  return payload;
3977
4392
  }
3978
4393
  /** The expanded body: connection form, workspace allow-list, env, actions. */
3979
- function AgentDetails({ acp, detected, form, patch, isConfigured, newCustom, formIncomplete, idValid, idTaken, cwdInvalid, rootsInvalid, idleInvalid, mcpInvalid, saving, savedSuccess, testing, testResult, showSaveWarning, onUseDefault, onSave, onTest }) {
3980
- useUi$1(acp);
3981
- const [advancedOpen, setAdvancedOpen] = (0, react.useState)(false);
4394
+ function AgentDetails({ acp, advancedOpen, dirty, detected, form, patch, isConfigured, newCustom, formIncomplete, idValid, idTaken, cwdInvalid, rootsInvalid, idleInvalid, mcpInvalid, saving, savedSuccess, testing, testResult, showSaveWarning, onUseDefault, onSave, onTest }) {
3982
4395
  const parsedCommand = splitCommandLine(form.commandLine);
3983
4396
  const commandPreview = parsedCommand.command === "" ? acp.label("填整条命令,例如:codebuddy --acp", "Type the whole command, e.g. codebuddy --acp") : `${acp.label("程序", "Program")}: ${parsedCommand.command}` + (parsedCommand.args.length === 0 ? "" : ` ${acp.label("参数", "Arguments")}: ${parsedCommand.args.join(" ")}`);
3984
4397
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
3985
- newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3986
- className: "dwa-form-row",
3987
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3988
- className: "dwa-form-col",
3989
- children: [
3990
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3991
- className: "dwa-form-label",
3992
- children: acp.label("Agent ID(唯一标识,创建后不可改)", "Agent ID (unique, immutable after creation)")
3993
- }),
3994
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3995
- className: "dwa-input dwa-input-mono",
3996
- value: form.agentId,
3997
- onChange: (e) => patch({ agentId: e.target.value }),
3998
- placeholder: "e.g. my-agent"
3999
- }),
4000
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4001
- className: idValid && !idTaken ? "dwa-hint" : "dwa-error",
4002
- children: idTaken ? acp.label("该 ID 已被现有 Agent 占用。", "This id is already taken by a configured agent.") : idValid ? acp.label("小写字母或数字开头,可用 a-z 0-9 . _ -,最长 64 字符。", "Starts with a lowercase letter or digit; a-z 0-9 . _ - allowed; max 64 chars.") : acp.label("ID 格式不合法:小写字母或数字开头,可用 a-z 0-9 . _ -。", "Invalid id: start with a lowercase letter or digit; only a-z 0-9 . _ - allowed.")
4003
- })
4004
- ]
4005
- })
4006
- }),
4007
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4008
- className: "dwa-form-row",
4009
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4010
- className: "dwa-form-col",
4011
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4012
- className: "dwa-form-label",
4013
- children: acp.label("显示名", "Display Name")
4014
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4015
- className: "dwa-input",
4016
- value: form.title,
4017
- onChange: (e) => patch({ title: e.target.value }),
4018
- placeholder: detected.name === "" ? acp.label("我的 Agent", "My Agent") : detected.name
4019
- })]
4020
- })
4021
- }),
4022
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4023
- className: "dwa-form-row",
4024
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4025
- className: "dwa-form-col",
4026
- children: [
4027
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4028
- className: "dwa-form-label",
4029
- children: acp.label("启动命令(程序 + 参数)", "Launch Command (program + arguments)")
4030
- }),
4031
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4032
- className: "dwa-input dwa-input-mono",
4033
- value: form.commandLine,
4034
- onChange: (e) => patch({ commandLine: e.target.value }),
4035
- placeholder: detected.defaultCommand === "" ? "e.g. codebuddy --acp" : formatCommandLine(detected.defaultCommand, detected.defaultArgs)
4036
- }),
4037
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4038
- className: "dwa-hint",
4039
- children: commandPreview
4040
- }),
4041
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4042
- className: "dwa-hint",
4043
- 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.")
4044
- })
4045
- ]
4046
- })
4047
- }),
4048
4398
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4049
- style: {
4050
- display: "flex",
4051
- alignItems: "center",
4052
- gap: 8,
4053
- marginTop: 12,
4054
- flexWrap: "wrap"
4055
- },
4399
+ hidden: advancedOpen,
4056
4400
  children: [
4057
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4058
- type: "button",
4059
- className: "dwa-btn dwa-btn-secondary",
4060
- "aria-expanded": advancedOpen,
4061
- onClick: () => setAdvancedOpen((value) => !value),
4062
- children: [
4063
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4064
- className: "dwa-caret",
4065
- "aria-hidden": true,
4066
- children: advancedOpen ? "▾" : "▸"
4067
- }),
4068
- " ",
4069
- acp.label("高级参数", "Advanced Parameters")
4070
- ]
4401
+ newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4402
+ className: "dwa-form-row",
4403
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4404
+ className: "dwa-form-col",
4405
+ children: [
4406
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4407
+ className: "dwa-form-label",
4408
+ children: acp.label("Agent ID(唯一标识,创建后不可改)", "Agent ID (unique, immutable after creation)")
4409
+ }),
4410
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4411
+ className: "dwa-input dwa-input-mono",
4412
+ "aria-label": acp.label("Agent ID", "Agent ID"),
4413
+ value: form.agentId,
4414
+ onChange: (e) => patch({ agentId: e.target.value }),
4415
+ placeholder: "e.g. my-agent"
4416
+ }),
4417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4418
+ className: idValid && !idTaken ? "dwa-hint" : "dwa-error",
4419
+ children: idTaken ? acp.label("该 ID 已被现有 Agent 占用。", "This id is already taken by a configured agent.") : idValid ? acp.label("小写字母或数字开头,可用 a-z 0-9 . _ -,最长 64 字符。", "Starts with a lowercase letter or digit; a-z 0-9 . _ - allowed; max 64 chars.") : acp.label("ID 格式不合法:小写字母或数字开头,可用 a-z 0-9 . _ -。", "Invalid id: start with a lowercase letter or digit; only a-z 0-9 . _ - allowed.")
4420
+ })
4421
+ ]
4422
+ })
4423
+ }),
4424
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4425
+ className: "dwa-form-row",
4426
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4427
+ className: "dwa-form-col",
4428
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4429
+ className: "dwa-form-label",
4430
+ children: acp.label("显示名", "Display Name")
4431
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4432
+ className: "dwa-input",
4433
+ "aria-label": acp.label("显示名", "Display Name"),
4434
+ value: form.title,
4435
+ onChange: (e) => patch({ title: e.target.value }),
4436
+ placeholder: detected.name === "" ? acp.label("我的 Agent", "My Agent") : detected.name
4437
+ })]
4438
+ })
4071
4439
  }),
4072
- !advancedOpen && (cwdInvalid || rootsInvalid) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4073
- className: "dwa-error",
4074
- children: acp.label("里面有非法的工作空间路径", "Contains an invalid workspace path")
4440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4441
+ className: "dwa-form-row",
4442
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4443
+ className: "dwa-form-col",
4444
+ children: [
4445
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4446
+ className: "dwa-form-label",
4447
+ children: acp.label("启动命令(程序 + 参数)", "Launch Command (program + arguments)")
4448
+ }),
4449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4450
+ className: "dwa-input dwa-input-mono",
4451
+ "aria-label": acp.label("启动命令(程序 + 参数)", "Launch Command (program + arguments)"),
4452
+ value: form.commandLine,
4453
+ onChange: (e) => patch({ commandLine: e.target.value }),
4454
+ placeholder: detected.defaultCommand === "" ? "e.g. codebuddy --acp" : formatCommandLine(detected.defaultCommand, detected.defaultArgs)
4455
+ }),
4456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4457
+ className: "dwa-hint",
4458
+ children: commandPreview
4459
+ }),
4460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4461
+ className: "dwa-hint",
4462
+ children: acp.label("含空格的程序路径请加引号。", "Quote the program path if it contains spaces.")
4463
+ })
4464
+ ]
4465
+ })
4075
4466
  }),
4076
- !advancedOpen && mcpInvalid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4077
- className: "dwa-error",
4078
- children: acp.label("里面的 MCP 服务器配置不是合法 JSON", "Contains an invalid MCP server list")
4467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceBlock, {
4468
+ acp,
4469
+ form,
4470
+ patch,
4471
+ cwdInvalid,
4472
+ rootsInvalid
4079
4473
  })
4080
4474
  ]
4081
4475
  }),
4082
4476
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4083
- style: advancedOpen ? void 0 : { display: "none" },
4477
+ hidden: !advancedOpen,
4084
4478
  children: [
4085
4479
  !newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4086
4480
  className: "dwa-default-hint",
@@ -4125,13 +4519,6 @@ window.__ModuleLoader__.load({
4125
4519
  })]
4126
4520
  })
4127
4521
  }),
4128
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceBlock, {
4129
- acp,
4130
- form,
4131
- patch,
4132
- cwdInvalid,
4133
- rootsInvalid
4134
- }),
4135
4522
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4136
4523
  className: "dwa-form-row",
4137
4524
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4143,19 +4530,14 @@ window.__ModuleLoader__.load({
4143
4530
  }),
4144
4531
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4145
4532
  className: "dwa-input dwa-input-mono",
4533
+ "aria-label": acp.label("空闲释放(毫秒)", "Idle Release (ms)"),
4146
4534
  value: form.idleStr,
4147
4535
  onChange: (e) => patch({ idleStr: e.target.value }),
4148
4536
  placeholder: acp.label(`留空 = 宿主默认(承载会话 ${String(DEFAULT_IDLE_RELEASE_MS / 6e4)} 分钟 / 探测进程 ${String(DEFAULT_PROBE_RELEASE_MS / 6e4)} 分钟)`, `Blank = host default (${String(DEFAULT_IDLE_RELEASE_MS / 6e4)} min for a chat process / ${String(DEFAULT_PROBE_RELEASE_MS / 6e4)} min for a probe)`)
4149
4537
  }),
4150
4538
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4151
4539
  className: idleInvalid ? "dwa-error" : "dwa-hint",
4152
- children: idleInvalid ? acp.label("必须是 0(永不释放)或 [1000, 86400000] 之间的整数毫秒。", "Must be 0 (never release) or an integer in [1000, 86400000] ms.") : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4153
- acp.label("空闲超过此时长就挂起进程(会话保留,下一条消息自动恢复)。", "The process is parked once it has been idle this long (the session is kept and resumes on the next message)."),
4154
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
4155
- acp.label("0 = 永不释放;回合在跑、取消未结算或审批待答时不释放。", "0 = never; a process with a running turn, an unsettled cancel, or a pending approval is never released."),
4156
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
4157
- 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.")
4158
- ] })
4540
+ children: idleInvalid ? acp.label("必须是 0(永不释放)或 [1000, 86400000] 之间的整数毫秒。", "Must be 0 (never release) or an integer in [1000, 86400000] ms.") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: acp.label("承载过会话的进程空闲后挂起;0 不释放,探测进程固定 5 分钟。", "Park idle chat processes; 0 keeps them alive. Probe processes always release after 5 minutes.") })
4159
4541
  })
4160
4542
  ]
4161
4543
  })
@@ -4245,6 +4627,7 @@ window.__ModuleLoader__.load({
4245
4627
  minHeight: 96,
4246
4628
  resize: "vertical"
4247
4629
  },
4630
+ "aria-label": acp.label("MCP 服务器(JSON)", "MCP Servers (JSON)"),
4248
4631
  value: form.mcpText,
4249
4632
  onChange: (e) => patch({ mcpText: e.target.value }),
4250
4633
  spellCheck: false,
@@ -4256,15 +4639,29 @@ window.__ModuleLoader__.load({
4256
4639
  }),
4257
4640
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4258
4641
  className: "dwa-hint",
4259
- 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.")
4642
+ children: acp.label("额外追加给智能体的 MCP 服务器,不影响其已有配置。", "Additional MCP servers for the agent; its own configuration is unchanged.")
4260
4643
  })
4261
4644
  ]
4262
4645
  })
4263
4646
  ]
4264
4647
  }),
4648
+ !advancedOpen && (mcpInvalid || idleInvalid) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4649
+ className: "dwa-error",
4650
+ role: "alert",
4651
+ children: acp.label("请检查「高级」中的无效配置。", "Check the invalid settings in Advanced.")
4652
+ }),
4653
+ advancedOpen && (!idValid || idTaken || cwdInvalid || rootsInvalid || parsedCommand.command === "") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4654
+ className: "dwa-error",
4655
+ role: "alert",
4656
+ children: acp.label("请检查「连接」中的未完成或无效配置。", "Check the incomplete or invalid settings in Connection.")
4657
+ }),
4265
4658
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4266
4659
  className: "dwa-form-actions",
4267
4660
  children: [
4661
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4662
+ className: "dwa-hint",
4663
+ children: isConfigured ? dirty ? acp.label("有未保存的更改", "Unsaved changes") : acp.label("配置已保存", "Settings saved") : ""
4664
+ }),
4268
4665
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4269
4666
  className: "dwa-btn",
4270
4667
  onClick: onTest,
@@ -4275,8 +4672,8 @@ window.__ModuleLoader__.load({
4275
4672
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4276
4673
  className: "dwa-btn dwa-btn-primary",
4277
4674
  onClick: onSave,
4278
- disabled: saving || testing || formIncomplete,
4279
- children: saving ? acp.label("保存中...", "Saving...") : savedSuccess ? acp.label("✓ 已保存", "✓ Saved") : isConfigured ? acp.label("保存", "Save") : acp.label("保存并启用", "Save & Enable")
4675
+ disabled: saving || testing || formIncomplete || isConfigured && !dirty,
4676
+ children: saving ? acp.label("保存中...", "Saving...") : savedSuccess ? acp.label("✓ 已保存", "✓ Saved") : isConfigured ? acp.label("保存更改", "Save changes") : acp.label("保存并启用", "Save & Enable")
4280
4677
  }),
4281
4678
  showSaveWarning && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4282
4679
  className: "dwa-hint",
@@ -4352,7 +4749,7 @@ window.__ModuleLoader__.load({
4352
4749
  }),
4353
4750
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4354
4751
  className: "dwa-hint",
4355
- children: acp.label("对话里的 NTR 会话以该 DSH 会话选中的工作区为工作目录(agent 进程也从那里启动);此项只在 /acp 直连会话与宿主未上报工作区时生效。", "An NTR session inside a conversation runs — and starts its agent process — in that DSH session’s own workspace; this value applies to /acp-direct sessions and when the host reports no workspace.")
4752
+ children: acp.label("聊天使用会话工作区;此处用于直连会话或宿主未提供工作区时。", "Chats use their session workspace; this applies to direct sessions or when the host provides no workspace.")
4356
4753
  }),
4357
4754
  !form.rootsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4358
4755
  className: "dwa-hint",
@@ -4721,7 +5118,7 @@ window.__ModuleLoader__.load({
4721
5118
  fontWeight: 400
4722
5119
  },
4723
5120
  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."),
4724
- children: acp.label("(NTR 座位 / 芯片的下拉列表)", "(the NTR seat and chip pickers)")
5121
+ children: acp.label("仅影响菜单可见性", "Menu visibility only")
4725
5122
  })]
4726
5123
  }),
4727
5124
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -4743,6 +5140,7 @@ window.__ModuleLoader__.load({
4743
5140
  children: probing ? acp.label("模型检测中...", "Probing models...") : acp.label("检测可用模型", "Detect available models")
4744
5141
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4745
5142
  className: "dwa-input dwa-model-search",
5143
+ "aria-label": acp.label("搜索模型", "Search models"),
4746
5144
  value: search,
4747
5145
  onChange: (e) => setSearch(e.target.value),
4748
5146
  placeholder: acp.label("搜索模型…", "Search models…")
@@ -4860,7 +5258,7 @@ window.__ModuleLoader__.load({
4860
5258
  className: "dwa-model-list",
4861
5259
  children: [selectedChoices.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4862
5260
  className: "dwa-hint dwa-model-empty",
4863
- children: acp.label("还没有选择模型(点左侧的模型加入)", "Nothing selected yet — pick models on the left")
5261
+ children: acp.label("尚未选择模型,从「全部模型」中加入。", "No models selected. Add from All models.")
4864
5262
  }), selectedChoices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4865
5263
  type: "button",
4866
5264
  className: "dwa-model-row is-on",