dsh-ntr 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -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,8 +2606,10 @@ 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
2615
  padding: 20px; overflow: auto; font-size: var(--ntr-font); line-height: 1.55;
@@ -2562,25 +2633,25 @@ window.__ModuleLoader__.load({
2562
2633
  }
2563
2634
  .dwa-btn:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); border-color: var(--dsw-alias-border-l1, #9ca3af); }
2564
2635
  .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; }
2636
+ .dwa-btn-primary { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); border-color: transparent; font-weight: 550; }
2566
2637
  .dwa-btn-primary:hover { background: var(--ntr-accent); border-color: transparent; opacity: 0.88; }
2567
2638
  .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; }
2639
+ .dwa-btn-danger { color: var(--ntr-danger); border-color: var(--dsw-alias-state-error-secondary, #fca5a5); }
2640
+ .dwa-btn-danger:hover { background: var(--dsw-alias-interactive-bg-hover-danger, #fee2e2); }
2570
2641
  .dwa-section { margin-bottom: 24px; }
2571
2642
  .dwa-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
2572
2643
  .dwa-section-title { font-size: 14px; font-weight: 650; margin: 0; display: flex; align-items: center; gap: 8px; }
2573
2644
  .dwa-section-copy { margin: 3px 0 0; font-size: var(--ntr-font-xs); color: var(--ntr-muted); }
2574
2645
  .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); }
2646
+ .dwa-card-warn { border-left: 3px solid var(--dsw-alias-state-warn-primary, #d97706); background: var(--dsw-alias-state-warn-tertiary, #fffbeb); }
2576
2647
  .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
2648
  .dwa-mode-title { display: flex; align-items: center; gap: 8px; font-weight: 650; }
2578
2649
  .dwa-mode-copy { grid-column: 1 / -1; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2579
2650
  .dwa-mode-source { display: block; margin-top: 5px; overflow-wrap: anywhere; opacity: 0.85; }
2580
2651
  .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; }
2652
+ .dwa-badge-success { background: var(--dsw-alias-state-success-tertiary, #dcfce7); color: var(--ntr-success); }
2653
+ .dwa-badge-warn { background: var(--dsw-alias-state-warn-tertiary, #fef3c7); color: var(--ntr-warn); }
2654
+ .dwa-badge-info { background: var(--dsw-alias-state-business-tertiary, #dbeafe); color: var(--dsw-alias-state-business-primary, #2563eb); }
2584
2655
  .dwa-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
2585
2656
  .dwa-agent-list { display: flex; flex-direction: column; gap: 8px; }
2586
2657
  .dwa-agent-card { border: var(--ntr-line); border-radius: var(--ntr-radius); background: var(--dsw-alias-bg-layer-1, #fff); overflow: visible; transition: border-color 0.15s ease; }
@@ -2602,7 +2673,7 @@ window.__ModuleLoader__.load({
2602
2673
  .dwa-subsection-copy { margin: 3px 0 0; color: var(--ntr-muted); font-size: var(--ntr-font-xs); }
2603
2674
  .dwa-form-row { display: flex; gap: var(--ntr-gap-3); margin-bottom: 14px; align-items: flex-start; }
2604
2675
  .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); }
2676
+ .dwa-form-label { font-size: var(--ntr-font-sm); font-weight: 600; color: var(--dsw-alias-label-primary, #374151); }
2606
2677
  .dwa-hint { font-size: var(--ntr-font-xs); line-height: 1.55; color: var(--ntr-muted); }
2607
2678
  .dwa-error { font-size: var(--ntr-font-xs); color: var(--ntr-danger); }
2608
2679
  .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; }
@@ -2627,7 +2698,7 @@ window.__ModuleLoader__.load({
2627
2698
  .dwa-add-content { border-top: var(--ntr-line); padding: 14px 16px 16px; }
2628
2699
  .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
2700
  .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; }
2701
+ .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
2702
  .dwa-chip-list { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 12px; }
2632
2703
  .dwa-switch { display: inline-flex; align-items: center; gap: 7px; font-size: var(--ntr-font-sm); cursor: pointer; user-select: none; }
2633
2704
  .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 +2711,7 @@ window.__ModuleLoader__.load({
2640
2711
  /* Tall enough for a comfortable selection (about ten rows) before scrolling;
2641
2712
  the cap keeps the two stacked panes from pushing the rest of the card away. */
2642
2713
  .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); }
2714
+ .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
2715
  .dwa-model-pane-head .dwa-btn { margin-left: auto; min-height: 26px; padding: 2px 8px; font-size: var(--ntr-font-xs); }
2645
2716
  .dwa-model-pane-head .dwa-caret-btn { flex: none; width: 22px; height: 22px; background: transparent; }
2646
2717
  .dwa-model-pane-head .dwa-caret-btn:hover { background: var(--dsw-alias-interactive-bg-active, #e5e7eb); }
@@ -2658,7 +2729,7 @@ window.__ModuleLoader__.load({
2658
2729
  .dwa-model-empty { padding: 6px; }
2659
2730
  .dwa-model-row { display: flex; align-items: center; gap: 6px; width: 100%; border: none; background: none; padding: 5px 6px; border-radius: 5px; cursor: pointer; color: inherit; font: inherit; font-size: var(--ntr-font-sm); text-align: left; }
2660
2731
  .dwa-model-row:hover { background: var(--dsw-alias-interactive-bg-active, #f3f4f6); }
2661
- .dwa-model-row.is-on { background: #3b82f614; color: #2563eb; }
2732
+ .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
2733
  .dwa-model-row-mark { width: 12px; flex: none; text-align: center; opacity: 0.75; }
2663
2734
  .dwa-model-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2664
2735
  /* Name and credit keep their room; the id is the only cell that collapses. */
@@ -2680,7 +2751,7 @@ window.__ModuleLoader__.load({
2680
2751
  .dwa-seg > button { min-height: 30px; font: inherit; font-size: var(--ntr-font-sm); color: inherit; border: none; background: var(--dsw-alias-bg-layer-1, #fff); padding: 4px 14px; cursor: pointer; }
2681
2752
  .dwa-seg > button + button { border-left: 1px solid var(--dsw-alias-border-l2, #d1d5db); }
2682
2753
  .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; }
2754
+ .dwa-seg > button.is-on { background: var(--ntr-accent); color: var(--dsw-alias-label-primary-foreground, #fff); font-weight: 550; }
2684
2755
  .dwa-preference-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 0; }
2685
2756
  .dwa-preference-row + .dwa-preference-row { border-top: var(--ntr-line); }
2686
2757
  /* A control that needs the whole card: the label sits above it, not beside
@@ -2739,19 +2810,19 @@ window.__ModuleLoader__.load({
2739
2810
  function stateVisual(state, acp) {
2740
2811
  switch (state) {
2741
2812
  case "ready": return {
2742
- color: "#22c55e",
2813
+ color: "var(--dsw-alias-state-success-primary, #22c55e)",
2743
2814
  text: acp.label("已连接", "Connected")
2744
2815
  };
2745
2816
  case "starting": return {
2746
- color: "#eab308",
2817
+ color: "var(--dsw-alias-state-warn-primary, #eab308)",
2747
2818
  text: acp.label("启动中", "Starting")
2748
2819
  };
2749
2820
  case "error": return {
2750
- color: "#ef4444",
2821
+ color: "var(--dsw-alias-state-error-primary, #ef4444)",
2751
2822
  text: acp.label("错误", "Error")
2752
2823
  };
2753
2824
  default: return {
2754
- color: "#9ca3af",
2825
+ color: "var(--dsw-alias-state-idle-primary, #9ca3af)",
2755
2826
  text: acp.label("已配置", "Configured"),
2756
2827
  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
2828
  };
@@ -2924,25 +2995,15 @@ window.__ModuleLoader__.load({
2924
2995
  /**
2925
2996
  * The settings panel's permission-ledger rule.
2926
2997
  *
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.
2998
+ * Pending asks are shown in the conversation card while that conversation is
2999
+ * open; this card is the cross-session mirror for the ones it cannot show —
3000
+ * asks belonging to another conversation, and `/acp` RPC-only asks with no
3001
+ * chat identity at all. It therefore appears whenever ANY ask is pending: a
3002
+ * pending entry with no conversation card would otherwise have no answer
3003
+ * surface anywhere.
2939
3004
  */
2940
3005
  function permissionLedgerCard(ui) {
2941
- const visible = ui.permissions.length > 0;
2942
- return {
2943
- visible,
2944
- fellBackFromConversation: visible && ui.approvalRouted
2945
- };
3006
+ return { visible: ui.permissions.length > 0 };
2946
3007
  }
2947
3008
  /**
2948
3009
  * The ONE wait window, as the rungs the slider snaps to.
@@ -3272,7 +3333,7 @@ window.__ModuleLoader__.load({
3272
3333
  children: [
3273
3334
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3274
3335
  className: "dwa-dot",
3275
- style: { background: ui.stream === "live" ? "#22c55e" : ui.stream === "down" ? "#ef4444" : "#9ca3af" },
3336
+ style: { background: ui.stream === "live" ? "var(--dsw-alias-state-success-primary, #22c55e)" : ui.stream === "down" ? "var(--dsw-alias-state-error-primary, #ef4444)" : "var(--dsw-alias-state-idle-primary, #9ca3af)" },
3276
3337
  title: ui.stream
3277
3338
  }),
3278
3339
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: streamText(ui.stream, acp) }),
@@ -3300,14 +3361,14 @@ window.__ModuleLoader__.load({
3300
3361
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3301
3362
  style: {
3302
3363
  fontWeight: 600,
3303
- marginBottom: 8
3364
+ marginBottom: 4
3304
3365
  },
3305
3366
  children: acp.label("待处理权限请求", "Pending Permission Requests")
3306
3367
  }),
3307
- ledger.fellBackFromConversation && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3368
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3308
3369
  className: "dwa-hint",
3309
3370
  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.")
3371
+ children: acp.label("对话打开时在那边的卡片上回答;这里兜底显示其他会话的询问。", "Answer in the conversation card while that conversation is open; this list covers the asks whose conversation is not.")
3311
3372
  }),
3312
3373
  ui.permissions.map((permission) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3313
3374
  style: {
@@ -3318,21 +3379,31 @@ window.__ModuleLoader__.load({
3318
3379
  flexWrap: "wrap"
3319
3380
  },
3320
3381
  children: [
3321
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3322
3383
  style: {
3323
3384
  flex: 1,
3324
3385
  minWidth: 160
3325
3386
  },
3326
- children: permission.title
3387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3388
+ style: { fontWeight: 600 },
3389
+ children: permission.toolName
3390
+ }), permission.reason !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3391
+ style: { color: "var(--ntr-muted)" },
3392
+ children: [" · ", permission.reason]
3393
+ })]
3327
3394
  }),
3328
3395
  permission.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3329
3396
  className: "dwa-btn dwa-btn-primary",
3330
- onClick: () => void acp.respondPermission(permission.requestId, option.optionId),
3397
+ onClick: () => {
3398
+ acp.respondPermission(permission.requestId, option.optionId).catch(() => void 0);
3399
+ },
3331
3400
  children: option.name
3332
3401
  }, option.optionId)),
3333
3402
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3334
3403
  className: "dwa-btn",
3335
- onClick: () => void acp.respondPermission(permission.requestId),
3404
+ onClick: () => {
3405
+ acp.respondPermission(permission.requestId).catch(() => void 0);
3406
+ },
3336
3407
  children: acp.label("拒绝", "Dismiss")
3337
3408
  })
3338
3409
  ]
@@ -4022,7 +4093,7 @@ window.__ModuleLoader__.load({
4022
4093
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", {
4023
4094
  className: "dwa-input-mono",
4024
4095
  style: {
4025
- background: "#0000000a",
4096
+ background: "var(--dsw-alias-markdown-inline-code, #0000000a)",
4026
4097
  padding: "2px 6px",
4027
4098
  borderRadius: 4
4028
4099
  },
@@ -4221,7 +4292,7 @@ window.__ModuleLoader__.load({
4221
4292
  fontSize: 12,
4222
4293
  whiteSpace: "pre-wrap",
4223
4294
  wordBreak: "break-all",
4224
- color: testResult.ok ? "#16a34a" : "var(--ntr-danger)"
4295
+ color: testResult.ok ? "var(--ntr-success)" : "var(--ntr-danger)"
4225
4296
  },
4226
4297
  children: [testResult.ok ? "✓ " : "✕ ", testResult.text]
4227
4298
  })
@@ -4610,7 +4681,7 @@ window.__ModuleLoader__.load({
4610
4681
  onChange: (e) => void toggleAutoDiscover(e.target.checked),
4611
4682
  style: {
4612
4683
  margin: 0,
4613
- accentColor: "#2563eb"
4684
+ accentColor: "var(--dsw-alias-state-business-primary, #2563eb)"
4614
4685
  }
4615
4686
  }), acp.label("自动发现新模型", "Auto-discover new models")]
4616
4687
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -5088,10 +5159,10 @@ window.__ModuleLoader__.load({
5088
5159
  * the entry point for conversations no agent has served.
5089
5160
  */
5090
5161
  const STATE_COLORS = {
5091
- ready: "#22c55e",
5092
- starting: "#eab308",
5093
- error: "#ef4444",
5094
- stopped: "#9ca3af"
5162
+ ready: "var(--dsw-alias-state-success-primary, #22c55e)",
5163
+ starting: "var(--dsw-alias-state-warn-primary, #eab308)",
5164
+ error: "var(--dsw-alias-state-error-primary, #ef4444)",
5165
+ stopped: "var(--dsw-alias-state-idle-primary, #9ca3af)"
5095
5166
  };
5096
5167
  function agentStateText(acp, state) {
5097
5168
  switch (state) {
@@ -6465,6 +6536,130 @@ window.__ModuleLoader__.load({
6465
6536
  });
6466
6537
  }
6467
6538
  //#endregion
6539
+ //#region src/client/permission-dock.tsx
6540
+ /**
6541
+ * Pending ACP permission asks for the staged DSH conversation, rendered as
6542
+ * composer-width entries above the composer card (`conversation.input.dock`).
6543
+ * The slot itself spans the conversation column; the dock mirrors the host's
6544
+ * InputBar width formula so the cards line up with the input box.
6545
+ *
6546
+ * The host broadcasts every ask as `permission/request`, carrying the DSH chat
6547
+ * session id when the ask belongs to a conversation. This dock is the primary
6548
+ * answer surface: it renders exactly the asks whose `dshSessionId` is the
6549
+ * conversation it was mounted for, and nothing otherwise. There is no
6550
+ * ACP-mode gate — the frame set itself is the gate, so an ordinary
6551
+ * conversation shows this card if and only if it actually has an ask.
6552
+ *
6553
+ * The ask's description and options are the agent's own: buttons appear in the
6554
+ * agent's order with the agent's names (including "Always Allow"), and
6555
+ * `kind` is not rendered. A separate cancel button answers with no option,
6556
+ * which the host forwards as `cancelled`. No Enter/Escape handlers: the
6557
+ * composer owns its keyboard, and these are ordinary focusable buttons.
6558
+ *
6559
+ * Multiple asks stack in ARRIVAL order, oldest first: the question ignored
6560
+ * longest stays at the top. The list is capped in height by CSS and scrolls.
6561
+ * Asks without a chat identity never appear here — they stay in the
6562
+ * settings-panel ledger, the cross-session mirror.
6563
+ */
6564
+ /** Tick interval for the countdown (seconds are all the label shows). */
6565
+ const COUNTDOWN_TICK_MS = 1e3;
6566
+ function PermissionDock({ acp, sessionId }) {
6567
+ ensureAcpStyle();
6568
+ const ui = useUi$2(acp);
6569
+ const dshSessionId = String(sessionId);
6570
+ const pending = permissionsForSession(ui.permissions, dshSessionId);
6571
+ const counting = pending.some((ask) => ask.timeoutMode !== "wait" && ask.deadlineAt !== void 0);
6572
+ const [now, setNow] = (0, react.useState)(() => Date.now());
6573
+ (0, react.useEffect)(() => {
6574
+ if (!counting) return;
6575
+ setNow(Date.now());
6576
+ const timer = setInterval(() => {
6577
+ setNow(Date.now());
6578
+ }, COUNTDOWN_TICK_MS);
6579
+ return () => {
6580
+ clearInterval(timer);
6581
+ };
6582
+ }, [counting]);
6583
+ if (pending.length === 0) return null;
6584
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6585
+ className: "ntr-permission-dock",
6586
+ role: "region",
6587
+ "aria-label": acp.label("待处理的权限请求", "Pending permission requests"),
6588
+ children: pending.map((permission) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionAskCard, {
6589
+ acp,
6590
+ permission,
6591
+ now
6592
+ }, permission.requestId))
6593
+ });
6594
+ }
6595
+ function PermissionAskCard(props) {
6596
+ const { acp, permission, now } = props;
6597
+ const [busy, setBusy] = (0, react.useState)(false);
6598
+ const [error, setError] = (0, react.useState)();
6599
+ const remaining = deadlineRemainingLabel(permission.deadlineAt, now, permission.timeoutMode);
6600
+ const expired = remaining === PERMISSION_DEADLINE_EXPIRED;
6601
+ const respond = (optionId) => {
6602
+ if (busy) return;
6603
+ setBusy(true);
6604
+ setError(void 0);
6605
+ acp.respondPermission(permission.requestId, optionId).catch((failure) => {
6606
+ setError(failure instanceof Error ? failure.message : String(failure));
6607
+ }).finally(() => {
6608
+ setBusy(false);
6609
+ });
6610
+ };
6611
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6612
+ className: "ntr-permission-card",
6613
+ children: [
6614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6615
+ className: "ntr-permission-head",
6616
+ children: [
6617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6618
+ className: "ntr-permission-tool",
6619
+ children: permission.toolName
6620
+ }),
6621
+ permission.reason !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6622
+ className: "ntr-permission-reason",
6623
+ title: permission.reason,
6624
+ children: permission.reason
6625
+ }),
6626
+ remaining !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6627
+ className: `ntr-permission-deadline${expired ? " is-expired" : ""}`,
6628
+ children: expired ? acp.label("已超时", "expired") : acp.label(`剩余 ${remaining}`, `${remaining} left`)
6629
+ })
6630
+ ]
6631
+ }),
6632
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6633
+ className: "ntr-permission-actions",
6634
+ children: [permission.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6635
+ type: "button",
6636
+ className: "ntr-permission-option",
6637
+ disabled: busy,
6638
+ title: option.name,
6639
+ onClick: () => {
6640
+ respond(option.optionId);
6641
+ },
6642
+ children: option.name
6643
+ }, option.optionId)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6644
+ type: "button",
6645
+ className: "ntr-permission-cancel",
6646
+ disabled: busy,
6647
+ title: acp.label("取消这次询问(不选择任何选项)", "Cancel this ask without choosing an option"),
6648
+ onClick: () => {
6649
+ respond();
6650
+ },
6651
+ children: acp.label("取消", "Cancel")
6652
+ })]
6653
+ }),
6654
+ error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6655
+ className: "ntr-permission-error",
6656
+ role: "alert",
6657
+ children: error
6658
+ })
6659
+ ]
6660
+ });
6661
+ }
6662
+ //#endregion
6468
6663
  //#region src/client/model-seat.tsx
6469
6664
  /** Exact key of the composer's model seat (a `single`, session-scoped slot). */
6470
6665
  const MODEL_SEAT_KEY = "conversation.input.model";
@@ -7125,6 +7320,12 @@ window.__ModuleLoader__.load({
7125
7320
  order: 5,
7126
7321
  inject: (sessionId) => chatFace(sessionId)
7127
7322
  }, AcpComposerAgentTag)), "dsh-ntr: composer agent tag");
7323
+ ctx.effect(() => ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
7324
+ name: "conversation.input.dock",
7325
+ id: "acp-permissions",
7326
+ order: 10,
7327
+ inject: (sessionId) => chatFace(sessionId)
7328
+ }, PermissionDock)), "dsh-ntr: conversation permission dock");
7128
7329
  /**
7129
7330
  * The client runtime's session list store, read structurally. Used to find
7130
7331
  * the session the composer is staged on and whether it runs the ACP preset —