dsh-ntr 0.1.6 → 0.2.1

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
@@ -14,6 +14,36 @@ window.__ModuleLoader__.load({
14
14
  };
15
15
  //#endregion
16
16
  //#region src/client/store.ts
17
+ /** Returned by {@link deadlineRemainingLabel} once the deadline has passed. */
18
+ const PERMISSION_DEADLINE_EXPIRED = "expired";
19
+ /**
20
+ * The asks that belong to one DSH chat conversation. An ask without a
21
+ * `dshSessionId` belongs to no conversation (an `/acp` RPC-only session), so it
22
+ * is deliberately absent from every conversation's dock and stays in the
23
+ * settings-panel ledger.
24
+ */
25
+ function permissionsForSession(permissions, dshSessionId) {
26
+ return permissions.filter((permission) => permission.dshSessionId === dshSessionId);
27
+ }
28
+ /**
29
+ * Compact countdown for one ask, or `undefined` when there is nothing to count
30
+ * down (`wait` mode, or a frame without a deadline). Pure so the formatting is
31
+ * testable without a clock: the dock passes `Date.now()` on its tick.
32
+ *
33
+ * Past the deadline the answer is {@link PERMISSION_DEADLINE_EXPIRED} rather
34
+ * than a negative number — the host has already armed the expiry, and a card
35
+ * counting backwards would claim a deadline that no longer exists.
36
+ */
37
+ function deadlineRemainingLabel(deadlineAt, now, mode) {
38
+ if (mode === "wait" || deadlineAt === void 0) return void 0;
39
+ const remainingMs = deadlineAt - now;
40
+ if (remainingMs <= 0) return PERMISSION_DEADLINE_EXPIRED;
41
+ const seconds = Math.ceil(remainingMs / 1e3);
42
+ if (seconds < 60) return `${String(seconds)}s`;
43
+ const minutes = Math.floor(seconds / 60);
44
+ const rest = seconds % 60;
45
+ return rest === 0 ? `${String(minutes)}m` : `${String(minutes)}m ${String(rest)}s`;
46
+ }
17
47
  const MAX_BLOCKS = 400;
18
48
  /** How many observed ACP transcripts stay live at once (insertion-ordered eviction). */
19
49
  const MAX_WATCHED = 6;
@@ -284,7 +314,6 @@ window.__ModuleLoader__.load({
284
314
  seatCatalogs: {},
285
315
  probingModels: {},
286
316
  probeErrors: {},
287
- approvalRouted: false,
288
317
  chatSessions: [],
289
318
  tagHalo: { ...DEFAULT_TAG_HALO },
290
319
  approvalDeadlines: void 0,
@@ -470,7 +499,7 @@ window.__ModuleLoader__.load({
470
499
  /** Snapshot refresh used on panel open and after a stream outage. */
471
500
  async resync() {
472
501
  try {
473
- const [{ agents, approvalRouted }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult] = await Promise.all([
502
+ const [{ agents }, { sessions }, defaultAgentInfo, detectResult, presetResult, tagHaloResult, deadlinesResult] = await Promise.all([
474
503
  this.call("agents/list"),
475
504
  this.call("sessions/list"),
476
505
  this.call("default/agent").catch(() => ({})),
@@ -486,7 +515,6 @@ window.__ModuleLoader__.load({
486
515
  sessions,
487
516
  defaultAgent: defaultAgentInfo?.agentId,
488
517
  detectedAgents,
489
- approvalRouted: approvalRouted === true,
490
518
  preset: presetResult?.preset ?? void 0,
491
519
  tagHalo: tagHaloResult?.halo ?? { ...DEFAULT_TAG_HALO },
492
520
  approvalDeadlines: deadlinesResult.deadlines
@@ -1113,6 +1141,14 @@ window.__ModuleLoader__.load({
1113
1141
  this.flashError(error);
1114
1142
  }
1115
1143
  }
1144
+ /**
1145
+ * Answer one ask (`optionId` absent = cancel it). A failure is reported
1146
+ * through the store's notice — the settings panel renders that — AND
1147
+ * rethrown, so the conversation dock can pin the message next to the card
1148
+ * that failed instead of leaving it on a settings page the user may not
1149
+ * have open. Callers that cannot show it (the settings panel) swallow it
1150
+ * after the notice.
1151
+ */
1116
1152
  async respondPermission(requestId, optionId) {
1117
1153
  try {
1118
1154
  await this.call("permission/respond", {
@@ -1121,6 +1157,7 @@ window.__ModuleLoader__.load({
1121
1157
  });
1122
1158
  } catch (error) {
1123
1159
  this.flashError(error);
1160
+ throw error;
1124
1161
  }
1125
1162
  }
1126
1163
  async stopAgent(agentId) {
@@ -1437,13 +1474,17 @@ window.__ModuleLoader__.load({
1437
1474
  return this.loadAgentCatalog(event.agentId);
1438
1475
  }
1439
1476
  case "permission/request": {
1440
- const title = typeof event.toolCall?.title === "string" ? String(event.toolCall.title) : this.locale("工具调用", "tool call");
1441
- this.mutate({ permissions: [...this.state.permissions.filter((p) => p.requestId !== event.requestId), {
1477
+ const permission = {
1442
1478
  requestId: event.requestId,
1443
1479
  sessionId: event.sessionId,
1444
- title,
1445
- options: event.options
1446
- }] });
1480
+ ...event.dshSessionId === void 0 ? {} : { dshSessionId: event.dshSessionId },
1481
+ toolName: event.toolName,
1482
+ ...event.reason === void 0 ? {} : { reason: event.reason },
1483
+ options: event.options,
1484
+ timeoutMode: event.timeoutMode,
1485
+ ...event.deadlineAt === void 0 ? {} : { deadlineAt: event.deadlineAt }
1486
+ };
1487
+ this.mutate({ permissions: [...this.state.permissions.filter((p) => p.requestId !== event.requestId), permission] });
1447
1488
  return Promise.resolve();
1448
1489
  }
1449
1490
  case "permission/resolved":
@@ -2380,9 +2421,9 @@ window.__ModuleLoader__.load({
2380
2421
  .ntr-acp-seat:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-active, #0000000d); }
2381
2422
  .ntr-acp-seat:disabled { opacity: 0.5; cursor: default; }
2382
2423
  .ntr-acp-seat.is-open { border-color: var(--dsw-alias-state-business-primary, #2563eb); color: var(--dsw-alias-state-business-primary, #2563eb); }
2383
- .ntr-acp-seat.is-detached { border-color: var(--ntr-warn, #d97706); color: var(--ntr-warn, #d97706); }
2424
+ .ntr-acp-seat.is-detached { border-color: var(--dsw-alias-state-warn-label, #d97706); color: var(--dsw-alias-state-warn-label, #d97706); }
2384
2425
  .ntr-acp-seat-main { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2385
- .ntr-acp-seat-sub { color: var(--dsw-alias-text-secondary, #6b7280); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2426
+ .ntr-acp-seat-sub { color: var(--dsw-alias-label-secondary, #6b7280); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2386
2427
  .ntr-acp-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
2387
2428
  .ntr-acp-tick { margin-left: auto; color: var(--dsw-alias-state-business-primary, #2563eb); }
2388
2429
  .ntr-acp-check { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 13px; height: 13px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); border-radius: 3px; font-size: 10px; line-height: 1; color: var(--dsw-alias-state-business-primary, #2563eb); }
@@ -2391,80 +2432,108 @@ window.__ModuleLoader__.load({
2391
2432
  .ntr-acp-menu-head { padding: 8px 10px 6px; font-weight: 600; display: flex; align-items: center; gap: 6px; }
2392
2433
  .ntr-acp-menu-title { flex: 1; }
2393
2434
  .ntr-acp-menu-spin { opacity: 0.5; }
2394
- .ntr-acp-menu-note { padding: 0 10px 6px; font-size: 11px; color: var(--dsw-alias-text-secondary, #6b7280); line-height: 1.5; }
2395
- .ntr-acp-menu-error { margin: 0 10px 6px; padding: 6px 8px; border-radius: 6px; background: #fee2e2; color: #b91c1c; font-size: 11px; }
2435
+ .ntr-acp-menu-note { padding: 0 10px 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #6b7280); line-height: 1.5; }
2436
+ .ntr-acp-menu-error { margin: 0 10px 6px; padding: 6px 8px; border-radius: 6px; background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #b91c1c) 12%, transparent); color: var(--dsw-alias-state-error-primary, #b91c1c); font-size: 11px; }
2396
2437
  .ntr-acp-menu-body { overflow: auto; padding: 0 4px 6px; }
2397
2438
  .ntr-acp-menu-section { display: flex; flex-direction: column; padding-top: 4px; }
2398
- .ntr-acp-menu-section-title { padding: 6px 8px 4px; font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dsw-alias-text-secondary, #6b7280); }
2439
+ .ntr-acp-menu-section-title { padding: 6px 8px 4px; font-size: 10px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dsw-alias-label-secondary, #6b7280); }
2399
2440
  .ntr-acp-menu-item { display: flex; align-items: center; gap: 6px; font: inherit; font-size: 12px; text-align: left; padding: 6px 8px; border: none; border-radius: 6px; background: none; color: inherit; cursor: pointer; width: 100%; }
2400
2441
  .ntr-acp-menu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-active, #0000000d); }
2401
2442
  .ntr-acp-menu-item:disabled { opacity: 0.45; cursor: default; }
2402
2443
  .ntr-acp-menu-item.is-active { font-weight: 600; }
2403
2444
  .ntr-acp-menu-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2404
- .ntr-acp-menu-item-detail { color: var(--dsw-alias-text-secondary, #6b7280); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: right; }
2405
- .ntr-acp-menu-empty { padding: 10px; text-align: center; color: var(--dsw-alias-text-secondary, #6b7280); }
2445
+ .ntr-acp-menu-item-detail { color: var(--dsw-alias-label-secondary, #6b7280); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: right; }
2446
+ .ntr-acp-menu-empty { padding: 10px; text-align: center; color: var(--dsw-alias-label-secondary, #6b7280); }
2406
2447
  /* ---- ACP view tab ---- */
2407
2448
  .ntr-acp-view { display: flex; flex-direction: column; height: 100%; min-height: 0; font-size: 13px; line-height: 1.6; }
2408
- .ntr-acp-view-head { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); font-size: 12px; color: var(--dsw-alias-text-secondary, #6b7280); }
2449
+ .ntr-acp-view-head { display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); font-size: 12px; color: var(--dsw-alias-label-secondary, #6b7280); }
2409
2450
  .ntr-acp-view-agent { font-weight: 600; color: inherit; }
2410
2451
  .ntr-acp-view-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40%; direction: rtl; text-align: left; }
2411
2452
  .ntr-acp-view-head .ntr-acp-more { margin-left: auto; }
2412
2453
  .ntr-acp-stream { flex: 1; min-height: 0; overflow: auto; padding: 12px 14px 24px; display: flex; flex-direction: column; gap: 10px; }
2413
2454
  .ntr-acp-badge { font-size: 11px; padding: 1px 8px; border-radius: 999px; border: 1px solid transparent; }
2414
- .ntr-acp-badge.is-done { color: #15803d; background: #dcfce7; }
2415
- .ntr-acp-badge.is-busy { color: #1d4ed8; background: #dbeafe; }
2416
- .ntr-acp-badge.is-warn { color: #b45309; background: #fef3c7; }
2417
- .ntr-acp-badge.is-fail { color: #b91c1c; background: #fee2e2; }
2418
- .ntr-acp-badge.is-idle { color: var(--dsw-alias-text-secondary, #6b7280); background: var(--dsw-alias-bg-layer-2, #f3f4f6); }
2455
+ .ntr-acp-badge.is-done { color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #22c55e) 72%, var(--dsw-alias-label-primary, #111827)); background: var(--dsw-alias-state-success-tertiary, #dcfce7); }
2456
+ .ntr-acp-badge.is-busy { color: var(--dsw-alias-state-business-primary, #1d4ed8); background: var(--dsw-alias-state-business-tertiary, #dbeafe); }
2457
+ .ntr-acp-badge.is-warn { color: var(--dsw-alias-state-warn-label, #b45309); background: var(--dsw-alias-state-warn-tertiary, #fef3c7); }
2458
+ .ntr-acp-badge.is-fail { color: var(--dsw-alias-state-error-primary, #b91c1c); background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #b91c1c) 12%, transparent); }
2459
+ .ntr-acp-badge.is-idle { color: var(--dsw-alias-label-secondary, #6b7280); background: var(--dsw-alias-bg-layer-2, #f3f4f6); }
2419
2460
  .ntr-acp-bubble { max-width: 88%; padding: 8px 12px; border-radius: 12px; white-space: pre-wrap; word-break: break-word; }
2420
2461
  .ntr-acp-bubble.is-user { align-self: flex-end; background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #2563eb) 12%, transparent); }
2421
2462
  .ntr-acp-bubble.is-agent { align-self: flex-start; background: var(--dsw-alias-bg-layer-2, #f3f4f6); }
2422
2463
  .ntr-acp-inline-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; padding: 1px 4px; border-radius: 4px; background: color-mix(in srgb, currentColor 10%, transparent); }
2423
2464
  .ntr-acp-code { margin: 6px 0; padding: 8px 10px; border-radius: 8px; background: #0f172a; color: #e2e8f0; overflow: auto; font-size: 12px; line-height: 1.5; }
2424
- .ntr-acp-thought { border-left: 3px solid var(--dsw-alias-border-l2, #d1d5db); padding-left: 8px; color: var(--dsw-alias-text-secondary, #6b7280); font-size: 12px; }
2465
+ .ntr-acp-thought { border-left: 3px solid var(--dsw-alias-border-l2, #d1d5db); padding-left: 8px; color: var(--dsw-alias-label-secondary, #6b7280); font-size: 12px; }
2425
2466
  .ntr-acp-thought > summary { cursor: pointer; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; }
2426
2467
  .ntr-acp-thought-body { white-space: pre-wrap; padding: 4px 0 2px; }
2427
2468
  .ntr-acp-tool { border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 10px; background: var(--dsw-alias-bg-layer-1, #fff); overflow: hidden; }
2428
- .ntr-acp-tool.is-fail { border-color: #fca5a5; }
2469
+ .ntr-acp-tool.is-fail { border-color: var(--dsw-alias-state-error-secondary, #fca5a5); }
2429
2470
  .ntr-acp-tool-head { display: flex; align-items: center; gap: 8px; padding: 7px 10px; font-size: 12px; }
2430
2471
  .ntr-acp-tool-title { flex: 1; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2431
2472
  .ntr-acp-tool-body { margin: 0; padding: 8px 10px; border-top: 1px solid var(--dsw-alias-border-l2, #e5e7eb); background: var(--dsw-alias-bg-layer-2, #f9fafb); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; max-height: 340px; overflow: auto; }
2432
- .ntr-acp-tool-input { background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-text-primary, #111827); max-height: 240px; }
2433
- .ntr-acp-tool-meta { font-size: 11px; color: var(--dsw-alias-text-secondary, #6b7280); }
2473
+ .ntr-acp-tool-input { background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); max-height: 240px; }
2474
+ .ntr-acp-tool-meta { font-size: 11px; color: var(--dsw-alias-label-secondary, #6b7280); }
2434
2475
  .ntr-acp-diff { border-top: 1px solid var(--dsw-alias-border-l2, #e5e7eb); }
2435
2476
  .ntr-acp-diff-head { display: flex; align-items: center; gap: 8px; padding: 5px 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; background: var(--dsw-alias-bg-layer-2, #f9fafb); }
2436
2477
  .ntr-acp-diff-path { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2437
- .ntr-acp-diff-stats .is-add { color: #15803d; }
2438
- .ntr-acp-diff-stats .is-del { color: #b91c1c; }
2478
+ .ntr-acp-diff-stats .is-add { color: var(--dsw-alias-state-success-primary, #15803d); }
2479
+ .ntr-acp-diff-stats .is-del { color: var(--dsw-alias-state-error-primary, #b91c1c); }
2439
2480
  .ntr-acp-diff-body { max-height: 360px; overflow: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; line-height: 1.55; }
2440
2481
  .ntr-acp-diff-row { display: flex; gap: 8px; padding: 0 10px; white-space: pre; }
2441
- .ntr-acp-diff-row.is-add { background: #dcfce7; }
2442
- .ntr-acp-diff-row.is-del { background: #fee2e2; }
2443
- .ntr-acp-diff-row.is-ctx { color: var(--dsw-alias-text-secondary, #6b7280); }
2482
+ .ntr-acp-diff-row.is-add { background: var(--dsw-alias-file-diff-added-bg, #dcfce7); }
2483
+ .ntr-acp-diff-row.is-del { background: var(--dsw-alias-file-diff-deleted-bg, #fee2e2); }
2484
+ .ntr-acp-diff-row.is-ctx { color: var(--dsw-alias-label-secondary, #6b7280); }
2444
2485
  .ntr-acp-diff-sign { width: 1ch; flex: none; opacity: 0.7; }
2445
2486
  .ntr-acp-diff-text { overflow-x: auto; }
2446
- .ntr-acp-diff-note { padding: 4px 10px 6px; font-size: 11px; color: var(--dsw-alias-text-secondary, #6b7280); }
2487
+ .ntr-acp-diff-note { padding: 4px 10px 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #6b7280); }
2447
2488
  .ntr-acp-more { font: inherit; font-size: 11.5px; padding: 3px 10px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-2, #f3f4f6); color: inherit; cursor: pointer; }
2448
2489
  .ntr-acp-more:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-active, #0000000d); }
2449
2490
  .ntr-acp-more:disabled { opacity: 0.5; cursor: default; }
2450
- .ntr-acp-more.is-primary { border-color: transparent; background: var(--dsw-alias-button-primary-fill, #2563eb); color: var(--dsw-alias-button-primary-invert, #fff); }
2491
+ .ntr-acp-more.is-primary { border-color: transparent; background: var(--dsw-alias-button-primary-fill, #2563eb); color: var(--dsw-alias-label-primary-foreground, #fff); }
2451
2492
  .ntr-acp-tool > .ntr-acp-more, .ntr-acp-empty-actions .ntr-acp-more { margin: 6px 10px; }
2452
2493
  .ntr-acp-plan { border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 10px; padding: 8px 10px; background: var(--dsw-alias-bg-layer-1, #fff); }
2453
- .ntr-acp-plan-head { font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dsw-alias-text-secondary, #6b7280); margin-bottom: 4px; }
2494
+ .ntr-acp-plan-head { font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dsw-alias-label-secondary, #6b7280); margin-bottom: 4px; }
2454
2495
  .ntr-acp-plan-row { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; padding: 1px 0; }
2455
- .ntr-acp-plan-row.is-completed { color: var(--dsw-alias-text-secondary, #6b7280); text-decoration: line-through; }
2496
+ .ntr-acp-plan-row.is-completed { color: var(--dsw-alias-label-secondary, #6b7280); text-decoration: line-through; }
2456
2497
  .ntr-acp-plan-row.is-in_progress { font-weight: 600; }
2457
2498
  .ntr-acp-plan-glyph { width: 1ch; flex: none; }
2458
- .ntr-acp-plan-prio { margin-left: auto; font-size: 10px; text-transform: uppercase; color: var(--dsw-alias-text-secondary, #6b7280); }
2499
+ .ntr-acp-plan-prio { margin-left: auto; font-size: 10px; text-transform: uppercase; color: var(--dsw-alias-label-secondary, #6b7280); }
2459
2500
  .ntr-acp-notice { padding: 6px 10px; border-radius: 8px; font-size: 12px; }
2460
- .ntr-acp-notice.is-error { background: #fee2e2; color: #b91c1c; }
2461
- .ntr-acp-notice.is-info { background: var(--dsw-alias-bg-layer-2, #f3f4f6); color: var(--dsw-alias-text-secondary, #6b7280); }
2501
+ .ntr-acp-notice.is-error { background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #b91c1c) 12%, transparent); color: var(--dsw-alias-state-error-primary, #b91c1c); }
2502
+ .ntr-acp-notice.is-info { background: var(--dsw-alias-bg-layer-2, #f3f4f6); color: var(--dsw-alias-label-secondary, #6b7280); }
2462
2503
  .ntr-acp-empty { margin: auto; max-width: 460px; text-align: center; display: flex; flex-direction: column; gap: 10px; padding: 24px; }
2463
2504
  .ntr-acp-empty-title { font-size: 14px; font-weight: 600; }
2464
- .ntr-acp-empty-body { margin: 0; color: var(--dsw-alias-text-secondary, #6b7280); font-size: 12.5px; line-height: 1.7; }
2465
- .ntr-acp-empty-hint { color: var(--dsw-alias-text-secondary, #6b7280); font-size: 12px; }
2466
- .ntr-acp-seat-diag { font-family: var(--dsw-alias-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); opacity: 0.72; word-break: break-word; }
2505
+ .ntr-acp-empty-body { margin: 0; color: var(--dsw-alias-label-secondary, #6b7280); font-size: 12.5px; line-height: 1.7; }
2506
+ .ntr-acp-empty-hint { color: var(--dsw-alias-label-secondary, #6b7280); font-size: 12px; }
2507
+ .ntr-acp-seat-diag { font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace); opacity: 0.72; word-break: break-word; }
2467
2508
  .ntr-acp-empty-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
2509
+ /* ---- Pending permission asks (conversation.input.dock) ---- */
2510
+ /* The dock slot is the full conversation column, but the composer card inside
2511
+ it is narrower (InputBar: side clearance + card max width). Mirror that
2512
+ formula so each ask lines up with the input box instead of spanning the
2513
+ column. The list owns a bounded height and scrolls so a burst of asks cannot
2514
+ push the composer off screen. */
2515
+ .ntr-permission-dock {
2516
+ box-sizing: border-box;
2517
+ width: calc(100% - var(--dsh-composer-side-clearance, 16px) - var(--dsh-composer-side-clearance, 16px));
2518
+ max-width: var(--dsh-composer-card-max-width, 920px);
2519
+ margin: 0 auto;
2520
+ display: flex; flex-direction: column; gap: 6px;
2521
+ max-height: min(38vh, 320px); overflow-y: auto;
2522
+ font-size: 12px; line-height: 1.5;
2523
+ }
2524
+ .ntr-permission-card { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); border-radius: 10px; background: var(--dsw-alias-bg-layer-1, #fff); }
2525
+ .ntr-permission-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
2526
+ .ntr-permission-tool { flex: none; font-weight: 600; }
2527
+ .ntr-permission-reason { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary, #6b7280); }
2528
+ .ntr-permission-deadline { flex: none; font-size: 11px; color: var(--dsw-alias-label-secondary, #6b7280); font-variant-numeric: tabular-nums; }
2529
+ .ntr-permission-deadline.is-expired { color: var(--dsw-alias-state-warn-label, #b45309); font-weight: 600; }
2530
+ .ntr-permission-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
2531
+ .ntr-permission-option, .ntr-permission-cancel { font: inherit; font-size: 12px; line-height: 1.4; padding: 4px 10px; border-radius: 7px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-2, #f3f4f6); color: inherit; cursor: pointer; white-space: normal; text-align: left; }
2532
+ .ntr-permission-option:hover:not(:disabled), .ntr-permission-cancel:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-active, #0000000d); }
2533
+ .ntr-permission-option:disabled, .ntr-permission-cancel:disabled { opacity: 0.5; cursor: default; }
2534
+ .ntr-permission-option:focus-visible, .ntr-permission-cancel:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #2563eb); outline-offset: 2px; }
2535
+ .ntr-permission-cancel { margin-left: auto; color: var(--dsw-alias-label-secondary, #6b7280); }
2536
+ .ntr-permission-error { padding: 4px 8px; border-radius: 6px; background: color-mix(in srgb, var(--dsw-alias-state-error-primary, #b91c1c) 12%, transparent); color: var(--dsw-alias-state-error-primary, #b91c1c); font-size: 11px; word-break: break-word; }
2468
2537
  /* ---- NTR settings dialog (shell.overlay) ---- */
2469
2538
  /* Frame-wide surface opened from the agent base's empty-roster menu. The
2470
2539
  overlay layer itself is click-through, so the layer opts back in and blocks
@@ -2501,11 +2570,11 @@ window.__ModuleLoader__.load({
2501
2570
  * React surface for ACP: the settings configuration section.
2502
2571
  *
2503
2572
  * Scope is deliberately narrow — this is a CONFIGURATION surface. Runtime
2504
- * interaction lives in the conversation: permission asks are answered in the
2505
- * chat composer through the host approval chain. The ledger card answers
2506
- * whatever did NOT reach the conversation — a host without the chain, a
2507
- * session with no chat identity behind it, or an ask whose native route
2508
- * degraded to unavailable (see `permissionLedgerCard`).
2573
+ * interaction lives in the conversation: every permission ask with a chat
2574
+ * identity is answered on a card above that conversation's composer
2575
+ * (`permission-dock.tsx`). The ledger card here is the cross-session mirror —
2576
+ * asks whose conversation is not open, or which have no chat identity at all —
2577
+ * and is where those are answered (see `permissionLedgerCard`).
2509
2578
  *
2510
2579
  * Each configured agent is one compact row (status + current model + badges)
2511
2580
  * that expands into the full connection form and model catalog. The row stays
@@ -2537,22 +2606,40 @@ window.__ModuleLoader__.load({
2537
2606
  --ntr-radius: 10px; --ntr-radius-sm: 7px;
2538
2607
  --ntr-font: 13px; --ntr-font-sm: 12px; --ntr-font-xs: 11px;
2539
2608
  --ntr-line: 1px solid var(--dsw-alias-border-l2, #e5e7eb);
2540
- --ntr-muted: var(--dsw-alias-text-secondary, #6b7280);
2541
- --ntr-danger: #dc2626; --ntr-warn: #b45309;
2609
+ --ntr-muted: var(--dsw-alias-label-secondary, #6b7280);
2610
+ --ntr-danger: var(--dsw-alias-state-error-primary, #dc2626);
2611
+ --ntr-warn: var(--dsw-alias-state-warn-label, #b45309);
2612
+ --ntr-success: color-mix(in srgb, var(--dsw-alias-state-success-primary, #16a34a) 72%, var(--dsw-alias-label-primary, #111827));
2542
2613
  --ntr-accent: var(--dsw-alias-button-primary-fill, #2563eb);
2543
2614
  width: 100%; max-width: 960px; box-sizing: border-box; margin: 0 auto;
2544
- padding: 20px; overflow: auto; font-size: var(--ntr-font); line-height: 1.55;
2615
+ padding: 28px 32px; min-width: 0; container-type: inline-size; font-size: var(--ntr-font); line-height: 1.55;
2545
2616
  }
2546
- .dwa-root button, .dwa-root input, .dwa-root select { font-family: inherit; }
2617
+ .dwa-root [hidden] { display: none !important; }
2618
+ .dwa-root button, .dwa-root input, .dwa-root select, .dwa-root textarea { font-family: inherit; }
2619
+ .dwa-root input, .dwa-root textarea { min-width: 0; }
2620
+ .dwa-tabs { display: flex; gap: 24px; flex-wrap: wrap; border-bottom: var(--ntr-line); margin-bottom: 24px; }
2621
+ .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; }
2622
+ .dwa-tabs > button:hover { color: var(--dsw-alias-label-primary, #111827); }
2623
+ .dwa-tabs > button[aria-pressed="true"] { color: var(--dsw-alias-label-primary, #111827); border-bottom-color: currentColor; font-weight: 550; }
2624
+ .dwa-detail-tabs { margin: 4px 0 20px; gap: 20px; }
2625
+ .dwa-detail-tabs > button { font-size: var(--ntr-font-sm); }
2626
+ .dwa-row-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
2627
+ .dwa-notice { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
2628
+ .dwa-notice > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
2629
+ .dwa-empty { text-align: center; padding: 40px 16px; color: var(--ntr-muted); }
2630
+ .dwa-empty h3 { font-size: 16px; font-weight: 550; color: var(--dsw-alias-label-primary, #111827); margin: 0 0 8px; }
2631
+ .dwa-empty .dwa-btn { margin-top: 20px; }
2632
+ .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; }
2633
+ .dwa-health-status { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
2634
+ .dwa-health-status > span:last-child:not(:nth-child(2)) { padding-left: 12px; border-left: var(--ntr-line); }
2635
+ .dwa-back { margin-bottom: 16px; }
2547
2636
  .dwa-root button:focus-visible, .dwa-root input:focus-visible, .dwa-root select:focus-visible {
2548
2637
  outline: 2px solid var(--ntr-accent); outline-offset: 2px;
2549
2638
  }
2550
- .dwa-page-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 20px; }
2639
+ .dwa-page-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
2551
2640
  .dwa-page-heading { min-width: 0; }
2552
- .dwa-page-title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 650; letter-spacing: -0.02em; }
2641
+ .dwa-page-title { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 550; letter-spacing: -0.02em; }
2553
2642
  .dwa-page-description { margin: 5px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-sm); }
2554
- .dwa-page-status { display: flex; align-items: center; gap: 8px; margin-top: 10px; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2555
- .dwa-page-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
2556
2643
  .dwa-btn {
2557
2644
  min-height: 32px; box-sizing: border-box; font: inherit; border-radius: var(--ntr-radius-sm);
2558
2645
  border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff);
@@ -2562,58 +2649,58 @@ window.__ModuleLoader__.load({
2562
2649
  }
2563
2650
  .dwa-btn:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); border-color: var(--dsw-alias-border-l1, #9ca3af); }
2564
2651
  .dwa-btn:disabled { opacity: 0.5; cursor: not-allowed; }
2565
- .dwa-btn-primary { background: var(--ntr-accent); color: var(--dsw-alias-button-primary-invert, #fff); border-color: transparent; font-weight: 550; }
2652
+ .dwa-btn-primary { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); border-color: transparent; font-weight: 550; }
2566
2653
  .dwa-btn-primary:hover { background: var(--ntr-accent); border-color: transparent; opacity: 0.88; }
2567
2654
  .dwa-btn-secondary { background: var(--dsw-alias-bg-layer-2, #f3f4f6); }
2568
- .dwa-btn-danger { color: var(--ntr-danger); border-color: #fca5a5; }
2569
- .dwa-btn-danger:hover { background: #fee2e2; }
2655
+ .dwa-btn-quiet { background: transparent; border-color: transparent; color: var(--ntr-muted); }
2656
+ .dwa-btn-quiet:hover { border-color: transparent; color: var(--dsw-alias-label-primary, #111827); }
2657
+ .dwa-btn-danger { color: var(--ntr-danger); border-color: var(--dsw-alias-state-error-secondary, #fca5a5); }
2658
+ .dwa-btn-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, #fee2e2); }
2570
2659
  .dwa-section { margin-bottom: 24px; }
2571
2660
  .dwa-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
2572
- .dwa-section-title { font-size: 14px; font-weight: 650; margin: 0; display: flex; align-items: center; gap: 8px; }
2661
+ .dwa-section-title { font-size: 13px; font-weight: 550; margin: 0; display: flex; align-items: center; gap: 8px; }
2573
2662
  .dwa-section-copy { margin: 3px 0 0; font-size: var(--ntr-font-xs); color: var(--ntr-muted); }
2574
2663
  .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; }
2575
- .dwa-card-warn { border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-secondary, #fffbeb); }
2664
+ .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); }
2576
2665
  .dwa-mode-banner { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 5px 16px; align-items: start; margin-bottom: 24px; padding: 14px 16px; }
2577
2666
  .dwa-mode-title { display: flex; align-items: center; gap: 8px; font-weight: 650; }
2578
2667
  .dwa-mode-copy { grid-column: 1 / -1; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2579
2668
  .dwa-mode-source { display: block; margin-top: 5px; overflow-wrap: anywhere; opacity: 0.85; }
2580
2669
  .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; }
2581
- .dwa-badge-success { background: #22c55e1c; color: #15803d; }
2582
- .dwa-badge-warn { background: #f59e0b1f; color: #b45309; }
2583
- .dwa-badge-info { background: #3b82f61a; color: #2563eb; }
2670
+ .dwa-badge-success { background: var(--dsw-alias-state-success-tertiary, #dcfce7); color: var(--ntr-success); }
2671
+ .dwa-badge-warn { background: var(--dsw-alias-state-warn-tertiary, #fef3c7); color: var(--ntr-warn); }
2672
+ .dwa-badge-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
2584
2673
  .dwa-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
2585
- .dwa-agent-list { display: flex; flex-direction: column; gap: 8px; }
2586
- .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; }
2587
- .dwa-agent-card.is-open { border-color: var(--dsw-alias-border-l1, #c7ccd1); }
2588
- .dwa-agent-row { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 12px 14px; }
2674
+ .dwa-agent-list { display: flex; flex-direction: column; border-top: var(--ntr-line); }
2675
+ .dwa-agent-card { border-bottom: var(--ntr-line); min-width: 0; }
2676
+ .dwa-agent-row { display: grid; grid-template-columns: 38px minmax(0, 1fr) auto; align-items: center; gap: 14px; padding: 16px 0; }
2677
+ .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); }
2589
2678
  .dwa-caret { font-size: 14px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; width: 1em; }
2590
2679
  .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; }
2591
2680
  .dwa-caret-btn:hover { background: var(--dsw-alias-interactive-bg-active, #e5e7eb); color: inherit; }
2592
2681
  .dwa-agent-summary { min-width: 0; }
2593
2682
  .dwa-agent-titleline { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
2594
- .dwa-agent-name { font-size: 13px; font-weight: 650; overflow-wrap: anywhere; }
2683
+ .dwa-agent-name { font-size: 13px; font-weight: 550; overflow-wrap: anywhere; }
2595
2684
  .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); }
2596
2685
  .dwa-agent-meta-separator { opacity: 0.45; }
2597
- .dwa-agent-body { border-top: var(--ntr-line); padding: 0 16px 16px; }
2598
- .dwa-subsection { padding-top: 16px; }
2599
- .dwa-subsection + .dwa-subsection { margin-top: 16px; border-top: var(--ntr-line); }
2600
- .dwa-subsection-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
2601
- .dwa-subsection-title { margin: 0; font-size: 13px; font-weight: 650; }
2602
- .dwa-subsection-copy { margin: 3px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2603
- .dwa-form-row { display: flex; gap: var(--ntr-gap-3); margin-bottom: 14px; align-items: flex-start; }
2686
+ .dwa-agent-body { padding: 0 0 24px 52px; }
2687
+ .dwa-subsection { min-width: 0; }
2688
+ .dwa-form-row { display: flex; gap: var(--ntr-gap-3); margin-bottom: 14px; align-items: stretch; }
2604
2689
  .dwa-form-col { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
2605
- .dwa-form-label { font-size: var(--ntr-font-sm); font-weight: 600; color: var(--dsw-alias-text-primary, #374151); }
2690
+ .dwa-form-label { font-size: var(--ntr-font-sm); font-weight: 600; color: var(--dsw-alias-label-primary, #374151); }
2606
2691
  .dwa-hint { font-size: var(--ntr-font-xs); line-height: 1.55; color: var(--ntr-muted); }
2607
2692
  .dwa-error { font-size: var(--ntr-font-xs); color: var(--ntr-danger); }
2608
2693
  .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; }
2609
2694
  .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; }
2610
2695
  .dwa-input:hover, .dwa-select:hover { border-color: var(--dsw-alias-border-l1, #9ca3af); }
2611
2696
  .dwa-input:focus, .dwa-select:focus { border-color: var(--ntr-accent); }
2612
- .dwa-input-mono, .dwa-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
2697
+ .dwa-input-mono, .dwa-mono { font-family: var(--ds-font-family-code, ui-monospace), SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
2613
2698
  .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; }
2614
2699
  .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; }
2615
2700
  .dwa-form-actions { display: flex; align-items: center; gap: 8px; margin-top: 16px; padding-top: 14px; border-top: var(--ntr-line); flex-wrap: wrap; }
2616
2701
  .dwa-form-actions .dwa-btn-primary { min-width: 88px; }
2702
+ .dwa-form-actions > .dwa-hint:first-child { margin-right: auto; }
2703
+ .dwa-form-actions > .dwa-hint:last-child:not(:first-child) { width: 100%; }
2617
2704
  .dwa-menu-wrap { position: relative; }
2618
2705
  .dwa-menu-wrap > .dwa-btn { width: 32px; padding: 0; }
2619
2706
  .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; }
@@ -2621,13 +2708,13 @@ window.__ModuleLoader__.load({
2621
2708
  .dwa-menu-item:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2622
2709
  .dwa-menu-item-danger { color: var(--ntr-danger); }
2623
2710
  .dwa-menu-confirm { display: flex; align-items: center; gap: 4px; padding: 4px; }
2624
- .dwa-add-panel { border: var(--ntr-line); border-radius: var(--ntr-radius); background: var(--dsw-alias-bg-layer-1, #fff); }
2625
- .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); }
2626
- .dwa-add-toggle:hover { background: var(--dsw-alias-interactive-bg-active, #f9fafb); }
2627
- .dwa-add-content { border-top: var(--ntr-line); padding: 14px 16px 16px; }
2711
+ .dwa-add-panel .dwa-agent-row { display: none; }
2712
+ .dwa-add-panel .dwa-agent-body { padding: 0 0 24px; }
2713
+ .dwa-add-panel .dwa-agent-card { border: 0; }
2714
+ .dwa-add-content { padding-top: 10px; }
2628
2715
  .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; }
2629
2716
  .dwa-chip:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2630
- .dwa-chip-active { border-color: var(--ntr-accent); background: #3b82f614; color: #2563eb; font-weight: 550; }
2717
+ .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; }
2631
2718
  .dwa-chip-list { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 12px; }
2632
2719
  .dwa-switch { display: inline-flex; align-items: center; gap: 7px; font-size: var(--ntr-font-sm); cursor: pointer; user-select: none; }
2633
2720
  .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); }
@@ -2640,7 +2727,7 @@ window.__ModuleLoader__.load({
2640
2727
  /* Tall enough for a comfortable selection (about ten rows) before scrolling;
2641
2728
  the cap keeps the two stacked panes from pushing the rest of the card away. */
2642
2729
  .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; }
2643
- .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); }
2730
+ .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); }
2644
2731
  .dwa-model-pane-head .dwa-btn { margin-left: auto; min-height: 26px; padding: 2px 8px; font-size: var(--ntr-font-xs); }
2645
2732
  .dwa-model-pane-head .dwa-caret-btn { flex: none; width: 22px; height: 22px; background: transparent; }
2646
2733
  .dwa-model-pane-head .dwa-caret-btn:hover { background: var(--dsw-alias-interactive-bg-active, #e5e7eb); }
@@ -2656,13 +2743,13 @@ window.__ModuleLoader__.load({
2656
2743
  .dwa-model-status { margin-left: auto; white-space: nowrap; }
2657
2744
  .dwa-model-search { width: 200px; min-height: 30px; }
2658
2745
  .dwa-model-empty { padding: 6px; }
2659
- .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; }
2746
+ .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; }
2660
2747
  .dwa-model-row:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2661
- .dwa-model-row.is-on { background: #3b82f614; color: #2563eb; }
2748
+ .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); }
2662
2749
  .dwa-model-row-mark { width: 12px; flex: none; text-align: center; opacity: 0.75; }
2663
2750
  .dwa-model-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2664
2751
  /* Name and credit keep their room; the id is the only cell that collapses. */
2665
- .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; }
2752
+ .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; }
2666
2753
  /* The trailing 14px is reserved for the fade mask: while the id fits, the
2667
2754
  gradient only covers empty padding; once it overflows, its tail dissolves
2668
2755
  instead of ending in a hard ellipsis. armModelIdMarquee reads the same
@@ -2676,12 +2763,12 @@ window.__ModuleLoader__.load({
2676
2763
  }
2677
2764
  .dwa-model-row-remove { flex: none; opacity: 0.5; padding: 0 2px; }
2678
2765
  .dwa-model-row:hover .dwa-model-row-remove { opacity: 1; }
2679
- .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; }
2680
- .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; }
2681
- .dwa-seg > button + button { border-left: 1px solid var(--dsw-alias-border-l2, #d1d5db); }
2766
+ .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; }
2767
+ .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; }
2682
2768
  .dwa-seg > button:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2683
- .dwa-seg > button.is-on { background: var(--ntr-accent); color: var(--dsw-alias-button-primary-invert, #fff); font-weight: 550; }
2684
- .dwa-preference-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 0; }
2769
+ .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; }
2770
+ .dwa-preferences { border-top: var(--ntr-line); }
2771
+ .dwa-preference-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 20px 0; }
2685
2772
  .dwa-preference-row + .dwa-preference-row { border-top: var(--ntr-line); }
2686
2773
  /* A control that needs the whole card: the label sits above it, not beside
2687
2774
  it. A right-aligned segmented control squeezed its labels into two lines
@@ -2706,13 +2793,20 @@ window.__ModuleLoader__.load({
2706
2793
  .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; }
2707
2794
  .dwa-deadline-reset { display: flex; justify-content: flex-end; margin-top: 8px; }
2708
2795
  @media (max-width: 640px) {
2709
- .dwa-root { padding: 14px; }
2710
- .dwa-page-header { flex-direction: column; }
2711
- .dwa-page-actions { width: 100%; }
2712
- .dwa-page-actions .dwa-btn { width: 100%; }
2796
+ .dwa-root { padding: 20px 16px; }
2797
+ }
2798
+ @container (max-width: 640px) {
2799
+ .dwa-page-header { align-items: flex-start; flex-wrap: wrap; }
2800
+ .dwa-agent-body { padding-left: 0; }
2801
+ .dwa-agent-row { grid-template-columns: 32px minmax(0, 1fr) auto; gap: 10px; }
2802
+ .dwa-agent-avatar { width: 32px; height: 32px; }
2803
+ .dwa-section-heading { flex-wrap: wrap; }
2804
+ .dwa-tabs { gap: 20px; }
2805
+ .dwa-notice { flex-wrap: wrap; }
2806
+ .dwa-seg-wide > button { padding-inline: 5px; }
2713
2807
  .dwa-model-toolbar-actions { width: 100%; }
2714
2808
  .dwa-model-search { flex: 1 1 auto; width: auto; }
2715
- .dwa-subsection-head, .dwa-preference-row { align-items: flex-start; flex-direction: column; }
2809
+ .dwa-preference-row { align-items: flex-start; flex-direction: column; }
2716
2810
  .dwa-default-hint { align-items: flex-start; flex-direction: column; }
2717
2811
  /* The slider's scale: up to eight rungs with full unit words ("15 分钟")
2718
2812
  need a smaller face before they can collide on a narrow panel. The rung
@@ -2739,19 +2833,19 @@ window.__ModuleLoader__.load({
2739
2833
  function stateVisual(state, acp) {
2740
2834
  switch (state) {
2741
2835
  case "ready": return {
2742
- color: "#22c55e",
2836
+ color: "var(--dsw-alias-state-success-primary, #22c55e)",
2743
2837
  text: acp.label("已连接", "Connected")
2744
2838
  };
2745
2839
  case "starting": return {
2746
- color: "#eab308",
2840
+ color: "var(--dsw-alias-state-warn-primary, #eab308)",
2747
2841
  text: acp.label("启动中", "Starting")
2748
2842
  };
2749
2843
  case "error": return {
2750
- color: "#ef4444",
2844
+ color: "var(--dsw-alias-state-error-primary, #ef4444)",
2751
2845
  text: acp.label("错误", "Error")
2752
2846
  };
2753
2847
  default: return {
2754
- color: "#9ca3af",
2848
+ color: "var(--dsw-alias-state-idle-primary, #9ca3af)",
2755
2849
  text: acp.label("已配置", "Configured"),
2756
2850
  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.")
2757
2851
  };
@@ -2924,25 +3018,15 @@ window.__ModuleLoader__.load({
2924
3018
  /**
2925
3019
  * The settings panel's permission-ledger rule.
2926
3020
  *
2927
- * A pending ask ALWAYS gets an answerable card here, whatever `approvalRouted`
2928
- * says. That flag reports that the host HAS an approval service — it does not
2929
- * promise THIS ask reached the conversation. An ask lands in `ui.permissions`
2930
- * only on the ledger path (`AgentBridge.handlePermissionRequest` broadcasts
2931
- * `permission/request` there and nowhere else), so a pending entry is by
2932
- * definition a question the conversation is not showing. The previous rule
2933
- * (`approvalRouted` → a read-only count, no buttons) hid the only answer
2934
- * entry exactly when the native route had degraded to unavailable, and the
2935
- * ask then hung until its timeout.
2936
- *
2937
- * `fellBackFromConversation` is the case the card has to explain: the host
2938
- * can route asks, and this one still did not make it.
3021
+ * Pending asks are shown in the conversation card while that conversation is
3022
+ * open; this card is the cross-session mirror for the ones it cannot show —
3023
+ * asks belonging to another conversation, and `/acp` RPC-only asks with no
3024
+ * chat identity at all. It therefore appears whenever ANY ask is pending: a
3025
+ * pending entry with no conversation card would otherwise have no answer
3026
+ * surface anywhere.
2939
3027
  */
2940
3028
  function permissionLedgerCard(ui) {
2941
- const visible = ui.permissions.length > 0;
2942
- return {
2943
- visible,
2944
- fellBackFromConversation: visible && ui.approvalRouted
2945
- };
3029
+ return { visible: ui.permissions.length > 0 };
2946
3030
  }
2947
3031
  /**
2948
3032
  * The ONE wait window, as the rungs the slider snaps to.
@@ -3210,11 +3294,7 @@ window.__ModuleLoader__.load({
3210
3294
  children: acp.label("权限请求的等待时长与超时处理方式。", "How long to wait for a permission request, and what happens when nobody answers.")
3211
3295
  })] })
3212
3296
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3213
- className: "dwa-card",
3214
- style: {
3215
- paddingTop: 4,
3216
- paddingBottom: 4
3217
- },
3297
+ className: "dwa-preferences",
3218
3298
  children: [
3219
3299
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeadlineModeBlock, {
3220
3300
  acp,
@@ -3240,17 +3320,21 @@ window.__ModuleLoader__.load({
3240
3320
  })]
3241
3321
  });
3242
3322
  }
3243
- /** Settings configuration section for ACP agents. */
3244
3323
  function AcpSettingsSection(props) {
3245
3324
  const acp = props.acp;
3246
3325
  ensureStyle();
3247
3326
  const ui = useUi$1(acp);
3248
3327
  const ledger = permissionLedgerCard(ui);
3249
- const [addOpen, setAddOpen] = (0, react.useState)(true);
3328
+ const [page, setPage] = (0, react.useState)("agents");
3329
+ const [addOpen, setAddOpen] = (0, react.useState)(false);
3250
3330
  const [addTarget, setAddTarget] = (0, react.useState)(void 0);
3251
- const [createNonce, setCreateNonce] = (0, react.useState)(0);
3252
3331
  const { configured, available } = partitionAgents(ui.detectedAgents, ui.agents);
3253
3332
  const selected = addTarget ?? available[0] ?? NEW_AGENT_SEED;
3333
+ const openAdd = () => {
3334
+ setAddOpen(true);
3335
+ setPage("add");
3336
+ };
3337
+ const presetProblem = ui.preset !== void 0 && (!ui.preset.installed || ui.preset.problem !== void 0);
3254
3338
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3255
3339
  className: "dwa-root",
3256
3340
  children: [
@@ -3258,39 +3342,18 @@ window.__ModuleLoader__.load({
3258
3342
  className: "dwa-page-header",
3259
3343
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3260
3344
  className: "dwa-page-heading",
3261
- children: [
3262
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3263
- className: "dwa-page-title",
3264
- children: acp.label("NTR 智能体", "NTR Agents")
3265
- }),
3266
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3267
- className: "dwa-page-description",
3268
- children: acp.label("管理智能体连接、可用模型与对话显示偏好。", "Manage agent connections, available models, and conversation display preferences.")
3269
- }),
3270
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3271
- className: "dwa-page-status",
3272
- children: [
3273
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3274
- className: "dwa-dot",
3275
- style: { background: ui.stream === "live" ? "#22c55e" : ui.stream === "down" ? "#ef4444" : "#9ca3af" },
3276
- title: ui.stream
3277
- }),
3278
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: streamText(ui.stream, acp) }),
3279
- ui.detectSummary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: ["· ", ui.detectSummary] })
3280
- ]
3281
- })
3282
- ]
3283
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3284
- className: "dwa-page-actions",
3285
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3286
- className: "dwa-btn",
3287
- onClick: () => {
3288
- acp.resync();
3289
- acp.detectAgents();
3290
- },
3291
- disabled: ui.detecting,
3292
- children: ui.detecting ? acp.label("检测中...", "Detecting...") : acp.label("重新检测与刷新", "Re-detect & Refresh")
3293
- })
3345
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3346
+ className: "dwa-page-title",
3347
+ children: acp.label("NTR 设置", "NTR settings")
3348
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3349
+ className: "dwa-page-description",
3350
+ children: acp.label("连接智能体,设置你的对话偏好。", "Connect agents and set your conversation preferences.")
3351
+ })]
3352
+ }), page === "agents" && configured.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3353
+ type: "button",
3354
+ className: "dwa-btn dwa-btn-primary",
3355
+ onClick: openAdd,
3356
+ children: acp.label("添加智能体", "Add agent")
3294
3357
  })]
3295
3358
  }),
3296
3359
  ledger.visible && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3300,14 +3363,14 @@ window.__ModuleLoader__.load({
3300
3363
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3301
3364
  style: {
3302
3365
  fontWeight: 600,
3303
- marginBottom: 8
3366
+ marginBottom: 4
3304
3367
  },
3305
3368
  children: acp.label("待处理权限请求", "Pending Permission Requests")
3306
3369
  }),
3307
- ledger.fellBackFromConversation && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3308
3371
  className: "dwa-hint",
3309
3372
  style: { marginBottom: 8 },
3310
- 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.")
3373
+ children: acp.label("对话打开时在那边的卡片上回答;这里兜底显示其他会话的询问。", "Answer in the conversation card while that conversation is open; this list covers the asks whose conversation is not.")
3311
3374
  }),
3312
3375
  ui.permissions.map((permission) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3313
3376
  style: {
@@ -3318,22 +3381,34 @@ window.__ModuleLoader__.load({
3318
3381
  flexWrap: "wrap"
3319
3382
  },
3320
3383
  children: [
3321
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3322
3385
  style: {
3323
3386
  flex: 1,
3324
3387
  minWidth: 160
3325
3388
  },
3326
- children: permission.title
3389
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3390
+ style: { fontWeight: 600 },
3391
+ children: permission.toolName
3392
+ }), permission.reason !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3393
+ style: { color: "var(--ntr-muted)" },
3394
+ children: [" · ", permission.reason]
3395
+ })]
3327
3396
  }),
3328
3397
  permission.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3329
- className: "dwa-btn dwa-btn-primary",
3330
- onClick: () => void acp.respondPermission(permission.requestId, option.optionId),
3398
+ type: "button",
3399
+ className: "dwa-btn",
3400
+ onClick: () => {
3401
+ acp.respondPermission(permission.requestId, option.optionId).catch(() => void 0);
3402
+ },
3331
3403
  children: option.name
3332
3404
  }, option.optionId)),
3333
3405
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3334
- className: "dwa-btn",
3335
- onClick: () => void acp.respondPermission(permission.requestId),
3336
- children: acp.label("拒绝", "Dismiss")
3406
+ type: "button",
3407
+ className: "dwa-btn dwa-btn-quiet",
3408
+ onClick: () => {
3409
+ acp.respondPermission(permission.requestId).catch(() => void 0);
3410
+ },
3411
+ children: acp.label("取消", "Cancel")
3337
3412
  })
3338
3413
  ]
3339
3414
  }, permission.requestId))
@@ -3351,12 +3426,81 @@ window.__ModuleLoader__.load({
3351
3426
  style: { flex: 1 },
3352
3427
  children: ui.notice
3353
3428
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3354
- className: "dwa-btn",
3429
+ type: "button",
3430
+ className: "dwa-btn dwa-btn-quiet",
3431
+ "aria-label": acp.label("关闭通知", "Dismiss notice"),
3355
3432
  onClick: () => acp.clearNotice(),
3356
3433
  children: "✕"
3357
3434
  })]
3358
3435
  }),
3359
- ui.preset !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3436
+ presetProblem && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3437
+ className: "dwa-card dwa-card-warn dwa-notice",
3438
+ role: "status",
3439
+ 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", {
3440
+ type: "button",
3441
+ className: "dwa-btn",
3442
+ onClick: () => setPage("diagnostics"),
3443
+ children: acp.label("查看诊断", "View diagnostics")
3444
+ })]
3445
+ }),
3446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
3447
+ className: "dwa-tabs",
3448
+ "aria-label": acp.label("NTR 设置分类", "NTR settings categories"),
3449
+ children: [
3450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3451
+ type: "button",
3452
+ "aria-pressed": page === "agents",
3453
+ onClick: () => setPage("agents"),
3454
+ children: acp.label("智能体", "Agents")
3455
+ }),
3456
+ ui.approvalDeadlines !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3457
+ type: "button",
3458
+ "aria-pressed": page === "approvals",
3459
+ onClick: () => setPage("approvals"),
3460
+ children: acp.label("审批时限", "Approval deadlines")
3461
+ }),
3462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3463
+ type: "button",
3464
+ "aria-pressed": page === "appearance",
3465
+ onClick: () => setPage("appearance"),
3466
+ children: acp.label("对话外观", "Appearance")
3467
+ })
3468
+ ]
3469
+ }),
3470
+ page === "diagnostics" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3471
+ className: "dwa-section",
3472
+ children: [
3473
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3474
+ className: "dwa-section-title",
3475
+ children: acp.label("连接诊断", "Connection diagnostics")
3476
+ }),
3477
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3478
+ className: "dwa-preference-row",
3479
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label("宿主事件流", "Host event stream") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3480
+ className: "dwa-hint",
3481
+ children: streamText(ui.stream, acp)
3482
+ })]
3483
+ }),
3484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3485
+ className: "dwa-row-actions",
3486
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3487
+ type: "button",
3488
+ className: "dwa-btn",
3489
+ onClick: () => {
3490
+ acp.resync();
3491
+ acp.detectAgents();
3492
+ },
3493
+ disabled: ui.detecting,
3494
+ children: ui.detecting ? acp.label("检测中...", "Detecting...") : acp.label("重新检测与刷新", "Re-detect & Refresh")
3495
+ }), ui.detectSummary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3496
+ className: "dwa-hint",
3497
+ role: "status",
3498
+ children: ui.detectSummary
3499
+ })]
3500
+ })
3501
+ ]
3502
+ }),
3503
+ page === "diagnostics" && ui.preset !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3360
3504
  className: "dwa-card dwa-mode-banner",
3361
3505
  children: [
3362
3506
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3389,28 +3533,42 @@ window.__ModuleLoader__.load({
3389
3533
  }),
3390
3534
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3391
3535
  className: "dwa-section",
3536
+ hidden: page !== "agents",
3537
+ "aria-label": acp.label("已接入的智能体", "Connected agents"),
3392
3538
  children: [
3393
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3539
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3394
3540
  className: "dwa-section-heading",
3395
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
3541
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
3396
3542
  className: "dwa-section-title",
3397
- children: [acp.label("已接入的智能体", "Connected agents"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3543
+ children: [acp.label("已接入的智能体", "Connected agents"), ui.agentsLoaded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3398
3544
  className: "dwa-badge",
3399
3545
  children: configured.length
3400
3546
  })]
3401
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3402
- className: "dwa-section-copy",
3403
- children: acp.label("展开智能体可管理模型、启动命令与工作区。", "Expand an agent to manage its models, launch command, and workspace.")
3404
- })] })
3547
+ }) }), configured.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3548
+ className: "dwa-hint",
3549
+ children: acp.label("默认项用于新的 NTR 会话", "The default serves new NTR conversations")
3550
+ })]
3405
3551
  }),
3406
- configured.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3407
- className: "dwa-card",
3408
- style: {
3409
- textAlign: "center",
3410
- color: "var(--ntr-muted)",
3411
- padding: 20
3412
- },
3413
- 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.")
3552
+ !ui.agentsLoaded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3553
+ className: "dwa-empty",
3554
+ role: "status",
3555
+ children: acp.label("正在读取智能体…", "Loading agents…")
3556
+ }),
3557
+ ui.agentsLoaded && configured.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3558
+ className: "dwa-empty",
3559
+ children: [
3560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: acp.label("接入第一个智能体", "Connect your first agent") }),
3561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3562
+ className: "dwa-section-copy",
3563
+ children: acp.label("选择已有预设,或填写自己的 ACP 启动命令。", "Choose a preset or enter your own ACP launch command.")
3564
+ }),
3565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3566
+ type: "button",
3567
+ className: "dwa-btn dwa-btn-primary",
3568
+ onClick: openAdd,
3569
+ children: acp.label("添加智能体", "Add agent")
3570
+ })
3571
+ ]
3414
3572
  }),
3415
3573
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3416
3574
  className: "dwa-agent-list",
@@ -3421,36 +3579,33 @@ window.__ModuleLoader__.load({
3421
3579
  activeAgent,
3422
3580
  acp,
3423
3581
  isDefault: ui.defaultAgent === info.id,
3424
- synthesized
3582
+ synthesized,
3583
+ active: page === "agents"
3425
3584
  }, info.id);
3426
3585
  })
3427
3586
  })
3428
3587
  ]
3429
3588
  }),
3430
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3431
3590
  className: "dwa-section",
3432
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3591
+ hidden: page !== "add",
3592
+ "aria-label": acp.label("添加智能体", "Add agent"),
3593
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3594
+ type: "button",
3595
+ className: "dwa-btn dwa-btn-quiet dwa-back",
3596
+ onClick: () => setPage("agents"),
3597
+ children: acp.label("返回智能体列表", "Back to agents")
3598
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3433
3599
  className: "dwa-add-panel",
3434
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3435
- type: "button",
3436
- className: "dwa-add-toggle",
3437
- "aria-expanded": addOpen,
3438
- onClick: () => setAddOpen((open) => !open),
3439
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3600
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3601
+ className: "dwa-section-heading",
3602
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3440
3603
  className: "dwa-section-title",
3441
3604
  children: acp.label("添加智能体", "Add agent")
3442
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3605
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3443
3606
  className: "dwa-section-copy",
3444
- style: { display: "block" },
3445
3607
  children: acp.label("从预设快速启用,或创建自定义连接。", "Enable a preset or create a custom connection.")
3446
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3447
- className: "dwa-caret-btn",
3448
- "aria-hidden": true,
3449
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3450
- className: "dwa-caret",
3451
- children: addOpen ? "▾" : "▸"
3452
- })
3453
- })]
3608
+ })] })
3454
3609
  }), addOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3455
3610
  className: "dwa-add-content",
3456
3611
  children: [
@@ -3480,21 +3635,28 @@ window.__ModuleLoader__.load({
3480
3635
  isDefault: false,
3481
3636
  synthesized: false,
3482
3637
  newCustom: selected.id === "",
3638
+ active: page === "add",
3483
3639
  onCreated: () => {
3484
3640
  setAddTarget(void 0);
3485
- setCreateNonce((nonce) => nonce + 1);
3641
+ setAddOpen(false);
3642
+ setPage("agents");
3486
3643
  }
3487
- }, selected.id === "" ? `custom-${createNonce}` : selected.id)
3644
+ }, selected.id)
3488
3645
  ]
3489
3646
  })]
3490
- })
3647
+ })]
3491
3648
  }),
3492
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApprovalDeadlinesCard, {
3493
- acp,
3494
- ui
3649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3650
+ hidden: page !== "approvals",
3651
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApprovalDeadlinesCard, {
3652
+ acp,
3653
+ ui
3654
+ })
3495
3655
  }),
3496
3656
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3497
3657
  className: "dwa-section",
3658
+ hidden: page !== "appearance",
3659
+ "aria-label": acp.label("对话外观", "Appearance"),
3498
3660
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3499
3661
  className: "dwa-section-heading",
3500
3662
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -3505,11 +3667,7 @@ window.__ModuleLoader__.load({
3505
3667
  children: acp.label("设置对话中 NTR 挂牌的进程存活提示。", "Choose how the NTR tag indicates a running agent process.")
3506
3668
  })] })
3507
3669
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3508
- className: "dwa-card",
3509
- style: {
3510
- paddingTop: 4,
3511
- paddingBottom: 4
3512
- },
3670
+ className: "dwa-preferences",
3513
3671
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3514
3672
  className: "dwa-preference-row",
3515
3673
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -3556,15 +3714,35 @@ window.__ModuleLoader__.load({
3556
3714
  })]
3557
3715
  })]
3558
3716
  })]
3717
+ }),
3718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
3719
+ className: "dwa-health",
3720
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3721
+ className: "dwa-health-status",
3722
+ children: [
3723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3724
+ className: "dwa-dot",
3725
+ style: { background: ui.stream === "live" ? "var(--ntr-success)" : ui.stream === "down" ? "var(--ntr-danger)" : "var(--ntr-muted)" }
3726
+ }),
3727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: streamText(ui.stream, acp) }),
3728
+ 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") })
3729
+ ]
3730
+ }), page !== "diagnostics" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3731
+ type: "button",
3732
+ className: "dwa-btn dwa-btn-quiet",
3733
+ onClick: () => setPage("diagnostics"),
3734
+ children: acp.label("查看诊断", "View diagnostics")
3735
+ })]
3559
3736
  })
3560
3737
  ]
3561
3738
  });
3562
3739
  }
3563
3740
  /** One row per agent: compact when collapsed, the full form when expanded. */
3564
- function AgentRow({ detected, activeAgent, acp, isDefault, synthesized, newCustom = false, onCreated }) {
3741
+ function AgentRow({ detected, activeAgent, acp, isDefault, synthesized, active, newCustom = false, onCreated }) {
3565
3742
  const ui = useUi$1(acp);
3566
3743
  const configured = activeAgent !== void 0 || detected.currentConfig !== void 0;
3567
3744
  const [expanded, setExpanded] = (0, react.useState)(!configured);
3745
+ const [detailPage, setDetailPage] = (0, react.useState)(configured ? "models" : "connection");
3568
3746
  const [form, setForm] = (0, react.useState)(() => initialForm(detected, activeAgent));
3569
3747
  const [saving, setSaving] = (0, react.useState)(false);
3570
3748
  const [savedSuccess, setSavedSuccess] = (0, react.useState)(false);
@@ -3600,7 +3778,9 @@ window.__ModuleLoader__.load({
3600
3778
  const formIncomplete = splitCommandLine(form.commandLine).command === "" || !idValid || idTaken || cwdInvalid || rootsInvalid || idleInvalid || mcpInvalid;
3601
3779
  const payload = buildPayload(form, detected, newCustom);
3602
3780
  const current = persistedDef(detected, activeAgent);
3603
- const showSaveWarning = current !== void 0 && !payloadEquals(payload, current) && activeAgent !== void 0 && activeAgent.state !== "stopped";
3781
+ const clearingIdleOverride = payload.idleTimeoutMs === void 0 && detected.currentConfig?.idleTimeoutMs !== void 0;
3782
+ const dirty = current !== void 0 && (!payloadEquals(payload, current) || clearingIdleOverride);
3783
+ const showSaveWarning = dirty && activeAgent !== void 0 && activeAgent.state !== "stopped";
3604
3784
  const handleUseDefault = () => patch({ commandLine: formatCommandLine(detected.defaultCommand, detected.defaultArgs) });
3605
3785
  const handleSave = async () => {
3606
3786
  setSaving(true);
@@ -3640,7 +3820,7 @@ window.__ModuleLoader__.load({
3640
3820
  const displayName = newCustom ? acp.label("新建自定义 Agent", "New Custom Agent") : activeAgent?.title ?? detected.currentConfig?.title ?? detected.name;
3641
3821
  const stderr = activeAgent?.state === "error" ? activeAgent.stderrTail ?? [] : [];
3642
3822
  const errorSummary = stderr.length > 0 ? stderr[stderr.length - 1] : void 0;
3643
- const currentModel = ui.modelCatalogs[detected.id]?.catalog.model?.currentValue;
3823
+ const selectedModelCount = form.exposed?.length ?? 0;
3644
3824
  const isPreset = PRESET_IDS.has(detected.id);
3645
3825
  const open = expanded || activeAgent?.state === "error" && configured;
3646
3826
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3648,17 +3828,10 @@ window.__ModuleLoader__.load({
3648
3828
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3649
3829
  className: "dwa-agent-row",
3650
3830
  children: [
3651
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3652
- type: "button",
3653
- className: "dwa-caret-btn",
3654
- "aria-expanded": open,
3655
- onClick: () => setExpanded((value) => !value),
3656
- title: open ? acp.label("收起", "Collapse") : acp.label("展开配置与模型", "Expand configuration and models"),
3657
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3658
- className: "dwa-caret",
3659
- "aria-hidden": true,
3660
- children: open ? "▾" : "▸"
3661
- })
3831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3832
+ className: "dwa-agent-avatar",
3833
+ "aria-hidden": true,
3834
+ children: displayName.slice(0, 2).toUpperCase()
3662
3835
  }),
3663
3836
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3664
3837
  className: "dwa-agent-summary",
@@ -3672,7 +3845,7 @@ window.__ModuleLoader__.load({
3672
3845
  children: displayName
3673
3846
  }),
3674
3847
  isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3675
- className: "dwa-badge dwa-badge-success",
3848
+ className: "dwa-badge dwa-badge-info",
3676
3849
  children: acp.label("默认", "Default")
3677
3850
  }),
3678
3851
  !isPreset && !newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -3699,7 +3872,7 @@ window.__ModuleLoader__.load({
3699
3872
  configured && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3700
3873
  className: "dwa-agent-meta-separator",
3701
3874
  children: "·"
3702
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: currentModel === void 0 ? acp.label("未探测模型", "Model not probed") : `${acp.label("模型", "Model")}: ${currentModel}` })] }),
3875
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: acp.label(`${String(selectedModelCount)} 个已选模型`, `${String(selectedModelCount)} selected models`) })] }),
3703
3876
  !synthesized && !newCustom && !detected.detected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3704
3877
  style: { color: "var(--ntr-warn)" },
3705
3878
  title: detected.description,
@@ -3718,24 +3891,31 @@ window.__ModuleLoader__.load({
3718
3891
  ]
3719
3892
  }),
3720
3893
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3721
- style: {
3722
- display: "flex",
3723
- alignItems: "center",
3724
- gap: 6
3725
- },
3726
- children: [activeAgent?.state === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3727
- className: "dwa-btn",
3728
- disabled: testing,
3729
- onClick: () => void handleTest(),
3730
- children: testing ? acp.label("测试中...", "Testing...") : acp.label("测试", "Test")
3731
- }), configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowMenu, {
3732
- acp,
3733
- canStop: activeAgent !== void 0 && activeAgent.state !== "stopped",
3734
- onStop: () => void acp.stopAgent(detected.id),
3735
- onRemove: async () => {
3736
- await acp.deleteAgent(detected.id).catch(() => {});
3737
- }
3738
- })]
3894
+ className: "dwa-row-actions",
3895
+ children: [
3896
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3897
+ type: "button",
3898
+ className: "dwa-btn dwa-btn-quiet",
3899
+ "aria-expanded": open,
3900
+ "aria-label": acp.label(`${open ? "收起" : "管理"} ${displayName}`, `${open ? "Collapse" : "Manage"} ${displayName}`),
3901
+ onClick: () => setExpanded((value) => !value),
3902
+ children: open ? acp.label("收起", "Collapse") : acp.label("管理", "Manage")
3903
+ }),
3904
+ activeAgent?.state === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3905
+ className: "dwa-btn",
3906
+ disabled: testing,
3907
+ onClick: () => void handleTest(),
3908
+ children: testing ? acp.label("测试中...", "Testing...") : acp.label("测试", "Test")
3909
+ }),
3910
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowMenu, {
3911
+ acp,
3912
+ canStop: activeAgent !== void 0 && activeAgent.state !== "stopped",
3913
+ onStop: () => void acp.stopAgent(detected.id),
3914
+ onRemove: async () => {
3915
+ await acp.deleteAgent(detected.id).catch(() => {});
3916
+ }
3917
+ })
3918
+ ]
3739
3919
  })
3740
3920
  ]
3741
3921
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -3746,42 +3926,52 @@ window.__ModuleLoader__.load({
3746
3926
  className: "dwa-stderr",
3747
3927
  children: stderr.join("\n")
3748
3928
  }),
3749
- configured && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3929
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("nav", {
3930
+ className: "dwa-tabs dwa-detail-tabs",
3931
+ "aria-label": acp.label(`${displayName} 配置`, `${displayName} settings`),
3932
+ children: [
3933
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3934
+ type: "button",
3935
+ "aria-pressed": detailPage === "models",
3936
+ onClick: () => setDetailPage("models"),
3937
+ children: acp.label("模型", "Models")
3938
+ }),
3939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3940
+ type: "button",
3941
+ "aria-pressed": detailPage === "connection",
3942
+ onClick: () => setDetailPage("connection"),
3943
+ children: acp.label("连接", "Connection")
3944
+ }),
3945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3946
+ type: "button",
3947
+ "aria-pressed": detailPage === "advanced",
3948
+ onClick: () => setDetailPage("advanced"),
3949
+ children: acp.label("高级", "Advanced")
3950
+ })
3951
+ ]
3952
+ }),
3953
+ configured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3750
3954
  className: "dwa-subsection",
3751
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3752
- className: "dwa-subsection-head",
3753
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3754
- className: "dwa-subsection-title",
3755
- children: acp.label("模型与默认值", "Models & defaults")
3756
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3757
- className: "dwa-subsection-copy",
3758
- children: acp.label("检测模型目录,并控制对话中可选择的模型。", "Detect the model catalog and choose what appears in conversations.")
3759
- })] })
3760
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogBlock, {
3955
+ hidden: detailPage !== "models",
3956
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalogBlock, {
3761
3957
  agentId: detected.id,
3762
3958
  detected,
3763
3959
  acp,
3764
- active: open,
3960
+ active: active && open && detailPage === "models",
3765
3961
  exposed: form.exposed,
3766
3962
  onExposedChange: (exposed) => patch({ exposed }),
3767
3963
  autoDiscover: form.autoDiscoverModels,
3768
3964
  onAutoDiscoverChange: (autoDiscoverModels) => patch({ autoDiscoverModels }),
3769
3965
  isDefault
3770
- })]
3966
+ })
3771
3967
  }),
3772
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3968
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
3773
3969
  className: "dwa-subsection",
3774
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3775
- className: "dwa-subsection-head",
3776
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
3777
- className: "dwa-subsection-title",
3778
- children: acp.label("连接配置", "Connection")
3779
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3780
- className: "dwa-subsection-copy",
3781
- children: acp.label("设置显示名与启动命令,高级选项按需展开。", "Set the display name and launch command; expand advanced options only when needed.")
3782
- })] })
3783
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentDetails, {
3970
+ hidden: detailPage === "models",
3971
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentDetails, {
3784
3972
  acp,
3973
+ advancedOpen: detailPage === "advanced",
3974
+ dirty,
3785
3975
  detected,
3786
3976
  form,
3787
3977
  patch,
@@ -3802,7 +3992,7 @@ window.__ModuleLoader__.load({
3802
3992
  onUseDefault: handleUseDefault,
3803
3993
  onSave: () => void handleSave(),
3804
3994
  onTest: () => void handleTest()
3805
- })]
3995
+ })
3806
3996
  })
3807
3997
  ]
3808
3998
  })]
@@ -3905,111 +4095,90 @@ window.__ModuleLoader__.load({
3905
4095
  return payload;
3906
4096
  }
3907
4097
  /** The expanded body: connection form, workspace allow-list, env, actions. */
3908
- function AgentDetails({ acp, detected, form, patch, isConfigured, newCustom, formIncomplete, idValid, idTaken, cwdInvalid, rootsInvalid, idleInvalid, mcpInvalid, saving, savedSuccess, testing, testResult, showSaveWarning, onUseDefault, onSave, onTest }) {
3909
- useUi$1(acp);
3910
- const [advancedOpen, setAdvancedOpen] = (0, react.useState)(false);
4098
+ 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 }) {
3911
4099
  const parsedCommand = splitCommandLine(form.commandLine);
3912
4100
  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(" ")}`);
3913
4101
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
3914
- newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3915
- className: "dwa-form-row",
3916
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3917
- className: "dwa-form-col",
3918
- children: [
3919
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3920
- className: "dwa-form-label",
3921
- children: acp.label("Agent ID(唯一标识,创建后不可改)", "Agent ID (unique, immutable after creation)")
3922
- }),
3923
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3924
- className: "dwa-input dwa-input-mono",
3925
- value: form.agentId,
3926
- onChange: (e) => patch({ agentId: e.target.value }),
3927
- placeholder: "e.g. my-agent"
3928
- }),
3929
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3930
- className: idValid && !idTaken ? "dwa-hint" : "dwa-error",
3931
- 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.")
3932
- })
3933
- ]
3934
- })
3935
- }),
3936
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3937
- className: "dwa-form-row",
3938
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3939
- className: "dwa-form-col",
3940
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3941
- className: "dwa-form-label",
3942
- children: acp.label("显示名", "Display Name")
3943
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3944
- className: "dwa-input",
3945
- value: form.title,
3946
- onChange: (e) => patch({ title: e.target.value }),
3947
- placeholder: detected.name === "" ? acp.label("我的 Agent", "My Agent") : detected.name
3948
- })]
3949
- })
3950
- }),
3951
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3952
- className: "dwa-form-row",
3953
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3954
- className: "dwa-form-col",
3955
- children: [
3956
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3957
- className: "dwa-form-label",
3958
- children: acp.label("启动命令(程序 + 参数)", "Launch Command (program + arguments)")
3959
- }),
3960
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3961
- className: "dwa-input dwa-input-mono",
3962
- value: form.commandLine,
3963
- onChange: (e) => patch({ commandLine: e.target.value }),
3964
- placeholder: detected.defaultCommand === "" ? "e.g. codebuddy --acp" : formatCommandLine(detected.defaultCommand, detected.defaultArgs)
3965
- }),
3966
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3967
- className: "dwa-hint",
3968
- children: commandPreview
3969
- }),
3970
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3971
- className: "dwa-hint",
3972
- 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.")
3973
- })
3974
- ]
3975
- })
3976
- }),
3977
4102
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3978
- style: {
3979
- display: "flex",
3980
- alignItems: "center",
3981
- gap: 8,
3982
- marginTop: 12,
3983
- flexWrap: "wrap"
3984
- },
4103
+ hidden: advancedOpen,
3985
4104
  children: [
3986
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3987
- type: "button",
3988
- className: "dwa-btn dwa-btn-secondary",
3989
- "aria-expanded": advancedOpen,
3990
- onClick: () => setAdvancedOpen((value) => !value),
3991
- children: [
3992
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3993
- className: "dwa-caret",
3994
- "aria-hidden": true,
3995
- children: advancedOpen ? "▾" : "▸"
3996
- }),
3997
- " ",
3998
- acp.label("高级参数", "Advanced Parameters")
3999
- ]
4105
+ newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4106
+ className: "dwa-form-row",
4107
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4108
+ className: "dwa-form-col",
4109
+ children: [
4110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4111
+ className: "dwa-form-label",
4112
+ children: acp.label("Agent ID(唯一标识,创建后不可改)", "Agent ID (unique, immutable after creation)")
4113
+ }),
4114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4115
+ className: "dwa-input dwa-input-mono",
4116
+ "aria-label": acp.label("Agent ID", "Agent ID"),
4117
+ value: form.agentId,
4118
+ onChange: (e) => patch({ agentId: e.target.value }),
4119
+ placeholder: "e.g. my-agent"
4120
+ }),
4121
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4122
+ className: idValid && !idTaken ? "dwa-hint" : "dwa-error",
4123
+ 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.")
4124
+ })
4125
+ ]
4126
+ })
4000
4127
  }),
4001
- !advancedOpen && (cwdInvalid || rootsInvalid) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4002
- className: "dwa-error",
4003
- children: acp.label("里面有非法的工作空间路径", "Contains an invalid workspace path")
4128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4129
+ className: "dwa-form-row",
4130
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4131
+ className: "dwa-form-col",
4132
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4133
+ className: "dwa-form-label",
4134
+ children: acp.label("显示名", "Display Name")
4135
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4136
+ className: "dwa-input",
4137
+ "aria-label": acp.label("显示名", "Display Name"),
4138
+ value: form.title,
4139
+ onChange: (e) => patch({ title: e.target.value }),
4140
+ placeholder: detected.name === "" ? acp.label("我的 Agent", "My Agent") : detected.name
4141
+ })]
4142
+ })
4004
4143
  }),
4005
- !advancedOpen && mcpInvalid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4006
- className: "dwa-error",
4007
- children: acp.label("里面的 MCP 服务器配置不是合法 JSON", "Contains an invalid MCP server list")
4144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4145
+ className: "dwa-form-row",
4146
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4147
+ className: "dwa-form-col",
4148
+ children: [
4149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
4150
+ className: "dwa-form-label",
4151
+ children: acp.label("启动命令(程序 + 参数)", "Launch Command (program + arguments)")
4152
+ }),
4153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4154
+ className: "dwa-input dwa-input-mono",
4155
+ "aria-label": acp.label("启动命令(程序 + 参数)", "Launch Command (program + arguments)"),
4156
+ value: form.commandLine,
4157
+ onChange: (e) => patch({ commandLine: e.target.value }),
4158
+ placeholder: detected.defaultCommand === "" ? "e.g. codebuddy --acp" : formatCommandLine(detected.defaultCommand, detected.defaultArgs)
4159
+ }),
4160
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4161
+ className: "dwa-hint",
4162
+ children: commandPreview
4163
+ }),
4164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4165
+ className: "dwa-hint",
4166
+ children: acp.label("含空格的程序路径请加引号。", "Quote the program path if it contains spaces.")
4167
+ })
4168
+ ]
4169
+ })
4170
+ }),
4171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceBlock, {
4172
+ acp,
4173
+ form,
4174
+ patch,
4175
+ cwdInvalid,
4176
+ rootsInvalid
4008
4177
  })
4009
4178
  ]
4010
4179
  }),
4011
4180
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4012
- style: advancedOpen ? void 0 : { display: "none" },
4181
+ hidden: !advancedOpen,
4013
4182
  children: [
4014
4183
  !newCustom && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4015
4184
  className: "dwa-default-hint",
@@ -4022,7 +4191,7 @@ window.__ModuleLoader__.load({
4022
4191
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", {
4023
4192
  className: "dwa-input-mono",
4024
4193
  style: {
4025
- background: "#0000000a",
4194
+ background: "var(--dsw-alias-markdown-inline-code, #0000000a)",
4026
4195
  padding: "2px 6px",
4027
4196
  borderRadius: 4
4028
4197
  },
@@ -4054,13 +4223,6 @@ window.__ModuleLoader__.load({
4054
4223
  })]
4055
4224
  })
4056
4225
  }),
4057
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceBlock, {
4058
- acp,
4059
- form,
4060
- patch,
4061
- cwdInvalid,
4062
- rootsInvalid
4063
- }),
4064
4226
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4065
4227
  className: "dwa-form-row",
4066
4228
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4072,19 +4234,14 @@ window.__ModuleLoader__.load({
4072
4234
  }),
4073
4235
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4074
4236
  className: "dwa-input dwa-input-mono",
4237
+ "aria-label": acp.label("空闲释放(毫秒)", "Idle Release (ms)"),
4075
4238
  value: form.idleStr,
4076
4239
  onChange: (e) => patch({ idleStr: e.target.value }),
4077
4240
  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)`)
4078
4241
  }),
4079
4242
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4080
4243
  className: idleInvalid ? "dwa-error" : "dwa-hint",
4081
- 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: [
4082
- acp.label("空闲超过此时长就挂起进程(会话保留,下一条消息自动恢复)。", "The process is parked once it has been idle this long (the session is kept and resumes on the next message)."),
4083
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
4084
- acp.label("0 = 永不释放;回合在跑、取消未结算或审批待答时不释放。", "0 = never; a process with a running turn, an unsettled cancel, or a pending approval is never released."),
4085
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("br", {}),
4086
- 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.")
4087
- ] })
4244
+ 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.") })
4088
4245
  })
4089
4246
  ]
4090
4247
  })
@@ -4174,6 +4331,7 @@ window.__ModuleLoader__.load({
4174
4331
  minHeight: 96,
4175
4332
  resize: "vertical"
4176
4333
  },
4334
+ "aria-label": acp.label("MCP 服务器(JSON)", "MCP Servers (JSON)"),
4177
4335
  value: form.mcpText,
4178
4336
  onChange: (e) => patch({ mcpText: e.target.value }),
4179
4337
  spellCheck: false,
@@ -4185,15 +4343,29 @@ window.__ModuleLoader__.load({
4185
4343
  }),
4186
4344
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4187
4345
  className: "dwa-hint",
4188
- 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.")
4346
+ children: acp.label("额外追加给智能体的 MCP 服务器,不影响其已有配置。", "Additional MCP servers for the agent; its own configuration is unchanged.")
4189
4347
  })
4190
4348
  ]
4191
4349
  })
4192
4350
  ]
4193
4351
  }),
4352
+ !advancedOpen && (mcpInvalid || idleInvalid) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4353
+ className: "dwa-error",
4354
+ role: "alert",
4355
+ children: acp.label("请检查「高级」中的无效配置。", "Check the invalid settings in Advanced.")
4356
+ }),
4357
+ advancedOpen && (!idValid || idTaken || cwdInvalid || rootsInvalid || parsedCommand.command === "") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4358
+ className: "dwa-error",
4359
+ role: "alert",
4360
+ children: acp.label("请检查「连接」中的未完成或无效配置。", "Check the incomplete or invalid settings in Connection.")
4361
+ }),
4194
4362
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4195
4363
  className: "dwa-form-actions",
4196
4364
  children: [
4365
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4366
+ className: "dwa-hint",
4367
+ children: isConfigured ? dirty ? acp.label("有未保存的更改", "Unsaved changes") : acp.label("配置已保存", "Settings saved") : ""
4368
+ }),
4197
4369
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4198
4370
  className: "dwa-btn",
4199
4371
  onClick: onTest,
@@ -4204,8 +4376,8 @@ window.__ModuleLoader__.load({
4204
4376
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4205
4377
  className: "dwa-btn dwa-btn-primary",
4206
4378
  onClick: onSave,
4207
- disabled: saving || testing || formIncomplete,
4208
- children: saving ? acp.label("保存中...", "Saving...") : savedSuccess ? acp.label("✓ 已保存", "✓ Saved") : isConfigured ? acp.label("保存", "Save") : acp.label("保存并启用", "Save & Enable")
4379
+ disabled: saving || testing || formIncomplete || isConfigured && !dirty,
4380
+ children: saving ? acp.label("保存中...", "Saving...") : savedSuccess ? acp.label("✓ 已保存", "✓ Saved") : isConfigured ? acp.label("保存更改", "Save changes") : acp.label("保存并启用", "Save & Enable")
4209
4381
  }),
4210
4382
  showSaveWarning && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4211
4383
  className: "dwa-hint",
@@ -4221,7 +4393,7 @@ window.__ModuleLoader__.load({
4221
4393
  fontSize: 12,
4222
4394
  whiteSpace: "pre-wrap",
4223
4395
  wordBreak: "break-all",
4224
- color: testResult.ok ? "#16a34a" : "var(--ntr-danger)"
4396
+ color: testResult.ok ? "var(--ntr-success)" : "var(--ntr-danger)"
4225
4397
  },
4226
4398
  children: [testResult.ok ? "✓ " : "✕ ", testResult.text]
4227
4399
  })
@@ -4281,7 +4453,7 @@ window.__ModuleLoader__.load({
4281
4453
  }),
4282
4454
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4283
4455
  className: "dwa-hint",
4284
- 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.")
4456
+ children: acp.label("聊天使用会话工作区;此处用于直连会话或宿主未提供工作区时。", "Chats use their session workspace; this applies to direct sessions or when the host provides no workspace.")
4285
4457
  }),
4286
4458
  !form.rootsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4287
4459
  className: "dwa-hint",
@@ -4610,7 +4782,7 @@ window.__ModuleLoader__.load({
4610
4782
  onChange: (e) => void toggleAutoDiscover(e.target.checked),
4611
4783
  style: {
4612
4784
  margin: 0,
4613
- accentColor: "#2563eb"
4785
+ accentColor: "var(--dsw-alias-state-business-primary, #2563eb)"
4614
4786
  }
4615
4787
  }), acp.label("自动发现新模型", "Auto-discover new models")]
4616
4788
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -4650,7 +4822,7 @@ window.__ModuleLoader__.load({
4650
4822
  fontWeight: 400
4651
4823
  },
4652
4824
  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."),
4653
- children: acp.label("(NTR 座位 / 芯片的下拉列表)", "(the NTR seat and chip pickers)")
4825
+ children: acp.label("仅影响菜单可见性", "Menu visibility only")
4654
4826
  })]
4655
4827
  }),
4656
4828
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -4672,6 +4844,7 @@ window.__ModuleLoader__.load({
4672
4844
  children: probing ? acp.label("模型检测中...", "Probing models...") : acp.label("检测可用模型", "Detect available models")
4673
4845
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4674
4846
  className: "dwa-input dwa-model-search",
4847
+ "aria-label": acp.label("搜索模型", "Search models"),
4675
4848
  value: search,
4676
4849
  onChange: (e) => setSearch(e.target.value),
4677
4850
  placeholder: acp.label("搜索模型…", "Search models…")
@@ -4789,7 +4962,7 @@ window.__ModuleLoader__.load({
4789
4962
  className: "dwa-model-list",
4790
4963
  children: [selectedChoices.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4791
4964
  className: "dwa-hint dwa-model-empty",
4792
- children: acp.label("还没有选择模型(点左侧的模型加入)", "Nothing selected yet — pick models on the left")
4965
+ children: acp.label("尚未选择模型,从「全部模型」中加入。", "No models selected. Add from All models.")
4793
4966
  }), selectedChoices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4794
4967
  type: "button",
4795
4968
  className: "dwa-model-row is-on",
@@ -5088,10 +5261,10 @@ window.__ModuleLoader__.load({
5088
5261
  * the entry point for conversations no agent has served.
5089
5262
  */
5090
5263
  const STATE_COLORS = {
5091
- ready: "#22c55e",
5092
- starting: "#eab308",
5093
- error: "#ef4444",
5094
- stopped: "#9ca3af"
5264
+ ready: "var(--dsw-alias-state-success-primary, #22c55e)",
5265
+ starting: "var(--dsw-alias-state-warn-primary, #eab308)",
5266
+ error: "var(--dsw-alias-state-error-primary, #ef4444)",
5267
+ stopped: "var(--dsw-alias-state-idle-primary, #9ca3af)"
5095
5268
  };
5096
5269
  function agentStateText(acp, state) {
5097
5270
  switch (state) {
@@ -6465,6 +6638,130 @@ window.__ModuleLoader__.load({
6465
6638
  });
6466
6639
  }
6467
6640
  //#endregion
6641
+ //#region src/client/permission-dock.tsx
6642
+ /**
6643
+ * Pending ACP permission asks for the staged DSH conversation, rendered as
6644
+ * composer-width entries above the composer card (`conversation.input.dock`).
6645
+ * The slot itself spans the conversation column; the dock mirrors the host's
6646
+ * InputBar width formula so the cards line up with the input box.
6647
+ *
6648
+ * The host broadcasts every ask as `permission/request`, carrying the DSH chat
6649
+ * session id when the ask belongs to a conversation. This dock is the primary
6650
+ * answer surface: it renders exactly the asks whose `dshSessionId` is the
6651
+ * conversation it was mounted for, and nothing otherwise. There is no
6652
+ * ACP-mode gate — the frame set itself is the gate, so an ordinary
6653
+ * conversation shows this card if and only if it actually has an ask.
6654
+ *
6655
+ * The ask's description and options are the agent's own: buttons appear in the
6656
+ * agent's order with the agent's names (including "Always Allow"), and
6657
+ * `kind` is not rendered. A separate cancel button answers with no option,
6658
+ * which the host forwards as `cancelled`. No Enter/Escape handlers: the
6659
+ * composer owns its keyboard, and these are ordinary focusable buttons.
6660
+ *
6661
+ * Multiple asks stack in ARRIVAL order, oldest first: the question ignored
6662
+ * longest stays at the top. The list is capped in height by CSS and scrolls.
6663
+ * Asks without a chat identity never appear here — they stay in the
6664
+ * settings-panel ledger, the cross-session mirror.
6665
+ */
6666
+ /** Tick interval for the countdown (seconds are all the label shows). */
6667
+ const COUNTDOWN_TICK_MS = 1e3;
6668
+ function PermissionDock({ acp, sessionId }) {
6669
+ ensureAcpStyle();
6670
+ const ui = useUi$2(acp);
6671
+ const dshSessionId = String(sessionId);
6672
+ const pending = permissionsForSession(ui.permissions, dshSessionId);
6673
+ const counting = pending.some((ask) => ask.timeoutMode !== "wait" && ask.deadlineAt !== void 0);
6674
+ const [now, setNow] = (0, react.useState)(() => Date.now());
6675
+ (0, react.useEffect)(() => {
6676
+ if (!counting) return;
6677
+ setNow(Date.now());
6678
+ const timer = setInterval(() => {
6679
+ setNow(Date.now());
6680
+ }, COUNTDOWN_TICK_MS);
6681
+ return () => {
6682
+ clearInterval(timer);
6683
+ };
6684
+ }, [counting]);
6685
+ if (pending.length === 0) return null;
6686
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6687
+ className: "ntr-permission-dock",
6688
+ role: "region",
6689
+ "aria-label": acp.label("待处理的权限请求", "Pending permission requests"),
6690
+ children: pending.map((permission) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionAskCard, {
6691
+ acp,
6692
+ permission,
6693
+ now
6694
+ }, permission.requestId))
6695
+ });
6696
+ }
6697
+ function PermissionAskCard(props) {
6698
+ const { acp, permission, now } = props;
6699
+ const [busy, setBusy] = (0, react.useState)(false);
6700
+ const [error, setError] = (0, react.useState)();
6701
+ const remaining = deadlineRemainingLabel(permission.deadlineAt, now, permission.timeoutMode);
6702
+ const expired = remaining === PERMISSION_DEADLINE_EXPIRED;
6703
+ const respond = (optionId) => {
6704
+ if (busy) return;
6705
+ setBusy(true);
6706
+ setError(void 0);
6707
+ acp.respondPermission(permission.requestId, optionId).catch((failure) => {
6708
+ setError(failure instanceof Error ? failure.message : String(failure));
6709
+ }).finally(() => {
6710
+ setBusy(false);
6711
+ });
6712
+ };
6713
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6714
+ className: "ntr-permission-card",
6715
+ children: [
6716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6717
+ className: "ntr-permission-head",
6718
+ children: [
6719
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6720
+ className: "ntr-permission-tool",
6721
+ children: permission.toolName
6722
+ }),
6723
+ permission.reason !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6724
+ className: "ntr-permission-reason",
6725
+ title: permission.reason,
6726
+ children: permission.reason
6727
+ }),
6728
+ remaining !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6729
+ className: `ntr-permission-deadline${expired ? " is-expired" : ""}`,
6730
+ children: expired ? acp.label("已超时", "expired") : acp.label(`剩余 ${remaining}`, `${remaining} left`)
6731
+ })
6732
+ ]
6733
+ }),
6734
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6735
+ className: "ntr-permission-actions",
6736
+ children: [permission.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6737
+ type: "button",
6738
+ className: "ntr-permission-option",
6739
+ disabled: busy,
6740
+ title: option.name,
6741
+ onClick: () => {
6742
+ respond(option.optionId);
6743
+ },
6744
+ children: option.name
6745
+ }, option.optionId)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6746
+ type: "button",
6747
+ className: "ntr-permission-cancel",
6748
+ disabled: busy,
6749
+ title: acp.label("取消这次询问(不选择任何选项)", "Cancel this ask without choosing an option"),
6750
+ onClick: () => {
6751
+ respond();
6752
+ },
6753
+ children: acp.label("取消", "Cancel")
6754
+ })]
6755
+ }),
6756
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6757
+ className: "ntr-permission-error",
6758
+ role: "alert",
6759
+ children: error
6760
+ })
6761
+ ]
6762
+ });
6763
+ }
6764
+ //#endregion
6468
6765
  //#region src/client/model-seat.tsx
6469
6766
  /** Exact key of the composer's model seat (a `single`, session-scoped slot). */
6470
6767
  const MODEL_SEAT_KEY = "conversation.input.model";
@@ -7125,6 +7422,12 @@ window.__ModuleLoader__.load({
7125
7422
  order: 5,
7126
7423
  inject: (sessionId) => chatFace(sessionId)
7127
7424
  }, AcpComposerAgentTag)), "dsh-ntr: composer agent tag");
7425
+ ctx.effect(() => ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
7426
+ name: "conversation.input.dock",
7427
+ id: "acp-permissions",
7428
+ order: 10,
7429
+ inject: (sessionId) => chatFace(sessionId)
7430
+ }, PermissionDock)), "dsh-ntr: conversation permission dock");
7128
7431
  /**
7129
7432
  * The client runtime's session list store, read structurally. Used to find
7130
7433
  * the session the composer is staged on and whether it runs the ACP preset —