newmark-agent 0.3.12 → 0.4.2

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.
Files changed (60) hide show
  1. package/dist/cli-commands.d.ts +1 -0
  2. package/dist/cli-commands.js +11 -2
  3. package/dist/context/domain/types.d.ts +37 -0
  4. package/dist/context/services/context-orchestrator.js +2 -0
  5. package/dist/conversation-utility-host.bundle.cjs +1259 -132
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +139 -5
  8. package/dist/core/agent.js +964 -82
  9. package/dist/core/agentKernel/agent-loop.js +29 -3
  10. package/dist/core/agentKernel/types.d.ts +7 -0
  11. package/dist/core/agentKernelRunner.d.ts +2 -0
  12. package/dist/core/agentKernelRunner.js +121 -19
  13. package/dist/core/conversationKernel.d.ts +5 -0
  14. package/dist/core/conversationKernel.js +29 -0
  15. package/dist/core/dshCompatibility.d.ts +198 -0
  16. package/dist/core/dshCompatibility.js +600 -0
  17. package/dist/core/electronUtilityAgentClient.d.ts +4 -0
  18. package/dist/core/electronUtilityAgentClient.js +4 -0
  19. package/dist/core/electronUtilityRuntimePool.d.ts +8 -0
  20. package/dist/core/electronUtilityRuntimePool.js +14 -0
  21. package/dist/core/mcpManager.d.ts +1 -0
  22. package/dist/core/mcpManager.js +100 -10
  23. package/dist/core/subagent.d.ts +6 -0
  24. package/dist/core/subagent.js +22 -1
  25. package/dist/core/toolPolicy.d.ts +15 -0
  26. package/dist/core/toolPolicy.js +174 -1
  27. package/dist/core/types.d.ts +1 -1
  28. package/dist/core/utilityAgentProtocol.d.ts +8 -1
  29. package/dist/core/workspace.d.ts +9 -0
  30. package/dist/core/workspace.js +48 -1
  31. package/dist/core/wslAgentClient.d.ts +4 -0
  32. package/dist/core/wslAgentClient.js +4 -0
  33. package/dist/core/wslAgentProtocol.d.ts +8 -1
  34. package/dist/core/wslAgentRuntimePool.d.ts +8 -0
  35. package/dist/core/wslAgentRuntimePool.js +15 -0
  36. package/dist/launcher.js +8 -0
  37. package/dist/llm/provider.d.ts +1 -1
  38. package/dist/llm/provider.js +4 -3
  39. package/dist/main.js +163 -11
  40. package/dist/preload.js +11 -0
  41. package/dist/providers/chat-completions.adapter.js +41 -15
  42. package/dist/providers/provider-adapter.d.ts +3 -0
  43. package/dist/toolchain/registry/tool-registry.d.ts +13 -1
  44. package/dist/toolchain/registry/tool-registry.js +8 -0
  45. package/dist/toolchain/registry-seeder.js +52 -6
  46. package/dist/tools/index.d.ts +1 -0
  47. package/dist/tools/index.js +39 -2
  48. package/dist/tools/nativeTools.js +6 -1
  49. package/dist/tui/src/app.js +24 -0
  50. package/dist/tui/src/i18n.js +151 -0
  51. package/dist/tui/src/render.js +152 -61
  52. package/dist/tui/src/state.js +83 -0
  53. package/dist/ui/index.html +2669 -234
  54. package/dist/ui/lucide-sprite.svg +31 -0
  55. package/dist/wsl-agent-host.bundle.cjs +1259 -132
  56. package/dist/wsl-agent-host.js +3 -0
  57. package/package.json +6 -10
  58. package/Flow/Electron-Debug-Release.Flow.json +0 -43
  59. package/Flow/Flow.md +0 -9
  60. package/Flow/UI-Feature-Integration.Flow.json +0 -96
@@ -10,6 +10,9 @@ try {
10
10
  if (startupQuery.get('startupPrewarm') === '1' && Number(startupQuery.get('startupAttempt') || 0) > 0) {
11
11
  document.documentElement.classList.add('startup-prewarm');
12
12
  }
13
+ if (sessionStorage.getItem('newmark-config-reloading') === '1') {
14
+ document.documentElement.classList.add('config-reloading');
15
+ }
13
16
  } catch {}
14
17
  </script>
15
18
  <style>
@@ -136,6 +139,21 @@ try {
136
139
  --duration-normal: 250ms;
137
140
  --duration-slow: 400ms;
138
141
 
142
+ /* Stable Newmark semantic layers. Components depend on meaning, not on an
143
+ upstream developer-preview framework's private tokens or brand palette. */
144
+ --nm-surface-canvas: var(--app-bg);
145
+ --nm-surface-sunken: var(--glass-bg-1);
146
+ --nm-surface-raised: var(--glass-bg-2);
147
+ --nm-surface-overlay: var(--modal-surface);
148
+ --nm-label-primary: var(--text-bright);
149
+ --nm-label-secondary: var(--text);
150
+ --nm-label-tertiary: var(--text-dim);
151
+ --nm-state-info: var(--accent);
152
+ --nm-state-success: var(--accent2);
153
+ --nm-state-warning: #f4c95d;
154
+ --nm-state-danger: #ff7785;
155
+ --nm-focus-ring: 0 0 0 3px var(--accent-glow);
156
+
139
157
  /* Layout sizes */
140
158
  --left-width: 200px;
141
159
  --left-secondary-width: 220px;
@@ -264,6 +282,7 @@ html, body {
264
282
  }
265
283
 
266
284
  .startup-prewarm #startup-cover { display: flex; }
285
+ .config-reloading #startup-cover { display: flex; }
267
286
 
268
287
  .startup-cover-shell {
269
288
  width: min(520px, calc(100vw - 48px));
@@ -338,6 +357,19 @@ html, body {
338
357
  border: 0;
339
358
  }
340
359
  button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus { outline: none; }
360
+ :where(button, select, input, textarea, [contenteditable], [tabindex]):focus-visible {
361
+ outline: 2px solid var(--accent);
362
+ outline-offset: 2px;
363
+ }
364
+
365
+ @media (prefers-reduced-motion: reduce) {
366
+ *, *::before, *::after {
367
+ scroll-behavior: auto !important;
368
+ animation-duration: 0.01ms !important;
369
+ animation-iteration-count: 1 !important;
370
+ transition-duration: 0.01ms !important;
371
+ }
372
+ }
341
373
  ::-webkit-scrollbar { width: 4px; height: 4px; }
342
374
  ::-webkit-scrollbar-track { background: transparent; }
343
375
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.10); border-radius: var(--radius-full); }
@@ -527,6 +559,24 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
527
559
  flex-shrink: 0;
528
560
  }
529
561
 
562
+ button.lt-item,
563
+ button.left-nav-icon {
564
+ border: 0;
565
+ background: transparent;
566
+ font-family: var(--font-ui);
567
+ text-align: left;
568
+ }
569
+
570
+ button.left-nav-icon { width: calc(100% - 12px); }
571
+
572
+ .lt-item:focus-visible,
573
+ .left-nav-icon:focus-visible {
574
+ outline: none;
575
+ color: var(--text-bright);
576
+ background: var(--control-hover-bg);
577
+ box-shadow: inset 0 0 0 2px var(--accent);
578
+ }
579
+
530
580
  .lt-item:hover {
531
581
  background: var(--glass-bg-2);
532
582
  color: var(--text);
@@ -749,6 +799,14 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
749
799
  overflow: hidden;
750
800
  }
751
801
 
802
+ button.left-ws-item {
803
+ width: 100%;
804
+ border: 0;
805
+ background: transparent;
806
+ font-family: var(--font-ui);
807
+ text-align: left;
808
+ }
809
+
752
810
  .left-ws-item:hover { background: var(--glass-bg-2); }
753
811
  .left-ws-item.active { background: var(--accent-glow); color: var(--text-bright); }
754
812
 
@@ -1005,6 +1063,19 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
1005
1063
  .conv-archive-btn:hover,
1006
1064
  .conv-rename-btn:hover { background: rgba(255,255,255,0.1); color: var(--text-bright); }
1007
1065
 
1066
+ .conv-archive-btn.archiving { cursor: default; border-color: var(--accent); }
1067
+ .conv-archive-spinner {
1068
+ width: 11px;
1069
+ height: 11px;
1070
+ border: 2px solid var(--border);
1071
+ border-top-color: var(--accent);
1072
+ border-radius: 50%;
1073
+ animation: conv-archive-spin 0.7s linear infinite;
1074
+ }
1075
+ @keyframes conv-archive-spin {
1076
+ to { transform: rotate(360deg); }
1077
+ }
1078
+
1008
1079
  .conv-item.dragging { opacity: 0.58; }
1009
1080
  .conv-item.drag-over { border-color: var(--accent); background: rgba(91,120,255,0.14); }
1010
1081
  .conv-rename-input {
@@ -1055,6 +1126,11 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
1055
1126
  }
1056
1127
 
1057
1128
  .resize-handle:hover { background: var(--accent); }
1129
+ .resize-handle:focus-visible {
1130
+ outline: none;
1131
+ background: var(--accent);
1132
+ box-shadow: 0 0 0 2px var(--accent-glow);
1133
+ }
1058
1134
 
1059
1135
  .resize-handle[data-side="left"] {
1060
1136
  cursor: col-resize;
@@ -1883,12 +1959,12 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
1883
1959
 
1884
1960
  .terminal-output {
1885
1961
  flex: 1;
1886
- overflow-y: auto;
1962
+ overflow: auto;
1887
1963
  padding: 8px 12px;
1888
1964
  font-family: var(--font-mono);
1889
1965
  font-size: 12px;
1890
1966
  color: var(--text-dim);
1891
- white-space: pre-wrap;
1967
+ white-space: pre;
1892
1968
  line-height: 1.5;
1893
1969
  }
1894
1970
 
@@ -2373,9 +2449,9 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2373
2449
  .conversation-work-event.error { color: #ff8888; }
2374
2450
  .conversation-work-event-content { min-width: 0; white-space: pre-wrap; word-break: break-word; }
2375
2451
  .conversation-work-event.narrative { display: block; color: var(--text); font-size: 13px; line-height: 1.65; padding: 3px 1px 7px; }
2376
- .conversation-work-event.activity-summary { font-size: 12px; font-weight: 600; padding: 2px 0; }
2452
+ .conversation-work-event.activity-summary { font-size: 11px; font-weight: 400; color: var(--text-dim); padding: 2px 0; }
2377
2453
  .conversation-work-activity { margin: 0; padding: 0; }
2378
- .conversation-work-activity > summary { display: grid; grid-template-columns: 17px minmax(0,1fr) 10px; gap: 8px; align-items: center; min-height: 28px; cursor: pointer; list-style: none; color: var(--text); font-size: 12px; font-weight: 600; }
2454
+ .conversation-work-activity > summary { display: grid; grid-template-columns: 17px minmax(0,1fr) 10px; gap: 8px; align-items: center; min-height: 28px; cursor: pointer; list-style: none; color: var(--text-dim); font-size: 11px; font-weight: 400; }
2379
2455
  .conversation-work-activity > summary::-webkit-details-marker { display: none; }
2380
2456
  .conversation-work-activity > summary .nm-icon { color: var(--text-dim); }
2381
2457
  .conversation-work-activity-chevron { width: 7px; height: 7px; border-right: 1px solid var(--text-dim); border-bottom: 1px solid var(--text-dim); transform: rotate(-45deg); transition: transform 140ms ease; }
@@ -2419,6 +2495,8 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2419
2495
  .conversation-work-subagent-chip.completed .conversation-work-subagent-status { color: var(--accent2); }
2420
2496
  .conversation-work-command-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2421
2497
  .conversation-work-activity-detail { margin-top: 2px; color: color-mix(in srgb, var(--text-dim) 88%, transparent); font: 10px/1.45 var(--font-mono); white-space: pre-wrap; word-break: break-word; }
2498
+ .conversation-work-thought-text { margin: 4px 0 5px 24px; color: color-mix(in srgb, var(--text-dim) 92%, transparent); font: 11px/1.55 var(--font-mono); white-space: pre-wrap; word-break: break-word; }
2499
+ .conversation-work-thought-pending { color: var(--text-dim); font-style: italic; }
2422
2500
  .conversation-work-files { margin-top: 1px; }
2423
2501
  .conversation-work-files > summary { display: grid; grid-template-columns: 17px minmax(0,1fr) 10px; gap: 7px; align-items: center; min-height: 26px; cursor: pointer; list-style: none; color: var(--text); font-size: 11px; font-weight: 600; }
2424
2502
  .conversation-work-files > summary::-webkit-details-marker,
@@ -2524,7 +2602,7 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2524
2602
  .shell-block {
2525
2603
  margin: 8px 0;
2526
2604
  border-radius: var(--radius-md);
2527
- background: rgba(0,0,0,0.3);
2605
+ background: var(--nm-surface-sunken);
2528
2606
  border: 1px solid var(--glass-border-1);
2529
2607
  overflow: hidden;
2530
2608
  }
@@ -2550,7 +2628,7 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2550
2628
  font-family: var(--font-mono);
2551
2629
  font-size: 12px;
2552
2630
  color: var(--text-dim);
2553
- white-space: pre-wrap;
2631
+ white-space: pre;
2554
2632
  line-height: 1.5;
2555
2633
  max-height: 400px;
2556
2634
  overflow: auto;
@@ -2583,6 +2661,23 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2583
2661
  .diff-block-header .arrow { transition: transform var(--duration-fast) var(--ease-out-expo); font-size: 9px; }
2584
2662
  .diff-block.collapsed .diff-block-header .arrow { transform: rotate(-90deg); }
2585
2663
 
2664
+ button.shell-block-header,
2665
+ button.diff-block-header,
2666
+ button.flow-item-header,
2667
+ button.work-review-head,
2668
+ button.todo-item {
2669
+ width: 100%;
2670
+ border: 0;
2671
+ font-family: var(--font-ui);
2672
+ text-align: left;
2673
+ }
2674
+
2675
+ button.shell-block-header { background: rgba(255,255,255,0.03); }
2676
+ button.diff-block-header,
2677
+ button.flow-item-header,
2678
+ button.work-review-head,
2679
+ button.todo-item { background: transparent; }
2680
+
2586
2681
  .diff-stat { display: flex; gap: 8px; margin-left: auto; }
2587
2682
  .diff-add { color: var(--accent2); font-size: 11px; }
2588
2683
  .diff-del { color: #ff6666; font-size: 11px; }
@@ -2958,6 +3053,8 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2958
3053
  .work-review-actions { display:flex; align-items:center; gap:5px; }
2959
3054
  .work-review-btn { height:28px; padding:0 10px; border:1px solid var(--glass-border-2); border-radius:6px; background:transparent; color:var(--text); cursor:pointer; font:600 11px var(--font); }
2960
3055
  .work-review-btn:hover { background:var(--control-hover-bg); border-color:var(--border-hover); }
3056
+ .work-review-toggle { width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center; padding:0; border:1px solid transparent; border-radius:6px; background:transparent; color:var(--text-dim); cursor:pointer; }
3057
+ .work-review-toggle:hover { background:var(--control-hover-bg); border-color:var(--border-hover); }
2961
3058
  .work-review-list { border-top:1px solid var(--glass-border-1); }
2962
3059
  .work-review.collapsed .work-review-list { display: none; }
2963
3060
  .work-review-head { cursor: pointer; }
@@ -3057,6 +3154,20 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3057
3154
  font: 9px var(--font-mono);
3058
3155
  }
3059
3156
 
3157
+ .conv-branch-comm-icon {
3158
+ width: 20px;
3159
+ height: 20px;
3160
+ border-radius: var(--radius-sm);
3161
+ border: 1px solid var(--border);
3162
+ background: transparent;
3163
+ color: var(--accent);
3164
+ display: flex;
3165
+ align-items: center;
3166
+ justify-content: center;
3167
+ flex-shrink: 0;
3168
+ }
3169
+ .conv-branch-comm-icon .nm-icon { width: 11px; height: 11px; }
3170
+
3060
3171
  .conv-runtime-badge.running { color: var(--accent2); }
3061
3172
  .conv-runtime-badge.stopping,
3062
3173
  .conv-runtime-badge.force_restarting { color: #ffd27a; }
@@ -3391,9 +3502,47 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3391
3502
  flex: 0 0 auto;
3392
3503
  }
3393
3504
 
3505
+ #context-inspector {
3506
+ position: absolute;
3507
+ right: -8px;
3508
+ bottom: calc(100% + 10px);
3509
+ z-index: 80;
3510
+ display: none;
3511
+ width: min(320px, calc(100vw - 24px));
3512
+ padding: 12px;
3513
+ border: 1px solid var(--glass-border-2);
3514
+ border-radius: var(--radius-lg);
3515
+ background: var(--modal-surface);
3516
+ box-shadow: var(--shadow-lg);
3517
+ backdrop-filter: blur(var(--glass-blur-3)) saturate(1.08);
3518
+ color: var(--text);
3519
+ font-size: 10px;
3520
+ line-height: 1.45;
3521
+ }
3522
+ #context-inspector.open { display: block; animation: model-menu-in 150ms var(--ease-out-expo); }
3523
+ .context-inspector-head { display:flex; align-items:flex-start; gap:8px; margin-bottom:10px; }
3524
+ .context-inspector-title { flex:1; min-width:0; color:var(--text-bright); font-size:12px; font-weight:700; }
3525
+ .context-inspector-subtitle { margin-top:2px; color:var(--text-dim); font-size:9px; }
3526
+ .context-inspector-close { width:22px; height:22px; padding:0; border:0; border-radius:var(--radius-sm); background:transparent; color:var(--text-dim); cursor:pointer; }
3527
+ .context-inspector-close:hover, .context-inspector-close:focus-visible { color:var(--text-bright); background:var(--control-hover-bg); outline:none; }
3528
+ .context-inspector-meter { height:6px; margin:8px 0 10px; overflow:hidden; border-radius:var(--radius-full); background:rgba(168,168,168,.18); }
3529
+ .context-inspector-meter-fill { height:100%; border-radius:inherit; background:var(--accent); transition:width var(--duration-normal) var(--ease-out-expo), background var(--duration-fast) var(--ease-out-expo); }
3530
+ .context-inspector-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
3531
+ .context-inspector-cell { padding:7px 8px; border:1px solid var(--glass-border-1); border-radius:var(--radius-sm); background:var(--glass-bg-1); }
3532
+ .context-inspector-label { display:block; color:var(--text-dim); font-size:9px; }
3533
+ .context-inspector-value { display:block; margin-top:2px; color:var(--text-bright); font:600 10px var(--font-mono); }
3534
+ .context-inspector-section { margin-top:10px; padding-top:9px; border-top:1px solid var(--glass-border-1); }
3535
+ .context-inspector-section-title { color:var(--text-bright); font-size:10px; font-weight:650; }
3536
+ .context-inspector-meta { margin-top:3px; color:var(--text-dim); overflow-wrap:anywhere; }
3537
+ .context-inspector-actions { display:flex; align-items:center; gap:7px; margin-top:10px; }
3538
+ .context-inspector-actions .sec-btn { flex:1; min-width:0; }
3539
+ .context-inspector-actions .sec-btn:disabled { opacity:.55; cursor:wait; }
3540
+
3394
3541
  #context-token-ring {
3395
3542
  width: 16px;
3396
3543
  height: 16px;
3544
+ padding: 0;
3545
+ border: 0;
3397
3546
  border-radius: 50%;
3398
3547
  background: conic-gradient(#a8a8a8 0deg, rgba(168,168,168,0.22) 0deg);
3399
3548
  cursor: default;
@@ -3601,6 +3750,124 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3601
3750
  50% { box-shadow: 0 0 24px rgba(91,120,255,0.5), 0 0 60px rgba(91,120,255,0.15); }
3602
3751
  }
3603
3752
 
3753
+ /* ============================================================
3754
+ KEYBOARD COMMAND SURFACE
3755
+ ============================================================ */
3756
+ .command-surface-overlay {
3757
+ position: fixed;
3758
+ inset: 0;
3759
+ z-index: 320;
3760
+ display: flex;
3761
+ align-items: flex-start;
3762
+ justify-content: center;
3763
+ padding: min(12vh, 96px) 18px 18px;
3764
+ background: color-mix(in srgb, var(--nm-surface-canvas) 62%, transparent);
3765
+ opacity: 0;
3766
+ visibility: hidden;
3767
+ pointer-events: none;
3768
+ transition: opacity var(--duration-fast) var(--ease-out-expo), visibility 0ms linear var(--duration-fast);
3769
+ }
3770
+
3771
+ .command-surface-overlay.open {
3772
+ opacity: 1;
3773
+ visibility: visible;
3774
+ pointer-events: auto;
3775
+ transition-delay: 0ms, 0ms;
3776
+ }
3777
+
3778
+ .command-surface {
3779
+ width: min(680px, calc(100vw - 36px));
3780
+ max-height: min(680px, calc(100vh - 132px));
3781
+ display: flex;
3782
+ flex-direction: column;
3783
+ overflow: hidden;
3784
+ border: 1px solid var(--glass-border-2);
3785
+ border-radius: var(--radius-xl);
3786
+ background: var(--nm-surface-overlay);
3787
+ box-shadow: var(--shadow-lg);
3788
+ backdrop-filter: blur(var(--glass-blur-3)) saturate(150%);
3789
+ transform: translateY(-8px) scale(0.985);
3790
+ transition: transform var(--duration-fast) var(--ease-out-expo);
3791
+ }
3792
+
3793
+ .command-surface-overlay.open .command-surface { transform: translateY(0) scale(1); }
3794
+ .command-surface-header { display:flex; align-items:flex-start; gap:12px; padding:14px 16px 10px; }
3795
+ .command-surface-heading { flex:1; min-width:0; }
3796
+ .command-surface-title { color:var(--text-bright); font-size:14px; font-weight:700; }
3797
+ .command-surface-subtitle { margin-top:3px; color:var(--text-dim); font-size:10px; line-height:1.45; }
3798
+ .command-surface-close { width:28px; height:28px; border:0; border-radius:var(--radius-sm); background:transparent; color:var(--text-dim); cursor:pointer; }
3799
+ .command-surface-close:hover { color:var(--text-bright); background:var(--control-hover-bg); }
3800
+ .command-search-wrap { padding:0 14px 12px; }
3801
+ .command-search {
3802
+ width:100%;
3803
+ height:40px;
3804
+ box-sizing:border-box;
3805
+ padding:0 12px;
3806
+ border:1px solid var(--glass-border-2);
3807
+ border-radius:var(--radius-md);
3808
+ background:var(--control-bg);
3809
+ color:var(--text);
3810
+ font:12px var(--font-ui);
3811
+ }
3812
+ .command-search:focus { border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow); }
3813
+ .command-list { min-height:96px; overflow:auto; padding:0 8px 8px; }
3814
+ .command-category { padding:9px 9px 5px; color:var(--text-dim); font-size:9px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; }
3815
+ .command-option {
3816
+ width:100%;
3817
+ min-height:42px;
3818
+ display:grid;
3819
+ grid-template-columns:minmax(0,1fr) auto;
3820
+ align-items:center;
3821
+ gap:12px;
3822
+ padding:6px 9px;
3823
+ border:1px solid transparent;
3824
+ border-radius:var(--radius-md);
3825
+ background:transparent;
3826
+ color:var(--text);
3827
+ text-align:left;
3828
+ cursor:pointer;
3829
+ font-family:var(--font-ui);
3830
+ }
3831
+ .command-option.active,
3832
+ .command-option:hover { background:var(--control-hover-bg); border-color:var(--glass-border-1); }
3833
+ .command-option[aria-disabled="true"] { opacity:.48; cursor:not-allowed; }
3834
+ .command-option-title { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }
3835
+ .command-option-context { display:block; margin-top:2px; color:var(--text-dim); font-size:9px; }
3836
+ .command-shortcuts { display:flex; justify-content:flex-end; gap:4px; }
3837
+ .command-key {
3838
+ min-width:22px;
3839
+ padding:3px 6px;
3840
+ border:1px solid var(--glass-border-2);
3841
+ border-bottom-color:var(--border-hover);
3842
+ border-radius:5px;
3843
+ background:var(--glass-bg-1);
3844
+ color:var(--text-dim);
3845
+ font:10px var(--font-mono);
3846
+ text-align:center;
3847
+ white-space:nowrap;
3848
+ }
3849
+ .command-empty { padding:24px 12px; color:var(--text-dim); font-size:11px; text-align:center; }
3850
+ .command-surface-footer { padding:8px 14px 10px; border-top:1px solid var(--glass-border-1); color:var(--text-dim); font-size:9px; }
3851
+ .shortcut-chord-hint {
3852
+ position:fixed;
3853
+ z-index:330;
3854
+ left:50%;
3855
+ bottom:22px;
3856
+ max-width:min(520px, calc(100vw - 32px));
3857
+ padding:7px 10px;
3858
+ border:1px solid var(--glass-border-2);
3859
+ border-radius:var(--radius-md);
3860
+ background:var(--modal-surface);
3861
+ color:var(--text);
3862
+ box-shadow:var(--shadow-md);
3863
+ font:10px var(--font-mono);
3864
+ opacity:0;
3865
+ visibility:hidden;
3866
+ transform:translate(-50%, 4px);
3867
+ transition:opacity var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-expo), visibility 0ms linear var(--duration-fast);
3868
+ }
3869
+ .shortcut-chord-hint.open { opacity:1; visibility:visible; transform:translate(-50%, 0); transition-delay:0ms; }
3870
+
3604
3871
  /* ============================================================
3605
3872
  SUB-WINDOW SYSTEM
3606
3873
  ============================================================ */
@@ -3702,6 +3969,7 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3702
3969
  }
3703
3970
 
3704
3971
  .sub-win-close:hover { background: rgba(255,68,68,0.15); color: #ff6666; }
3972
+ .sub-win-close:focus-visible { outline:none; color:#ff7777; box-shadow:inset 0 0 0 2px currentColor; }
3705
3973
 
3706
3974
  .sub-win-body {
3707
3975
  flex: 1;
@@ -3734,6 +4002,81 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3734
4002
 
3735
4003
  .stab-btn:hover { background: rgba(255,255,255,0.05); color: var(--text); }
3736
4004
  .stab-btn.active { background: rgba(91,120,255,0.12); color: var(--accent); border-color: rgba(91,120,255,0.25); }
4005
+ .stab-btn:focus-visible { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow); color:var(--text-bright); }
4006
+
4007
+ .plugin-tabs { overflow-x:auto; scrollbar-width:thin; }
4008
+ .plugin-tabs .stab-btn { flex:0 0 auto; }
4009
+ .plugin-panel { min-height:180px; margin-top:10px; }
4010
+ .plugin-panel-state { display:flex; align-items:flex-start; gap:10px; padding:14px; border:1px solid var(--glass-border-1); border-radius:var(--radius-md); background:var(--glass-bg-1); color:var(--text-dim); font-size:11px; line-height:1.5; }
4011
+ .plugin-panel-state::before { content:""; flex:0 0 auto; width:7px; height:7px; margin-top:5px; border-radius:50%; background:var(--nm-state-info); box-shadow:0 0 0 3px color-mix(in srgb, var(--nm-state-info) 14%, transparent); }
4012
+ .plugin-panel-state.error::before { background:var(--nm-state-danger); box-shadow:0 0 0 3px color-mix(in srgb, var(--nm-state-danger) 14%, transparent); }
4013
+ .plugin-panel-state.error { border-color:var(--notice-error-border); color:var(--notice-error-text); }
4014
+ .plugin-panel-state .sec-btn { flex:0 0 auto; margin-left:auto; }
4015
+ .plugin-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:12px; }
4016
+ .plugin-toolbar-copy { flex:1; min-width:0; }
4017
+ .plugin-toolbar-title { color:var(--text-bright); font-size:13px; font-weight:650; }
4018
+ .plugin-toolbar-meta { margin-top:2px; color:var(--text-dim); font-size:10px; }
4019
+ .plugin-toolbar .sec-btn { flex:0 0 auto; }
4020
+ .plugin-search { width:min(220px, 38%); height:34px; padding:0 10px; border:1px solid var(--glass-border-2); border-radius:var(--radius-md); background:var(--control-bg); color:var(--text); font:11px var(--font-ui); }
4021
+ .plugin-search:focus { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-glow); }
4022
+ .plugin-section { margin-top:14px; }
4023
+ .plugin-section-head { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
4024
+ .plugin-section-title { color:var(--text-bright); font-size:12px; font-weight:650; }
4025
+ .plugin-count-badge,
4026
+ .plugin-status-badge { display:inline-flex; align-items:center; min-height:20px; padding:1px 7px; border:1px solid var(--glass-border-2); border-radius:var(--radius-full); color:var(--text-dim); font-size:9px; font-weight:650; }
4027
+ .plugin-status-badge.enabled { border-color:color-mix(in srgb, var(--accent2) 45%, transparent); color:var(--accent2); }
4028
+ .plugin-status-badge.preview { border-color:color-mix(in srgb, var(--accent) 45%, transparent); color:var(--text-accent); }
4029
+ .plugin-empty { padding:18px 12px; border:1px dashed var(--glass-border-2); border-radius:var(--radius-md); color:var(--text-dim); text-align:center; font-size:11px; line-height:1.5; }
4030
+ .mcp-form-card { padding:12px; margin-bottom:14px; border:1px solid var(--glass-border-2); border-radius:var(--radius-md); background:var(--glass-bg-1); }
4031
+ .mcp-form-head { display:flex; align-items:flex-start; gap:10px; margin-bottom:12px; }
4032
+ .mcp-form-copy { flex:1; min-width:0; }
4033
+ .mcp-form-title { color:var(--text-bright); font-size:13px; font-weight:650; }
4034
+ .mcp-form-subtitle { margin-top:2px; color:var(--text-dim); font-size:10px; line-height:1.45; }
4035
+ .mcp-field-grid { display:grid; grid-template-columns:minmax(0,1fr) 130px; gap:10px; }
4036
+ .mcp-field { display:flex; flex-direction:column; gap:5px; margin-bottom:10px; }
4037
+ .mcp-field label { color:var(--text); font-size:10px; font-weight:600; }
4038
+ .mcp-input { width:100%; min-height:36px; padding:8px 10px; border:1px solid var(--glass-border-2); border-radius:var(--radius-md); background:var(--control-bg); color:var(--text); font:11px var(--font-ui); }
4039
+ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mono); line-height:1.5; }
4040
+ .mcp-input:focus { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-glow); }
4041
+ .mcp-field-help { margin-top:-1px; color:var(--text-dim); font-size:9px; line-height:1.45; word-break:break-word; }
4042
+ .mcp-enabled-check { display:inline-flex; align-items:center; gap:7px; color:var(--text); font-size:10px; cursor:pointer; }
4043
+ .mcp-enabled-check input { accent-color:var(--accent); }
4044
+ .mcp-form-actions { display:flex; align-items:center; justify-content:flex-end; gap:8px; margin-top:4px; }
4045
+ .mcp-form-actions .sec-btn { flex:0 0 auto; }
4046
+ .mcp-server-row { display:flex; align-items:center; gap:10px; padding:10px; margin:7px 0; border:1px solid var(--glass-border-1); border-radius:var(--radius-md); background:var(--glass-bg-1); }
4047
+ .mcp-server-copy { flex:1; min-width:0; }
4048
+ .mcp-server-title { display:flex; align-items:center; gap:6px; flex-wrap:wrap; color:var(--text-bright); font-size:12px; font-weight:650; }
4049
+ .mcp-server-meta { margin-top:4px; color:var(--text-dim); font:10px/1.45 var(--font-mono); overflow-wrap:anywhere; }
4050
+ .mcp-row-actions { display:flex; align-items:center; gap:5px; flex-wrap:wrap; justify-content:flex-end; }
4051
+ .mcp-row-actions .sec-btn { flex:0 0 auto; }
4052
+ .dsh-hero { padding:14px; border:1px solid var(--glass-border-2); border-radius:var(--radius-md); background:var(--glass-bg-1); }
4053
+ .dsh-hero-head { display:flex; align-items:flex-start; gap:10px; }
4054
+ .dsh-hero-copy { flex:1; min-width:0; }
4055
+ .dsh-title { color:var(--text-bright); font-size:14px; font-weight:700; }
4056
+ .dsh-description { margin-top:5px; color:var(--text-dim); font-size:11px; line-height:1.55; }
4057
+ .dsh-actions { display:flex; flex-wrap:wrap; gap:7px; margin-top:11px; }
4058
+ .dsh-actions .sec-btn { flex:0 0 auto; }
4059
+ .dsh-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; margin-top:10px; }
4060
+ .dsh-card { min-width:0; padding:11px; border:1px solid var(--glass-border-1); border-radius:var(--radius-md); background:var(--glass-bg-1); }
4061
+ .dsh-card-title { color:var(--text-bright); font-size:11px; font-weight:650; }
4062
+ .dsh-kv { display:grid; grid-template-columns:minmax(92px,auto) minmax(0,1fr); gap:5px 10px; margin-top:8px; font-size:10px; line-height:1.45; }
4063
+ .dsh-kv dt { color:var(--text-dim); }
4064
+ .dsh-kv dd { color:var(--text); overflow-wrap:anywhere; }
4065
+ .dsh-list { display:flex; flex-direction:column; gap:6px; margin-top:8px; }
4066
+ .dsh-list-item { padding:7px 8px; border:1px solid var(--glass-border-1); border-radius:var(--radius-sm); color:var(--text); font-size:10px; line-height:1.45; overflow-wrap:anywhere; }
4067
+ .dsh-list-item.warning { border-color:var(--notice-error-border); color:var(--notice-error-text); }
4068
+ .dsh-layer-profile { margin-top:10px; padding-top:9px; border-top:1px solid var(--glass-border-1); }
4069
+ .dsh-candidate-actions { display:flex; align-items:center; gap:7px; margin-top:7px; }
4070
+ .dsh-candidate-actions .sec-btn { flex:0 0 auto; }
4071
+
4072
+ @media (max-width: 560px) {
4073
+ .sub-win { width:calc(100vw - 20px); min-width:0; }
4074
+ .plugin-toolbar { align-items:stretch; flex-wrap:wrap; }
4075
+ .plugin-search { width:100%; order:3; }
4076
+ .mcp-field-grid, .dsh-grid { grid-template-columns:1fr; }
4077
+ .mcp-server-row { align-items:flex-start; flex-direction:column; }
4078
+ .mcp-row-actions { width:100%; justify-content:flex-start; }
4079
+ }
3737
4080
 
3738
4081
  .stab-panel {
3739
4082
  display: none;
@@ -4687,6 +5030,32 @@ html.newmark-memory-overview-viewer body {
4687
5030
  .auto-input-group select:focus,
4688
5031
  .auto-input-group textarea:focus { border-color: var(--accent); }
4689
5032
 
5033
+ .auto-input-group.checkbox-row {
5034
+ display: flex;
5035
+ padding: 9px 11px;
5036
+ border: 1px solid var(--border);
5037
+ border-radius: var(--radius-md);
5038
+ background: var(--glass-bg-1);
5039
+ }
5040
+ .auto-input-group.checkbox-row label {
5041
+ display: flex;
5042
+ align-items: center;
5043
+ gap: 8px;
5044
+ margin: 0;
5045
+ cursor: pointer;
5046
+ font-size: 12px;
5047
+ color: var(--text);
5048
+ }
5049
+ .auto-input-group.checkbox-row input[type="checkbox"] {
5050
+ width: 15px;
5051
+ height: 15px;
5052
+ flex-shrink: 0;
5053
+ accent-color: var(--accent);
5054
+ cursor: pointer;
5055
+ margin: 0;
5056
+ padding: 0;
5057
+ }
5058
+
4690
5059
  .auto-conditions {
4691
5060
  display: flex;
4692
5061
  gap: 8px;
@@ -4806,6 +5175,89 @@ html.newmark-memory-overview-viewer body {
4806
5175
 
4807
5176
  .hidden { display: none !important; }
4808
5177
  .no-select { user-select: none; -webkit-user-select: none; }
5178
+
5179
+ /* ============================================================
5180
+ NEWMARK CONVERSATION HIERARCHY (三级字体体系)
5181
+ 最终回复 > Build 内联历史 > 工具调用 toolcall
5182
+ 保持原有 DOM 结构不变,仅通过字体大小/行高/不透明度体现差异。
5183
+ ============================================================ */
5184
+
5185
+ /* --- Level 1: 最终回复(最大、最醒目) --- */
5186
+ .chat-msg.assistant {
5187
+ font-size: 15px;
5188
+ line-height: 1.72;
5189
+ }
5190
+
5191
+ /* Build 块内的最终回复同样以 Level 1 呈现 */
5192
+ .chat-msg.run-final-response {
5193
+ font-size: 15px;
5194
+ line-height: 1.72;
5195
+ }
5196
+
5197
+ /* 用户消息保持可读的稍大字号 */
5198
+ .chat-msg.user {
5199
+ font-size: 14px;
5200
+ line-height: 1.65;
5201
+ }
5202
+
5203
+ /* --- Level 2: Build 内联历史(中等字号) --- */
5204
+ .conversation-work-event.narrative {
5205
+ font-size: 13px;
5206
+ line-height: 1.6;
5207
+ opacity: 0.9;
5208
+ }
5209
+
5210
+ /* Build 块标题保持中等偏小,作为历史导航 */
5211
+ .conversation-work-run-head {
5212
+ font-size: 12px;
5213
+ opacity: 0.78;
5214
+ }
5215
+
5216
+ /* --- Level 3: 工具调用 toolcall(最小、最淡、等宽) --- */
5217
+ .tool-event-summary {
5218
+ font-size: 11px;
5219
+ font-family: var(--font-mono);
5220
+ opacity: 0.78;
5221
+ }
5222
+
5223
+ .tool-event-content {
5224
+ font-size: 11px;
5225
+ font-family: var(--font-mono);
5226
+ line-height: 1.45;
5227
+ opacity: 0.72;
5228
+ color: var(--text-dim);
5229
+ }
5230
+
5231
+ /* 工具调用外的活动/状态项也归入 Level 3 */
5232
+ .conversation-work-event:not(.narrative):not(.guide):not(.error) {
5233
+ font-size: 11px;
5234
+ line-height: 1.45;
5235
+ opacity: 0.72;
5236
+ font-family: var(--font-mono);
5237
+ }
5238
+
5239
+ .conversation-work-event.activity-summary {
5240
+ font-size: 11px;
5241
+ }
5242
+
5243
+ /* 工具调用活动列表项保持最小字号 */
5244
+ .conversation-work-activity-item {
5245
+ font-size: 10.5px;
5246
+ opacity: 0.68;
5247
+ }
5248
+
5249
+ /* --- 层级间距调整,强化视觉层次 --- */
5250
+ .chat-msg.assistant { margin-top: 2px; }
5251
+ .chat-msg.run-final-response { margin-bottom: 6px; }
5252
+ .conversation-work-run { margin: 4px 0 7px; }
5253
+
5254
+ /* 浅色主题下放宽不透明度,保证可读性 */
5255
+ .light .tool-event-content,
5256
+ .light .tool-event-summary,
5257
+ .light .conversation-work-event:not(.narrative):not(.guide):not(.error) {
5258
+ opacity: 0.8;
5259
+ }
5260
+
4809
5261
  </style>
4810
5262
  </head>
4811
5263
  <body>
@@ -4923,6 +5375,11 @@ html.newmark-memory-overview-viewer body {
4923
5375
  <symbol id="folder" viewBox="0 0 24 24">
4924
5376
  <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
4925
5377
  </symbol>
5378
+ <symbol id="git-branch" viewBox="0 0 24 24">
5379
+ <path d="M15 6a9 9 0 0 0-9 9V3" />
5380
+ <circle cx="18" cy="6" r="3" />
5381
+ <circle cx="6" cy="18" r="3" />
5382
+ </symbol>
4926
5383
  <symbol id="globe" viewBox="0 0 24 24">
4927
5384
  <circle cx="12" cy="12" r="10" />
4928
5385
  <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" />
@@ -4941,6 +5398,14 @@ html.newmark-memory-overview-viewer body {
4941
5398
  <circle cx="15" cy="5" r="1" />
4942
5399
  <circle cx="15" cy="19" r="1" />
4943
5400
  </symbol>
5401
+ <symbol id="group" viewBox="0 0 24 24">
5402
+ <path d="M3 7V5c0-1.1.9-2 2-2h2" />
5403
+ <path d="M17 3h2c1.1 0 2 .9 2 2v2" />
5404
+ <path d="M21 17v2c0 1.1-.9 2-2 2h-2" />
5405
+ <path d="M7 21H5c-1.1 0-2-.9-2-2v-2" />
5406
+ <rect width="7" height="5" x="7" y="7" rx="1" />
5407
+ <rect width="7" height="5" x="10" y="12" rx="1" />
5408
+ </symbol>
4944
5409
  <symbol id="image" viewBox="0 0 24 24">
4945
5410
  <rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
4946
5411
  <circle cx="9" cy="9" r="2" />
@@ -4968,6 +5433,14 @@ html.newmark-memory-overview-viewer body {
4968
5433
  <path d="m3 17 2 2 4-4" />
4969
5434
  <path d="m3 7 2 2 4-4" />
4970
5435
  </symbol>
5436
+ <symbol id="list" viewBox="0 0 24 24">
5437
+ <path d="M3 5h.01" />
5438
+ <path d="M3 12h.01" />
5439
+ <path d="M3 19h.01" />
5440
+ <path d="M8 5h13" />
5441
+ <path d="M8 12h13" />
5442
+ <path d="M8 19h13" />
5443
+ </symbol>
4971
5444
  <symbol id="loader-circle" viewBox="0 0 24 24">
4972
5445
  <path d="M21 12a9 9 0 1 1-6.219-8.56" />
4973
5446
  </symbol>
@@ -5008,6 +5481,9 @@ html.newmark-memory-overview-viewer body {
5008
5481
  <path d="m5 9-3 3 3 3" />
5009
5482
  <path d="m9 5 3-3 3 3" />
5010
5483
  </symbol>
5484
+ <symbol id="navigation" viewBox="0 0 24 24">
5485
+ <polygon points="3 11 22 2 13 21 11 13 3 11" />
5486
+ </symbol>
5011
5487
  <symbol id="octagon-x" viewBox="0 0 24 24">
5012
5488
  <path d="m15 9-6 6" />
5013
5489
  <path d="M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z" />
@@ -5020,6 +5496,13 @@ html.newmark-memory-overview-viewer body {
5020
5496
  <path d="M3 3h6l6 18h6" />
5021
5497
  <path d="M14 3h7" />
5022
5498
  </symbol>
5499
+ <symbol id="palette" viewBox="0 0 24 24">
5500
+ <path d="M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z" />
5501
+ <circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
5502
+ <circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
5503
+ <circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
5504
+ <circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
5505
+ </symbol>
5023
5506
  <symbol id="panel-left-open" viewBox="0 0 24 24">
5024
5507
  <rect width="18" height="18" x="3" y="3" rx="2" />
5025
5508
  <path d="M9 3v18" />
@@ -5231,37 +5714,37 @@ html.newmark-memory-overview-viewer body {
5231
5714
  <!-- Left Sidebar -->
5232
5715
  <div id="left">
5233
5716
  <div id="left-thumb">
5234
- <div class="lt-item" onclick="window.showNewConversationPage()" title="New conversation"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#message-square"></use></svg><span class="icon-only-label">New conversation</span></div>
5235
- <div class="lt-item" onclick="window.showPluginList()" title="Plugins"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#plug"></use></svg><span class="icon-only-label">Plugins</span></div>
5236
- <div class="lt-item" onclick="window.showMemoryLab()" title="Memory Lab"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#brain"></use></svg><span class="icon-only-label">Memory Lab</span></div>
5237
- <div class="lt-item" onclick="window.showAutomationWindow()" title="Automation"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#calendar"></use></svg><span class="icon-only-label">Automation</span></div>
5238
- <div class="lt-item" onclick="window.showFlowEditor()" title="Flow"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#workflow"></use></svg><span class="icon-only-label">Flow</span></div>
5239
- <div class="lt-item" onclick="window.openSettings()" title="Settings"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#settings"></use></svg><span class="icon-only-label">Settings</span></div>
5240
- <div class="lt-item" id="lt-ws-icon" onclick="window.toggleSecondarySidebar()" title="Workspaces"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#folder-open"></use></svg><span class="icon-only-label">Workspaces</span></div>
5717
+ <button type="button" class="lt-item" onclick="window.showNewConversationPage()" title="New conversation"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#message-square"></use></svg><span class="icon-only-label">New conversation</span></button>
5718
+ <button type="button" class="lt-item" onclick="window.showPluginList()" title="Plugins"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#plug"></use></svg><span class="icon-only-label">Plugins</span></button>
5719
+ <button type="button" class="lt-item" onclick="window.showMemoryLab()" title="Memory Lab"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#brain"></use></svg><span class="icon-only-label">Memory Lab</span></button>
5720
+ <button type="button" class="lt-item" onclick="window.showAutomationWindow()" title="Automation"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#calendar"></use></svg><span class="icon-only-label">Automation</span></button>
5721
+ <button type="button" class="lt-item" onclick="window.showFlowEditor()" title="Flow"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#workflow"></use></svg><span class="icon-only-label">Flow</span></button>
5722
+ <button type="button" class="lt-item" onclick="window.openSettings()" title="Settings"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#settings"></use></svg><span class="icon-only-label">Settings</span></button>
5723
+ <button type="button" class="lt-item" id="lt-ws-icon" onclick="window.toggleSecondarySidebar()" title="Workspaces"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#folder-open"></use></svg><span class="icon-only-label">Workspaces</span></button>
5241
5724
  </div>
5242
5725
  <div id="left-content">
5243
5726
  <div style="padding:10px 12px 4px;font-size:10px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.05em;flex-shrink:0;">Tools</div>
5244
- <div class="left-nav-icon" onclick="window.showNewConversationPage()" title="New conversation">
5727
+ <button type="button" class="left-nav-icon" onclick="window.showNewConversationPage()" title="New conversation">
5245
5728
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#message-square"></use></svg></span><span>New chat</span>
5246
- </div>
5247
- <div class="left-nav-icon" onclick="window.showPluginList()">
5729
+ </button>
5730
+ <button type="button" class="left-nav-icon" onclick="window.showPluginList()">
5248
5731
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#plug"></use></svg></span><span>Plugins</span>
5249
- </div>
5250
- <div class="left-nav-icon" onclick="window.showMemoryLab()">
5732
+ </button>
5733
+ <button type="button" class="left-nav-icon" onclick="window.showMemoryLab()">
5251
5734
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#brain"></use></svg></span><span>Memory Lab</span>
5252
- </div>
5253
- <div class="left-nav-icon" onclick="window.showAutomationWindow()">
5735
+ </button>
5736
+ <button type="button" class="left-nav-icon" onclick="window.showAutomationWindow()">
5254
5737
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#calendar"></use></svg></span><span>Automation</span>
5255
- </div>
5256
- <div class="left-nav-icon" onclick="window.showFlowEditor()">
5738
+ </button>
5739
+ <button type="button" class="left-nav-icon" onclick="window.showFlowEditor()">
5257
5740
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#workflow"></use></svg></span><span>Flow</span>
5258
- </div>
5259
- <div class="left-nav-icon" onclick="window.openSettings()">
5741
+ </button>
5742
+ <button type="button" class="left-nav-icon" onclick="window.openSettings()">
5260
5743
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#settings"></use></svg></span><span>Settings</span>
5261
- </div>
5744
+ </button>
5262
5745
  <div id="left-ws-section">
5263
5746
  <div id="left-ws-header">Workspaces</div>
5264
- <div id="left-ws-list"></div>
5747
+ <div id="left-ws-list" role="list" aria-label="Workspaces"></div>
5265
5748
  <button id="left-ws-add" class="icon-label-btn" onclick="window.showNewWorkspaceDialog()"><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#plus"></use></svg><span>New</span></button>
5266
5749
  </div>
5267
5750
  </div>
@@ -5275,11 +5758,11 @@ html.newmark-memory-overview-viewer body {
5275
5758
  <button class="sec-btn primary icon-label-btn" onclick="window.newConversation()"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#message-square"></use></svg><span>New chat</span></button>
5276
5759
  <button class="sec-btn" onclick="window.toggleLeftSecondary()" title="Collapse" style="flex:0;padding:6px 8px;"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#chevron-left"></use></svg><span class="icon-only-label">Collapse</span></button>
5277
5760
  </div>
5278
- <div id="conversation-list"></div>
5761
+ <div id="conversation-list" role="list" aria-label="Conversations"></div>
5279
5762
  </div>
5280
5763
 
5281
5764
  <!-- Resize Handle: left area (left + secondary) to center-stack -->
5282
- <div class="resize-handle" data-side="right" data-target="left" title="Resize left sidebar"></div>
5765
+ <div class="resize-handle" data-side="right" data-target="left" role="separator" aria-orientation="vertical" aria-label="Resize left sidebar" tabindex="0" title="Resize left sidebar"></div>
5283
5766
 
5284
5767
  <!-- Center Stack (chat + terminal) -->
5285
5768
  <div id="center-stack">
@@ -5305,7 +5788,7 @@ html.newmark-memory-overview-viewer body {
5305
5788
  <div id="todo-header" class="stack-row" onclick="window.toggleTodoCollapse()">
5306
5789
  <span id="todo-header-label" class="stack-title">Task</span>
5307
5790
  <span class="stack-actions">
5308
- <button class="stack-icon-btn" onclick="event.stopPropagation();window.toggleTodoCollapse()" title="Expand tasks"><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#chevron-up"></use></svg><span class="icon-only-label">Expand tasks</span></button>
5791
+ <button class="stack-icon-btn" aria-expanded="false" aria-controls="todo-list" onclick="event.stopPropagation();window.toggleTodoCollapse()" title="Expand tasks"><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#chevron-up"></use></svg><span class="icon-only-label">Expand tasks</span></button>
5309
5792
  </span>
5310
5793
  </div>
5311
5794
  <div id="todo-list"></div>
@@ -5317,7 +5800,7 @@ html.newmark-memory-overview-viewer body {
5317
5800
  <span id="queue-header-label" class="stack-title">Next</span>
5318
5801
  <span class="stack-actions">
5319
5802
  <button class="stack-icon-btn" id="queue-pause-btn" onclick="event.stopPropagation();window.toggleQueuePause()" title="Pause queue" aria-pressed="false"><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#pause"></use></svg><span class="icon-only-label">Pause queue</span></button>
5320
- <button class="stack-icon-btn" id="queue-expand-btn" onclick="event.stopPropagation();window.toggleQueuePanel()" title="Expand queue"><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#chevron-up"></use></svg><span class="icon-only-label">Expand queue</span></button>
5803
+ <button class="stack-icon-btn" id="queue-expand-btn" aria-expanded="false" aria-controls="queue-list" onclick="event.stopPropagation();window.toggleQueuePanel()" title="Expand queue"><svg class="nm-icon tiny" aria-hidden="true" focusable="false"><use href="#chevron-up"></use></svg><span class="icon-only-label">Expand queue</span></button>
5321
5804
  </span>
5322
5805
  </div>
5323
5806
  <div id="queue-list"></div>
@@ -5361,8 +5844,9 @@ html.newmark-memory-overview-viewer body {
5361
5844
  <div class="model-select-menu" id="model-select-menu" role="listbox" aria-label="Model" popover="manual"></div>
5362
5845
  </div>
5363
5846
  <div class="context-token-wrap">
5364
- <div id="context-token-ring" title="Context tokens" tabindex="0" onmouseenter="window.showContextWindowTooltip()" onmouseleave="window.hideContextWindowTooltip()" onfocus="window.showContextWindowTooltip()" onblur="window.hideContextWindowTooltip()"></div>
5847
+ <button type="button" id="context-token-ring" title="Context tokens" aria-haspopup="dialog" aria-expanded="false" onclick="window.toggleContextInspector()" onmouseenter="window.showContextWindowTooltip()" onmouseleave="window.hideContextWindowTooltip()" onfocus="window.showContextWindowTooltip()" onblur="window.hideContextWindowTooltip()"><span class="icon-only-label">Context tokens</span></button>
5365
5848
  <div id="context-token-tooltip"></div>
5849
+ <div id="context-inspector" role="dialog" aria-label="Context management" aria-live="polite"></div>
5366
5850
  </div>
5367
5851
  <select class="tool-select" id="intel-select">
5368
5852
  <option value="low">low</option>
@@ -5376,7 +5860,7 @@ html.newmark-memory-overview-viewer body {
5376
5860
  <button class="mode-toggle-btn active" data-mode="guide" onclick="window.setInputMode('guide')">Guide</button>
5377
5861
  <button class="mode-toggle-btn" data-mode="next" onclick="window.setInputMode('next')">Next</button>
5378
5862
  </div>
5379
- <button id="submit-btn" onclick="window.submitCurrentAction()" title="Send"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#send"></use></svg><span class="icon-only-label">Send</span></button>
5863
+ <button id="submit-btn" onclick="window.submitCurrentAction('click')" title="Send"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#send"></use></svg><span class="icon-only-label">Send</span></button>
5380
5864
  </div>
5381
5865
  </div>
5382
5866
  </div>
@@ -5384,7 +5868,7 @@ html.newmark-memory-overview-viewer body {
5384
5868
  <!-- Bottom Terminal -->
5385
5869
  <div id="bottom" class="open">
5386
5870
  <div id="bottom-header">
5387
- <div id="terminal-tabs"></div>
5871
+ <div id="terminal-tabs" role="tablist" aria-label="Terminals"></div>
5388
5872
  <select id="terminal-shell-select" onchange="window.spawnTerminal(this.value)">
5389
5873
  <option value="powershell" data-platform-shell="win32">PowerShell</option>
5390
5874
  <option value="cmd" data-platform-shell="win32">CMD</option>
@@ -5402,7 +5886,7 @@ html.newmark-memory-overview-viewer body {
5402
5886
  <div class="terminal-output"><span style="color:var(--text-dim);opacity:0.5;">Terminal ready</span></div>
5403
5887
  <div class="terminal-input-row">
5404
5888
  <span class="terminal-prompt">PS&gt;</span>
5405
- <input class="terminal-input" type="text" placeholder="Enter command..." onfocus="window.ensureTerminalStarted&&window.ensureTerminalStarted()" onkeydown="if(event.key==='Enter')window.terminalSend()">
5889
+ <input class="terminal-input" type="text" placeholder="Enter command..." onfocus="window.ensureTerminalStarted&&window.ensureTerminalStarted()" onkeydown="if(event.key==='Enter'&&!event.isComposing&&event.key!=='Process'&&event.keyCode!==229)window.terminalSend()">
5406
5890
  </div>
5407
5891
  </div>
5408
5892
  </div>
@@ -5410,25 +5894,25 @@ html.newmark-memory-overview-viewer body {
5410
5894
  </div>
5411
5895
 
5412
5896
  <!-- Right Resize Handle -->
5413
- <div class="resize-handle" data-side="left" data-target="right" title="Resize right sidebar"></div>
5897
+ <div class="resize-handle" data-side="left" data-target="right" role="separator" aria-orientation="vertical" aria-label="Resize right sidebar" tabindex="0" title="Resize right sidebar"></div>
5414
5898
 
5415
5899
  <!-- Right Sidebar -->
5416
5900
  <div id="right" class="open">
5417
- <div id="right-tabs">
5418
- <button class="tab-btn active" data-tab="file-tree" onclick="window.switchRightTab('file-tree')" title="Files"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#folder"></use></svg><span class="icon-only-label">Files</span></button>
5419
- <button class="tab-btn" data-tab="editor" onclick="window.switchRightTab('editor')" title="Editor"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#square-pen"></use></svg><span class="icon-only-label">Editor</span></button>
5420
- <button class="tab-btn" data-tab="plan" onclick="window.switchRightTab('plan')" title="Conversation plan"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#list-checks"></use></svg><span class="icon-only-label">Conversation plan</span></button>
5421
- <button class="tab-btn" data-tab="subagent" onclick="window.switchRightTab('subagent')" title="Subagents"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#bot"></use></svg><span class="icon-only-label">Subagents</span></button>
5422
- <button class="tab-btn" data-tab="browser" onclick="window.switchRightTab('browser')" title="Browser"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#globe"></use></svg><span class="icon-only-label">Browser</span></button>
5901
+ <div id="right-tabs" role="tablist" aria-label="Right sidebar">
5902
+ <button class="tab-btn active" id="right-tab-file-tree" role="tab" aria-selected="true" aria-controls="panel-file-tree" tabindex="0" data-tab="file-tree" onclick="window.switchRightTab('file-tree')" onkeydown="window.handleRightTabKey(event)" title="Files"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#folder"></use></svg><span class="icon-only-label">Files</span></button>
5903
+ <button class="tab-btn" id="right-tab-editor" role="tab" aria-selected="false" aria-controls="panel-editor" tabindex="-1" data-tab="editor" onclick="window.switchRightTab('editor')" onkeydown="window.handleRightTabKey(event)" title="Editor"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#square-pen"></use></svg><span class="icon-only-label">Editor</span></button>
5904
+ <button class="tab-btn" id="right-tab-plan" role="tab" aria-selected="false" aria-controls="panel-plan" tabindex="-1" data-tab="plan" onclick="window.switchRightTab('plan')" onkeydown="window.handleRightTabKey(event)" title="Conversation plan"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#list-checks"></use></svg><span class="icon-only-label">Conversation plan</span></button>
5905
+ <button class="tab-btn" id="right-tab-subagent" role="tab" aria-selected="false" aria-controls="panel-subagent" tabindex="-1" data-tab="subagent" onclick="window.switchRightTab('subagent')" onkeydown="window.handleRightTabKey(event)" title="Subagents"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#bot"></use></svg><span class="icon-only-label">Subagents</span></button>
5906
+ <button class="tab-btn" id="right-tab-browser" role="tab" aria-selected="false" aria-controls="panel-browser" tabindex="-1" data-tab="browser" onclick="window.switchRightTab('browser')" onkeydown="window.handleRightTabKey(event)" title="Browser"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#globe"></use></svg><span class="icon-only-label">Browser</span></button>
5423
5907
  <div class="tab-divider"></div>
5424
5908
  <button class="tab-btn" onclick="window.toggleRight()" title="Close right sidebar"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#x"></use></svg><span class="icon-only-label">Close right sidebar</span></button>
5425
5909
  </div>
5426
5910
  <div id="right-content">
5427
- <div class="tab-panel active" id="panel-file-tree">
5911
+ <div class="tab-panel active" id="panel-file-tree" role="tabpanel" aria-labelledby="right-tab-file-tree">
5428
5912
  <div style="font-size:11px;color:var(--text-dim);margin-bottom:8px;">Workspace file tree</div>
5429
- <div id="file-tree-container"></div>
5913
+ <div id="file-tree-container" role="tree"></div>
5430
5914
  </div>
5431
- <div class="tab-panel" id="panel-editor">
5915
+ <div class="tab-panel" id="panel-editor" role="tabpanel" aria-labelledby="right-tab-editor" hidden>
5432
5916
  <div class="editor-toolbar">
5433
5917
  <button class="et-btn" onclick="window.saveEditor()" title="Save" aria-label="Save"><svg class="nm-icon" aria-hidden="true" focusable="false"><use href="#save"></use></svg></button>
5434
5918
  <button class="et-btn editor-view-btn" id="editor-md-toggle" onclick="window.toggleEditorMarkdownPreview()" title="Markdown preview" aria-label="Markdown preview"><svg class="nm-icon" aria-hidden="true" focusable="false"><use href="#book-open"></use></svg></button>
@@ -5444,22 +5928,22 @@ html.newmark-memory-overview-viewer body {
5444
5928
  <div class="editor-status"><span class="editor-vim-mode" id="editor-vim-mode">INSERT</span><span id="editor-language">text</span><span id="editor-position">1:1</span><span style="margin-left:auto" id="editor-dirty"></span></div>
5445
5929
  </div>
5446
5930
  </div>
5447
- <div class="tab-panel" id="panel-plan">
5931
+ <div class="tab-panel" id="panel-plan" role="tabpanel" aria-labelledby="right-tab-plan" hidden>
5448
5932
  <div class="right-section-head"><span class="right-section-title">Conversation plan</span><button class="archive-action-btn" onclick="window.refreshConversationPlan()">Refresh</button></div>
5449
5933
  <div id="conversation-plan-content"></div>
5450
5934
  <div class="right-section-head linked-plan-head"><span class="right-section-title">Linked plan</span><span class="right-row-meta" id="linked-plan-revision"></span></div>
5451
5935
  <div id="linked-plan-content" class="linked-plan-reader md-rendered" aria-live="polite"></div>
5452
5936
  </div>
5453
- <div class="tab-panel" id="panel-subagent">
5937
+ <div class="tab-panel" id="panel-subagent" role="tabpanel" aria-labelledby="right-tab-subagent" hidden>
5454
5938
  <div style="font-size:11px;color:var(--text-dim);margin-bottom:8px;">Subagents</div>
5455
5939
  <div id="subagent-list"></div>
5456
5940
  </div>
5457
- <div class="tab-panel" id="panel-browser">
5941
+ <div class="tab-panel" id="panel-browser" role="tabpanel" aria-labelledby="right-tab-browser" hidden>
5458
5942
  <div class="browser-url-bar">
5459
5943
  <button class="shell-btn browser-action-btn" onclick="window.browserBack()" title="Back"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#chevron-left"></use></svg><span class="icon-only-label">Back</span></button>
5460
5944
  <button class="shell-btn browser-action-btn" onclick="window.browserForward()" title="Forward"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#chevron-right"></use></svg><span class="icon-only-label">Forward</span></button>
5461
5945
  <button class="shell-btn browser-action-btn" onclick="window.browserReload()" title="Reload"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#refresh-cw"></use></svg><span class="icon-only-label">Reload</span></button>
5462
- <input class="browser-url-input" id="browser-url" placeholder="Enter URL..." onkeydown="if(event.key==='Enter')window.navigateBrowser(this.value)">
5946
+ <input class="browser-url-input" id="browser-url" placeholder="Enter URL..." onkeydown="if(event.key==='Enter'&&!event.isComposing&&event.key!=='Process'&&event.keyCode!==229)window.navigateBrowser(this.value)">
5463
5947
  <button class="shell-btn" onclick="window.navigateBrowser(document.getElementById('browser-url').value)" title="Go"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#send"></use></svg><span class="icon-only-label">Go</span></button>
5464
5948
  <button class="shell-btn" id="computer-use-toggle" onclick="window.toggleComputerUse()" title="ComputerUse: enabled for this conversation" aria-pressed="true">CU</button>
5465
5949
  </div>
@@ -5476,7 +5960,7 @@ html.newmark-memory-overview-viewer body {
5476
5960
 
5477
5961
  <!-- ========== SUB-WINDOW OVERLAY ========== -->
5478
5962
  <div class="sub-win-overlay" id="sub-win-overlay" onclick="if(event.target===this)window.closeSubWin()">
5479
- <div class="sub-win" id="sub-win">
5963
+ <div class="sub-win" id="sub-win" role="dialog" aria-modal="true" aria-labelledby="sub-win-title" tabindex="-1">
5480
5964
  <div class="sub-win-header" id="sub-win-header">
5481
5965
  <span class="sub-win-title" id="sub-win-title">Window</span>
5482
5966
  <button class="sub-win-close" onclick="window.closeSubWin()" title="Close"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#x"></use></svg><span class="icon-only-label">Close</span></button>
@@ -5484,6 +5968,21 @@ html.newmark-memory-overview-viewer body {
5484
5968
  <div class="sub-win-body" id="sub-win-body"></div>
5485
5969
  </div>
5486
5970
  </div>
5971
+ <div class="command-surface-overlay" id="command-surface-overlay" onclick="if(event.target===this)window.closeCommandSurface()">
5972
+ <section class="command-surface" id="command-surface" role="dialog" aria-modal="true" aria-labelledby="command-surface-title" tabindex="-1">
5973
+ <header class="command-surface-header">
5974
+ <div class="command-surface-heading">
5975
+ <div class="command-surface-title" id="command-surface-title"></div>
5976
+ <div class="command-surface-subtitle" id="command-surface-subtitle"></div>
5977
+ </div>
5978
+ <button type="button" class="command-surface-close" onclick="window.closeCommandSurface()" aria-label="Close"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#x"></use></svg></button>
5979
+ </header>
5980
+ <div class="command-search-wrap"><input class="command-search" id="command-search" type="search" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="command-list"></div>
5981
+ <div class="command-list" id="command-list" role="listbox"></div>
5982
+ <footer class="command-surface-footer" id="command-surface-footer"></footer>
5983
+ </section>
5984
+ </div>
5985
+ <div class="shortcut-chord-hint" id="shortcut-chord-hint" role="status" aria-live="polite"></div>
5487
5986
  <script>
5488
5987
  /* ============================================================
5489
5988
  NEWMARK AGENT - Complete UI Controller v2.0
@@ -5579,6 +6078,8 @@ var state = {
5579
6078
  automationItems: [],
5580
6079
  contextCompression: null,
5581
6080
  contextWindow: null,
6081
+ contextInspectorOpen: false,
6082
+ contextMutationPending: false,
5582
6083
  flowWorks: [],
5583
6084
  activeAgentRunning: false,
5584
6085
  editorPath: '',
@@ -5596,6 +6097,9 @@ var state = {
5596
6097
  editorCompletionText: '',
5597
6098
  editorCompletionRequest: 0,
5598
6099
  editorCompletionAnchor: null,
6100
+ editorCompletionCache: [],
6101
+ editorCompletionInFlight: false,
6102
+ editorCompletionStreamText: '',
5599
6103
  editorCaretSignature: '',
5600
6104
  editorPredictionEnabled: true,
5601
6105
  editorCompletionTimer: null,
@@ -5651,7 +6155,14 @@ var state = {
5651
6155
  githubOverviewPendingRepo: '',
5652
6156
  mcpServers: [],
5653
6157
  mcpDiscovered: [],
6158
+ dshCompatibility: null,
5654
6159
  mcpEditingId: '',
6160
+ mcpDraft: null,
6161
+ mcpSearchQuery: '',
6162
+ mcpMutationPending: false,
6163
+ mcpMutationGeneration: 0,
6164
+ mcpFormFocusRequested: false,
6165
+ pluginPanelGeneration: 0,
5655
6166
  skillMarketQuery: '',
5656
6167
  memoryLab: null,
5657
6168
  memoryLabComponentContents: {},
@@ -5720,6 +6231,48 @@ var NEWMARK_I18N = {
5720
6231
  en: {
5721
6232
  'app.ready': 'Ready',
5722
6233
  'app.subtitle': 'Enter an instruction to begin. Enter sends. Shift+Enter inserts a newline.',
6234
+ 'shortcuts.paletteTitle': 'Command palette',
6235
+ 'shortcuts.helpTitle': 'Keyboard shortcuts',
6236
+ 'shortcuts.paletteSubtitle': 'Search every registered Newmark GUI command. Shortcuts never replace standard editing keys.',
6237
+ 'shortcuts.helpSubtitle': 'Common desktop keys plus conflict-safe Newmark command chords.',
6238
+ 'shortcuts.searchCommands': 'Search commands...',
6239
+ 'shortcuts.searchShortcuts': 'Search commands or shortcuts...',
6240
+ 'shortcuts.noResults': 'No matching commands.',
6241
+ 'shortcuts.footer': 'Up/Down navigate · Enter runs · Esc closes · Tab stays inside this dialog',
6242
+ 'shortcuts.category.general': 'General',
6243
+ 'shortcuts.category.navigation': 'Navigation',
6244
+ 'shortcuts.category.workspace': 'Workspace and chat',
6245
+ 'shortcuts.category.layout': 'Layout and focus',
6246
+ 'shortcuts.category.input': 'Input and modes',
6247
+ 'shortcuts.category.editor': 'Editor',
6248
+ 'shortcuts.category.browser': 'Browser',
6249
+ 'shortcuts.category.terminal': 'Terminal',
6250
+ 'shortcuts.category.context': 'Context controls',
6251
+ 'shortcuts.context.global': 'Application',
6252
+ 'shortcuts.context.prompt': 'Prompt',
6253
+ 'shortcuts.context.editor': 'Editor only',
6254
+ 'shortcuts.context.browser': 'Browser only',
6255
+ 'shortcuts.context.terminal': 'Terminal only',
6256
+ 'shortcuts.context.dialog': 'Dialog',
6257
+ 'shortcuts.openPalette': 'Open command palette',
6258
+ 'shortcuts.openHelp': 'Show keyboard shortcuts',
6259
+ 'shortcuts.focusPrimary': 'Focus primary input',
6260
+ 'shortcuts.focusNext': 'Focus next application region',
6261
+ 'shortcuts.focusPrevious': 'Focus previous application region',
6262
+ 'shortcuts.toggleLeft': 'Toggle left sidebar',
6263
+ 'shortcuts.toggleWorkspacePanel': 'Toggle workspace and conversation panel',
6264
+ 'shortcuts.toggleRight': 'Toggle right sidebar',
6265
+ 'shortcuts.toggleTerminal': 'Toggle terminal',
6266
+ 'shortcuts.nextConversation': 'Next conversation',
6267
+ 'shortcuts.previousConversation': 'Previous conversation',
6268
+ 'shortcuts.nextBranch': 'Next branch',
6269
+ 'shortcuts.previousBranch': 'Previous branch',
6270
+ 'shortcuts.scrollBottom': 'Scroll chat to bottom',
6271
+ 'shortcuts.newTerminal': 'New terminal',
6272
+ 'shortcuts.focusTerminal': 'Focus terminal input',
6273
+ 'shortcuts.focusBrowserAddress': 'Focus browser address',
6274
+ 'shortcuts.toggleTheme': 'Toggle light and dark theme',
6275
+ 'shortcuts.chordHint': '{prefix} waiting for: {keys}',
5723
6276
  'top.minimize': 'Minimize',
5724
6277
  'top.maximize': 'Maximize',
5725
6278
  'top.close': 'Close',
@@ -5731,12 +6284,14 @@ var NEWMARK_I18N = {
5731
6284
  'left.flow': 'Flow',
5732
6285
  'left.settings': 'Settings',
5733
6286
  'left.workspaces': 'Workspaces',
6287
+ 'left.conversations': 'Conversations',
5734
6288
  'left.new': 'New',
5735
6289
  'left.collapse': 'Collapse left sidebar',
5736
6290
  'left.collapseSecondary': 'Collapse workspace panel',
5737
6291
  'input.placeholder': 'Input instruction...',
5738
6292
  'input.send': 'Send',
5739
6293
  'input.stop': 'Stop',
6294
+ 'input.continue': 'Continue',
5740
6295
  'input.guide': 'Guide',
5741
6296
  'input.next': 'Next',
5742
6297
  'mode.build': 'Build',
@@ -5986,6 +6541,7 @@ var NEWMARK_I18N = {
5986
6541
  'model.yes': 'yes',
5987
6542
  'model.notChecked': 'not checked',
5988
6543
  'archive.current': 'Archive current chat',
6544
+ 'archive.archiving': 'Archiving...',
5989
6545
  'archive.empty': 'No archives yet.',
5990
6546
  'archive.loading': 'Loading workspace archives...',
5991
6547
  'archive.unavailable': 'Archive list unavailable',
@@ -6066,7 +6622,13 @@ var NEWMARK_I18N = {
6066
6622
  'flow.noAvailable': 'No workflows are available to run.',
6067
6623
  'flow.started': 'Flow started',
6068
6624
  'plugins.title': 'Plugins',
6625
+ 'plugins.tabMcp': 'MCP',
6626
+ 'plugins.tabDsh': 'DSH Plugin',
6627
+ 'plugins.tabSkills': 'Skills',
6628
+ 'plugins.tabMarket': 'Market',
6629
+ 'plugins.tabGithub': 'GitHub',
6069
6630
  'plugins.mcp': 'MCP Management',
6631
+ 'plugins.dsh': 'DSH Plugin',
6070
6632
  'plugins.management': 'Skills Management',
6071
6633
  'plugins.market': 'Skills Market',
6072
6634
  'plugins.github': 'GitHub CLI',
@@ -6124,6 +6686,74 @@ var NEWMARK_I18N = {
6124
6686
  'plugins.mcpNoServers': 'No user MCP servers.',
6125
6687
  'plugins.mcpDiscovered': 'Discovered from plugins',
6126
6688
  'plugins.mcpStored': 'User MCP servers',
6689
+ 'plugins.mcpConfiguredCount': '{count} configured',
6690
+ 'plugins.mcpSearch': 'Search MCP servers...',
6691
+ 'plugins.mcpNoMatch': 'No MCP servers match this search.',
6692
+ 'plugins.mcpRefresh': 'Refresh',
6693
+ 'plugins.mcpRefreshing': 'Refreshing MCP servers...',
6694
+ 'plugins.mcpLoadError': 'MCP servers could not be loaded.',
6695
+ 'plugins.mcpUnavailable': 'MCP management is unavailable in this interface.',
6696
+ 'plugins.mcpAddTitle': 'Add MCP server',
6697
+ 'plugins.mcpEditTitle': 'Edit MCP server',
6698
+ 'plugins.mcpReviewTitle': 'Review MCP candidate',
6699
+ 'plugins.mcpFormHelp': 'Review every field before saving. Imported candidates always start disabled.',
6700
+ 'plugins.mcpEnabled': 'Enable this server after saving',
6701
+ 'plugins.mcpEndpointStdio': 'Command',
6702
+ 'plugins.mcpEndpointHttp': 'Server URL',
6703
+ 'plugins.mcpCwd': 'Working directory (optional)',
6704
+ 'plugins.mcpSecretsHelp': 'When editing, leave blank to preserve saved values; enter {} to clear them. Secret values are never displayed.',
6705
+ 'plugins.mcpSavedEnvKeys': 'Saved environment keys: {keys}',
6706
+ 'plugins.mcpSavedHeaderKeys': 'Saved header keys: {keys}',
6707
+ 'plugins.mcpNoSavedKeys': 'No saved keys.',
6708
+ 'plugins.mcpInvalidArgs': 'Arguments must be a JSON array.',
6709
+ 'plugins.mcpInvalidObject': '{field} must be a JSON object.',
6710
+ 'plugins.mcpRequiredName': 'Enter a server name.',
6711
+ 'plugins.mcpRequiredCommand': 'Enter a command for this stdio server.',
6712
+ 'plugins.mcpRequiredUrl': 'Enter an http(s) URL for this server.',
6713
+ 'plugins.mcpSaving': 'Saving...',
6714
+ 'plugins.mcpSaved': 'MCP server saved.',
6715
+ 'plugins.mcpEnabledStatus': 'Enabled',
6716
+ 'plugins.mcpDisabledStatus': 'Disabled',
6717
+ 'plugins.mcpMutationFailed': 'MCP operation failed.',
6718
+ 'plugins.mcpRemoveConfirm': 'Remove MCP server "{name}"?',
6719
+ 'plugins.mcpReadonly': 'Read-only metadata',
6720
+ 'plugins.mcpDiscoveredEmpty': 'No MCP metadata was discovered from installed plugins.',
6721
+ 'plugins.mcpStoredEmpty': 'No user MCP servers yet. Add one or review a compatible DSH candidate.',
6722
+ 'plugins.mcpReviewImport': 'Review and import',
6723
+ 'plugins.mcpCandidateHelp': 'This only pre-fills the MCP form. Nothing is installed, executed, or enabled automatically.',
6724
+ 'plugins.retry': 'Retry',
6725
+ 'plugins.dshTitle': 'DSH Plugin compatibility',
6726
+ 'plugins.dshHelp': 'Developer preview: inspect the locally available official DSH CLI and package configuration. Discovery is read-only and never installs, executes, updates, or rewrites DSH.',
6727
+ 'plugins.dshPreview': 'Developer preview',
6728
+ 'plugins.dshRescan': 'Rescan',
6729
+ 'plugins.dshScanning': 'Scanning DSH compatibility...',
6730
+ 'plugins.dshLoadError': 'DSH compatibility information could not be loaded.',
6731
+ 'plugins.dshUnavailable': 'DSH discovery is unavailable in this interface.',
6732
+ 'plugins.dshCli': 'CLI and package',
6733
+ 'plugins.dshCliPath': 'CLI path',
6734
+ 'plugins.dshCliVersion': 'CLI version',
6735
+ 'plugins.dshPackageVersion': 'Package version',
6736
+ 'plugins.dshHome': 'DSH_HOME',
6737
+ 'plugins.dshHomeSource': 'Home source',
6738
+ 'plugins.dshConfigFiles': 'Configuration files',
6739
+ 'plugins.dshLayers': 'Configuration layer order',
6740
+ 'plugins.dshLayerOrder': 'Later layers override earlier rows; unknown keys remain visible and untouched.',
6741
+ 'plugins.dshHomePatches': 'Home-level patches',
6742
+ 'plugins.dshUpdateability': 'Updateability: use official DSH tooling or edit the shown patch files; Newmark never rewrites them.',
6743
+ 'plugins.dshUpdateChannel': 'Update channel',
6744
+ 'plugins.dshLatestChannel': 'latest (not locked)',
6745
+ 'plugins.dshProfiles': 'Profiles',
6746
+ 'plugins.dshBundles': 'Bundles',
6747
+ 'plugins.dshWarnings': 'Warnings',
6748
+ 'plugins.dshUnknownKeys': 'Unknown keys',
6749
+ 'plugins.dshMcpCandidates': 'MCP candidates',
6750
+ 'plugins.dshNone': 'None discovered',
6751
+ 'plugins.dshNotAvailable': 'Not available',
6752
+ 'plugins.dshOfficialRepo': 'Official repository',
6753
+ 'plugins.dshOfficialDocs': 'Documentation',
6754
+ 'plugins.dshOfficialNpm': 'npm package',
6755
+ 'plugins.dshReadonly': 'Read-only compatibility policy',
6756
+ 'plugins.dshReadonlyDetail': 'Newmark only reads local compatibility metadata. Use official DSH tooling to install, execute, update, or change DSH configuration.',
6127
6757
  'workspace.new': 'New workspace',
6128
6758
  'workspace.type': 'Type',
6129
6759
  'workspace.internal': 'Internal workspace',
@@ -6223,6 +6853,21 @@ var NEWMARK_I18N = {
6223
6853
  'status.contextModeModel': 'model',
6224
6854
  'status.messages': 'messages',
6225
6855
  'status.noCompression': 'No compression event in this conversation',
6856
+ 'status.contextInspector': 'Context management',
6857
+ 'status.contextInspectorHint': 'DSH-inspired live budget; visible chat history stays unchanged.',
6858
+ 'status.activeBuild': 'Active Build',
6859
+ 'status.longHistory': 'Long history',
6860
+ 'status.trigger': 'Trigger',
6861
+ 'status.retention': 'Retention',
6862
+ 'status.hotCache': 'Hot cache',
6863
+ 'status.coldArchive': 'Cold archive',
6864
+ 'status.lastCompression': 'Last compression',
6865
+ 'status.noCompressionShort': 'No compression yet',
6866
+ 'status.compressNow': 'Compress now',
6867
+ 'status.compressing': 'Compressing...',
6868
+ 'status.compressedNow': 'Context compressed.',
6869
+ 'status.compressionFailed': 'Context compression failed.',
6870
+ 'status.compressionBusy': 'Compression is unavailable while this conversation is running.',
6226
6871
  'status.recentFiles': 'Recent file changes',
6227
6872
  'status.noFileChanges': 'No file changes recorded for the latest run.',
6228
6873
  'status.pendingOptions': 'Pending options',
@@ -6243,6 +6888,8 @@ var NEWMARK_I18N = {
6243
6888
  'conversation.reorderFailed': 'Could not save the conversation order.',
6244
6889
  'conversation.pin': 'Pin conversation',
6245
6890
  'conversation.unpin': 'Unpin conversation',
6891
+ 'conversation.branchCommunication': 'Allow branch communication',
6892
+ 'conversation.branchCommunicationBadge': 'Branch communication',
6246
6893
  'conversation.loadingIsolated': 'Loading isolated conversation...',
6247
6894
  'conversation.locked': 'Current conversation is locked while the agent is working.',
6248
6895
  'queue.next': 'Next',
@@ -6286,6 +6933,48 @@ var NEWMARK_I18N = {
6286
6933
  zh: {
6287
6934
  'app.ready': '就绪',
6288
6935
  'app.subtitle': '输入指令开始。Enter 发送,Shift+Enter 换行。',
6936
+ 'shortcuts.paletteTitle': '命令面板',
6937
+ 'shortcuts.helpTitle': '键盘快捷键',
6938
+ 'shortcuts.paletteSubtitle': '搜索并执行所有已注册的 Newmark GUI 命令;不会覆盖标准文本编辑按键。',
6939
+ 'shortcuts.helpSubtitle': '通用桌面快捷键与避免冲突的 Newmark 命令序列。',
6940
+ 'shortcuts.searchCommands': '搜索命令...',
6941
+ 'shortcuts.searchShortcuts': '搜索命令或快捷键...',
6942
+ 'shortcuts.noResults': '没有匹配的命令。',
6943
+ 'shortcuts.footer': '上/下选择 · Enter 执行 · Esc 关闭 · Tab 保持在对话框内',
6944
+ 'shortcuts.category.general': '通用',
6945
+ 'shortcuts.category.navigation': '导航',
6946
+ 'shortcuts.category.workspace': '工作区与对话',
6947
+ 'shortcuts.category.layout': '布局与焦点',
6948
+ 'shortcuts.category.input': '输入与模式',
6949
+ 'shortcuts.category.editor': '编辑器',
6950
+ 'shortcuts.category.browser': '浏览器',
6951
+ 'shortcuts.category.terminal': '终端',
6952
+ 'shortcuts.category.context': '上下文操作',
6953
+ 'shortcuts.context.global': '应用',
6954
+ 'shortcuts.context.prompt': '输入框',
6955
+ 'shortcuts.context.editor': '仅编辑器',
6956
+ 'shortcuts.context.browser': '仅浏览器',
6957
+ 'shortcuts.context.terminal': '仅终端',
6958
+ 'shortcuts.context.dialog': '对话框',
6959
+ 'shortcuts.openPalette': '打开命令面板',
6960
+ 'shortcuts.openHelp': '显示键盘快捷键',
6961
+ 'shortcuts.focusPrimary': '聚焦主要输入框',
6962
+ 'shortcuts.focusNext': '聚焦下一个应用区域',
6963
+ 'shortcuts.focusPrevious': '聚焦上一个应用区域',
6964
+ 'shortcuts.toggleLeft': '切换左侧边栏',
6965
+ 'shortcuts.toggleWorkspacePanel': '切换工作区与对话面板',
6966
+ 'shortcuts.toggleRight': '切换右侧边栏',
6967
+ 'shortcuts.toggleTerminal': '切换终端',
6968
+ 'shortcuts.nextConversation': '下一个对话',
6969
+ 'shortcuts.previousConversation': '上一个对话',
6970
+ 'shortcuts.nextBranch': '下一个分支',
6971
+ 'shortcuts.previousBranch': '上一个分支',
6972
+ 'shortcuts.scrollBottom': '滚动到对话底部',
6973
+ 'shortcuts.newTerminal': '新建终端',
6974
+ 'shortcuts.focusTerminal': '聚焦终端输入框',
6975
+ 'shortcuts.focusBrowserAddress': '聚焦浏览器地址栏',
6976
+ 'shortcuts.toggleTheme': '切换明暗主题',
6977
+ 'shortcuts.chordHint': '{prefix} 等待:{keys}',
6289
6978
  'top.minimize': '最小化',
6290
6979
  'top.maximize': '最大化',
6291
6980
  'top.close': '关闭',
@@ -6297,12 +6986,14 @@ var NEWMARK_I18N = {
6297
6986
  'left.flow': '工作流',
6298
6987
  'left.settings': '设置',
6299
6988
  'left.workspaces': '工作区',
6989
+ 'left.conversations': '对话',
6300
6990
  'left.new': '新建',
6301
6991
  'left.collapse': '折叠左侧栏',
6302
6992
  'left.collapseSecondary': '折叠工作区面板',
6303
6993
  'input.placeholder': '输入指令...',
6304
6994
  'input.send': '发送',
6305
6995
  'input.stop': '停止',
6996
+ 'input.continue': '继续',
6306
6997
  'input.guide': 'Guide',
6307
6998
  'input.next': 'Next',
6308
6999
  'mode.build': 'Build',
@@ -6552,6 +7243,7 @@ var NEWMARK_I18N = {
6552
7243
  'model.yes': '是',
6553
7244
  'model.notChecked': '未校验',
6554
7245
  'archive.current': '归档当前对话',
7246
+ 'archive.archiving': '归档中...',
6555
7247
  'archive.empty': '暂无归档。',
6556
7248
  'archive.loading': '正在加载工作区归档...',
6557
7249
  'archive.unavailable': '归档列表不可用',
@@ -6632,7 +7324,13 @@ var NEWMARK_I18N = {
6632
7324
  'flow.noAvailable': '当前没有可运行的工作流。',
6633
7325
  'flow.started': 'Flow 已启动',
6634
7326
  'plugins.title': '插件',
7327
+ 'plugins.tabMcp': 'MCP',
7328
+ 'plugins.tabDsh': 'DSH Plugin',
7329
+ 'plugins.tabSkills': 'Skills',
7330
+ 'plugins.tabMarket': 'Market',
7331
+ 'plugins.tabGithub': 'GitHub',
6635
7332
  'plugins.mcp': 'MCP 管理',
7333
+ 'plugins.dsh': 'DSH Plugin',
6636
7334
  'plugins.management': 'Skills 管理',
6637
7335
  'plugins.market': 'Skills Market',
6638
7336
  'plugins.github': 'GitHub CLI',
@@ -6690,6 +7388,74 @@ var NEWMARK_I18N = {
6690
7388
  'plugins.mcpNoServers': '暂无用户 MCP 服务器。',
6691
7389
  'plugins.mcpDiscovered': '从插件发现',
6692
7390
  'plugins.mcpStored': '用户 MCP 服务器',
7391
+ 'plugins.mcpConfiguredCount': '已配置 {count} 项',
7392
+ 'plugins.mcpSearch': '搜索 MCP 服务器...',
7393
+ 'plugins.mcpNoMatch': '没有符合搜索条件的 MCP 服务器。',
7394
+ 'plugins.mcpRefresh': '刷新',
7395
+ 'plugins.mcpRefreshing': '正在刷新 MCP 服务器...',
7396
+ 'plugins.mcpLoadError': '无法加载 MCP 服务器。',
7397
+ 'plugins.mcpUnavailable': '当前界面不支持 MCP 管理。',
7398
+ 'plugins.mcpAddTitle': '添加 MCP 服务器',
7399
+ 'plugins.mcpEditTitle': '编辑 MCP 服务器',
7400
+ 'plugins.mcpReviewTitle': '审核 MCP 候选项',
7401
+ 'plugins.mcpFormHelp': '保存前请审核所有字段。从候选项导入时始终默认禁用。',
7402
+ 'plugins.mcpEnabled': '保存后启用此服务器',
7403
+ 'plugins.mcpEndpointStdio': '命令',
7404
+ 'plugins.mcpEndpointHttp': '服务器 URL',
7405
+ 'plugins.mcpCwd': '工作目录(可选)',
7406
+ 'plugins.mcpSecretsHelp': '编辑时留空可保留已保存的值;输入 {} 可清空。界面绝不显示密钥值。',
7407
+ 'plugins.mcpSavedEnvKeys': '已保存环境变量键:{keys}',
7408
+ 'plugins.mcpSavedHeaderKeys': '已保存请求头键:{keys}',
7409
+ 'plugins.mcpNoSavedKeys': '没有已保存的键。',
7410
+ 'plugins.mcpInvalidArgs': '参数必须是 JSON 数组。',
7411
+ 'plugins.mcpInvalidObject': '{field} 必须是 JSON 对象。',
7412
+ 'plugins.mcpRequiredName': '请输入服务器名称。',
7413
+ 'plugins.mcpRequiredCommand': '请输入 stdio 服务器命令。',
7414
+ 'plugins.mcpRequiredUrl': '请输入 http(s) 服务器 URL。',
7415
+ 'plugins.mcpSaving': '正在保存...',
7416
+ 'plugins.mcpSaved': 'MCP 服务器已保存。',
7417
+ 'plugins.mcpEnabledStatus': '已启用',
7418
+ 'plugins.mcpDisabledStatus': '已禁用',
7419
+ 'plugins.mcpMutationFailed': 'MCP 操作失败。',
7420
+ 'plugins.mcpRemoveConfirm': '移除 MCP 服务器“{name}”?',
7421
+ 'plugins.mcpReadonly': '只读元数据',
7422
+ 'plugins.mcpDiscoveredEmpty': '未从已安装插件中发现 MCP 元数据。',
7423
+ 'plugins.mcpStoredEmpty': '暂无用户 MCP 服务器。可以添加服务器,或审核兼容的 DSH 候选项。',
7424
+ 'plugins.mcpReviewImport': '审核并导入',
7425
+ 'plugins.mcpCandidateHelp': '此操作只会预填 MCP 表单,不会安装、执行或自动启用任何内容。',
7426
+ 'plugins.retry': '重试',
7427
+ 'plugins.dshTitle': 'DSH Plugin 兼容',
7428
+ 'plugins.dshHelp': '开发者预览:检查本地官方 DSH CLI 与包配置。发现过程严格只读,绝不会安装、执行、更新或改写 DSH。',
7429
+ 'plugins.dshPreview': '开发者预览',
7430
+ 'plugins.dshRescan': '重新扫描',
7431
+ 'plugins.dshScanning': '正在扫描 DSH 兼容信息...',
7432
+ 'plugins.dshLoadError': '无法加载 DSH 兼容信息。',
7433
+ 'plugins.dshUnavailable': '当前界面不支持 DSH 发现。',
7434
+ 'plugins.dshCli': 'CLI 与软件包',
7435
+ 'plugins.dshCliPath': 'CLI 路径',
7436
+ 'plugins.dshCliVersion': 'CLI 版本',
7437
+ 'plugins.dshPackageVersion': '软件包版本',
7438
+ 'plugins.dshHome': 'DSH_HOME',
7439
+ 'plugins.dshHomeSource': '目录来源',
7440
+ 'plugins.dshConfigFiles': '配置文件',
7441
+ 'plugins.dshLayers': '配置层顺序',
7442
+ 'plugins.dshLayerOrder': '后面的层覆盖前面的条目;未知键会被展示但不会改写。',
7443
+ 'plugins.dshHomePatches': 'Home 级 patch',
7444
+ 'plugins.dshUpdateability': '可更新性:请使用官方 DSH 工具或编辑下方 patch 文件;Newmark 不会重写这些文件。',
7445
+ 'plugins.dshUpdateChannel': '更新通道',
7446
+ 'plugins.dshLatestChannel': 'latest(未锁定)',
7447
+ 'plugins.dshProfiles': 'Profiles',
7448
+ 'plugins.dshBundles': 'Bundles',
7449
+ 'plugins.dshWarnings': '警告',
7450
+ 'plugins.dshUnknownKeys': '未知配置键',
7451
+ 'plugins.dshMcpCandidates': 'MCP 候选项',
7452
+ 'plugins.dshNone': '未发现',
7453
+ 'plugins.dshNotAvailable': '不可用',
7454
+ 'plugins.dshOfficialRepo': '官方仓库',
7455
+ 'plugins.dshOfficialDocs': '官方文档',
7456
+ 'plugins.dshOfficialNpm': 'npm 软件包',
7457
+ 'plugins.dshReadonly': '只读兼容策略',
7458
+ 'plugins.dshReadonlyDetail': 'Newmark 只读取本地兼容元数据。安装、执行、更新或更改 DSH 配置请使用官方 DSH 工具。',
6693
7459
  'workspace.new': '新建工作区',
6694
7460
  'workspace.type': '类型',
6695
7461
  'workspace.internal': '内部工作区',
@@ -6789,6 +7555,21 @@ var NEWMARK_I18N = {
6789
7555
  'status.contextModeModel': '模型',
6790
7556
  'status.messages': '条消息',
6791
7557
  'status.noCompression': '当前对话暂无压缩事件',
7558
+ 'status.contextInspector': '上下文管理',
7559
+ 'status.contextInspectorHint': '借鉴 DSH 的动态预算;可见对话历史不会被改写。',
7560
+ 'status.activeBuild': '当前 Build',
7561
+ 'status.longHistory': '长期历史',
7562
+ 'status.trigger': '触发线',
7563
+ 'status.retention': '保留量',
7564
+ 'status.hotCache': '热缓存',
7565
+ 'status.coldArchive': '冷归档',
7566
+ 'status.lastCompression': '最近一次压缩',
7567
+ 'status.noCompressionShort': '尚未压缩',
7568
+ 'status.compressNow': '立即压缩',
7569
+ 'status.compressing': '正在压缩...',
7570
+ 'status.compressedNow': '上下文已压缩。',
7571
+ 'status.compressionFailed': '上下文压缩失败。',
7572
+ 'status.compressionBusy': '对话运行中,暂时无法压缩上下文。',
6792
7573
  'status.recentFiles': '最近文件变更',
6793
7574
  'status.noFileChanges': '最近一轮没有记录文件变更。',
6794
7575
  'status.pendingOptions': '待处理选项',
@@ -6809,6 +7590,8 @@ var NEWMARK_I18N = {
6809
7590
  'conversation.reorderFailed': '无法保存对话顺序。',
6810
7591
  'conversation.pin': '置顶对话',
6811
7592
  'conversation.unpin': '取消置顶对话',
7593
+ 'conversation.branchCommunication': '允许分支交流',
7594
+ 'conversation.branchCommunicationBadge': '分支交流',
6812
7595
  'conversation.loadingIsolated': '正在加载隔离对话...',
6813
7596
  'conversation.locked': 'Agent 工作中,当前对话已锁定。',
6814
7597
  'queue.next': '下一轮',
@@ -6933,6 +7716,10 @@ function applyLanguageToUi() {
6933
7716
  }
6934
7717
  var wsHead = document.getElementById('left-ws-header');
6935
7718
  if (wsHead) wsHead.textContent = t('left.workspaces');
7719
+ var wsList = document.getElementById('left-ws-list');
7720
+ if (wsList) wsList.setAttribute('aria-label', t('left.workspaces'));
7721
+ var conversationList = document.getElementById('conversation-list');
7722
+ if (conversationList) conversationList.setAttribute('aria-label', t('left.conversations'));
6936
7723
  var wsAdd = document.querySelector('#left-ws-add span:last-child');
6937
7724
  if (wsAdd) wsAdd.textContent = t('left.new');
6938
7725
  setTitleAndLabel('.secondary-top button[onclick="window.openWsSettings()"]', t('workspace.settingsTitle'));
@@ -6958,6 +7745,10 @@ function applyLanguageToUi() {
6958
7745
  if (browserUrl) browserUrl.setAttribute('placeholder', t('browser.url'));
6959
7746
  var terminalInputs = document.querySelectorAll('.terminal-input');
6960
7747
  for (var ti = 0; ti < terminalInputs.length; ti++) terminalInputs[ti].setAttribute('placeholder', t('terminal.enterCommand'));
7748
+ var terminalTabs = document.getElementById('terminal-tabs');
7749
+ if (terminalTabs) terminalTabs.setAttribute('aria-label', t('shortcuts.category.terminal'));
7750
+ var fileTree = document.getElementById('file-tree-container');
7751
+ if (fileTree) fileTree.setAttribute('aria-label', t('right.fileTree'));
6961
7752
  var terminalReady = document.querySelector('#terminal-pane-0 .terminal-output span');
6962
7753
  if (terminalReady && terminalReady.textContent === 'Terminal ready') terminalReady.textContent = t('terminal.ready');
6963
7754
  var terminalStatusSpans = document.querySelectorAll('.terminal-output span');
@@ -8220,6 +9011,8 @@ window.toggleWorkReview = function(head) {
8220
9011
  var review = head && head.closest ? head.closest('.work-review') : null;
8221
9012
  if (!review) return;
8222
9013
  review.classList.toggle('collapsed');
9014
+ var toggle = review.querySelector('.work-review-toggle');
9015
+ if (toggle) toggle.setAttribute('aria-expanded', review.classList.contains('collapsed') ? 'false' : 'true');
8223
9016
  };
8224
9017
 
8225
9018
  window.toggleWorkReviewFiles = function(button) {
@@ -8324,7 +9117,7 @@ function addWorkReview(diffs) {
8324
9117
  review.innerHTML = '<div class="work-review-head" onclick="window.toggleWorkReview(this)"><div class="work-review-mark">' + iconSvg('file-diff', t('review.fileChanges'), 'small') + '</div>' +
8325
9118
  '<div><div class="work-review-title">' + esc(editedLabel) + '</div><div class="work-review-stats"><span class="work-review-add">+' + added + '</span><span class="work-review-del">-' + deleted + '</span></div></div>' +
8326
9119
  '<div class="work-review-actions"><button class="work-review-btn" onclick="window.openWorkReview(this);event.stopPropagation()">' + esc(t('review.open')) + '</button>' +
8327
- '<span class="work-review-chevron" aria-hidden="true"></span></div></div>' +
9120
+ '<button type="button" class="work-review-toggle" aria-expanded="false" onclick="event.stopPropagation();window.toggleWorkReview(this)" title="' + escAttr(t('queue.expand')) + '"><span class="work-review-chevron" aria-hidden="true"></span><span class="icon-only-label">' + esc(t('queue.expand')) + '</span></button></div></div>' +
8328
9121
  '<div class="work-review-list">' + rows + (files.length > 3 ? '<button class="work-review-more" onclick="window.toggleWorkReviewFiles(this)">' + esc(t('review.showMore').replace('{count}', files.length - 3)) + '</button>' : '') + '</div>';
8329
9122
  els['chat-area'].appendChild(review);
8330
9123
  autoScrollIfAtBottom();
@@ -8859,7 +9652,7 @@ function publicWorkEvent(event) {
8859
9652
  var content = String(event && event.content || '');
8860
9653
  var toolArgs = String(event && event.toolArgs || '');
8861
9654
  if (/<think(?:\s|>)/i.test(content) || /<\/think>/i.test(content) || /<\/?think\b|(?:reasoning_content|thinking_delta)\s*[::]/i.test(toolArgs)) return false;
8862
- return ['start', 'text', 'response', 'final_response', 'status', 'tool_call', 'tool_result', 'guide', 'guide_accepted', 'guide_applied', 'guide_deferred', 'guide_rejected', 'done', 'error', 'interrupted', 'force_interrupted'].indexOf(type) >= 0;
9655
+ return ['start', 'text', 'response', 'final_response', 'status', 'tool_call', 'tool_result', 'thought', 'thought_result', 'guide', 'guide_accepted', 'guide_applied', 'guide_deferred', 'guide_rejected', 'done', 'error', 'interrupted', 'force_interrupted'].indexOf(type) >= 0;
8863
9656
  }
8864
9657
 
8865
9658
  function publicToolNameForUi(value) {
@@ -9118,7 +9911,12 @@ function syncWorkRunsSnapshot(runs, target, branchId) {
9118
9911
  return item;
9119
9912
  });
9120
9913
  state.workRunsByBranch[workRunBranchKey(target, selectedBranchId)] = normalized;
9121
- state.workRunsByTarget[key] = normalized;
9914
+ // 串分支修复:全局显示列表(workRunsByTarget)只在 sync 的分支就是「浏览分支」时
9915
+ // 才更新。当运行分支(runtime)的 streaming 结果到达、而用户正在浏览另一个分支时,
9916
+ // 只按分支存储(workRunsByBranch),不污染当前浏览分支的显示。
9917
+ if (selectedBranchId === branchIds.viewed) {
9918
+ state.workRunsByTarget[key] = normalized;
9919
+ }
9122
9920
  return normalized;
9123
9921
  }
9124
9922
 
@@ -9410,6 +10208,23 @@ function renderWorkToolGroup(event, eventIndex) {
9410
10208
  activityRows + '</div></details>';
9411
10209
  }
9412
10210
 
10211
+ function renderWorkThought(event, eventIndex) {
10212
+ var zh = currentLang() === 'zh';
10213
+ var content = String(event.content || '').trim();
10214
+ var completed = !!event.completed;
10215
+ var activityDomKey = String(event.id || ('thought-' + eventIndex));
10216
+ var label = completed ? (zh ? '进行了思考' : 'Thought') : (zh ? '思考中' : 'Thinking');
10217
+ var detailRows = '';
10218
+ if (content) {
10219
+ detailRows = '<div class="conversation-work-thought-text">' + esc(content) + '</div>';
10220
+ } else if (!completed) {
10221
+ detailRows = '<div class="conversation-work-thought-text conversation-work-thought-pending">' + esc(zh ? '正在思考…' : 'Thinking…') + '</div>';
10222
+ }
10223
+ return '<details class="conversation-work-activity conversation-work-thought" data-activity-key="' + escAttr(activityDomKey) + '"><summary>' +
10224
+ iconSvg('brain', 'thought', 'tiny') + '<span>' + esc(label) + '</span>' +
10225
+ '<span class="conversation-work-activity-chevron" aria-hidden="true"></span></summary>' + detailRows + '</details>';
10226
+ }
10227
+
9413
10228
  function workEventLabel(event) {
9414
10229
  var type = String(event && event.type || 'status').toLowerCase();
9415
10230
  var content = String(event && event.content || '').trim();
@@ -9514,6 +10329,16 @@ function renderWorkRunEvents(run, includeGuides) {
9514
10329
  }
9515
10330
  if (!rawEvent) continue;
9516
10331
  }
10332
+ if (rawType === 'thought_result') {
10333
+ for (var thoughtPriorIndex = events.length - 1; thoughtPriorIndex >= 0; thoughtPriorIndex--) {
10334
+ var thoughtPrior = events[thoughtPriorIndex];
10335
+ if (String(thoughtPrior && thoughtPrior.type || '').toLowerCase() !== 'thought' || thoughtPrior.completed) continue;
10336
+ events[thoughtPriorIndex] = Object.assign({}, thoughtPrior, { completed: true, content: rawEvent.content || thoughtPrior.content });
10337
+ rawEvent = null;
10338
+ break;
10339
+ }
10340
+ if (!rawEvent) continue;
10341
+ }
9517
10342
  events.push(rawEvent);
9518
10343
  }
9519
10344
  flushPublicText();
@@ -9554,6 +10379,7 @@ function renderWorkRunEvents(run, includeGuides) {
9554
10379
  }
9555
10380
  if (type.indexOf('guide') === 0 || event.guide) return renderWorkRunGuideMessage(event);
9556
10381
  if (type === 'tool_group') return renderWorkToolGroup(event, eventIndex);
10382
+ if (type === 'thought') return renderWorkThought(event, eventIndex);
9557
10383
  var activitySummary = type.indexOf('tool_') === 0 && type !== 'tool_call' && type !== 'tool_result';
9558
10384
  var cls = type.indexOf('guide') === 0 ? ' guide' : (type === 'error' ? ' error' : (activitySummary ? ' activity-summary' : ''));
9559
10385
  var label = activitySummary ? workToolActivityLabel(event.activity, Number(event.count || 1), !!event.completed) : workEventLabel(event) + (event.completed ? (currentLang() === 'zh' ? ' · 已完成' : ' · completed') : '');
@@ -10576,7 +11402,12 @@ function applyConversationSnapshot(s, requestedConversationId) {
10576
11402
  if (s && s.runtimeKey) registerRuntimeKey(snapshotTarget, s.runtimeKey);
10577
11403
  hydrateConversationBranchState(s);
10578
11404
  rebindQueueToRuntimeBranch(snapshotTarget);
10579
- if (s && Array.isArray(s.workRuns)) syncWorkRunsSnapshot(s.workRuns, snapshotTarget, String(s.activeBranchId || ''));
11405
+ if (s && Array.isArray(s.workRuns)) {
11406
+ var viewedBranchIdForSync = Array.isArray(s.viewedBranchNodePath) && s.viewedBranchNodePath.length
11407
+ ? String(s.viewedBranchNodePath[s.viewedBranchNodePath.length - 1])
11408
+ : '';
11409
+ syncWorkRunsSnapshot(s.workRuns, snapshotTarget, viewedBranchIdForSync || undefined);
11410
+ }
10580
11411
  applyAutoRouteRatingState(s);
10581
11412
  if (s && s.chatMessages) {
10582
11413
  renderChatMessages(s.chatMessages);
@@ -10639,9 +11470,9 @@ function addShellBlock(title, content) {
10639
11470
  var div = document.createElement('div');
10640
11471
  div.className = 'chat-msg assistant';
10641
11472
  div.innerHTML = '<div class="shell-block collapsed">' +
10642
- '<div class="shell-block-header" onclick="this.parentElement.classList.toggle(\'collapsed\')">' +
11473
+ '<button type="button" class="shell-block-header" aria-expanded="false" onclick="this.parentElement.classList.toggle(\'collapsed\');this.setAttribute(\'aria-expanded\',this.parentElement.classList.contains(\'collapsed\')?\'false\':\'true\')">' +
10643
11474
  '<span class="arrow">&gt;</span> ' + esc(title) +
10644
- '</div><div class="shell-block-body">' + esc(content) + '</div></div>';
11475
+ '</button><div class="shell-block-body">' + esc(content) + '</div></div>';
10645
11476
  els['chat-area'].appendChild(div);
10646
11477
  autoScrollIfAtBottom();
10647
11478
  }
@@ -10650,10 +11481,10 @@ function addDiffBlock(title, adds, dels, linesHTML) {
10650
11481
  var div = document.createElement('div');
10651
11482
  div.className = 'chat-msg assistant';
10652
11483
  div.innerHTML = '<div class="diff-block collapsed">' +
10653
- '<div class="diff-block-header" onclick="this.parentElement.classList.toggle(\'collapsed\')">' +
11484
+ '<button type="button" class="diff-block-header" aria-expanded="false" onclick="this.parentElement.classList.toggle(\'collapsed\');this.setAttribute(\'aria-expanded\',this.parentElement.classList.contains(\'collapsed\')?\'false\':\'true\')">' +
10654
11485
  '<span class="arrow">&gt;</span> ' + esc(title) +
10655
11486
  '<span class="diff-stat"><span class="diff-add">+' + adds + '</span><span class="diff-del">-' + dels + '</span></span>' +
10656
- '</div><div class="diff-block-body">' + linesHTML + '</div></div>';
11487
+ '</button><div class="diff-block-body">' + linesHTML + '</div></div>';
10657
11488
  els['chat-area'].appendChild(div);
10658
11489
  autoScrollIfAtBottom();
10659
11490
  }
@@ -11135,7 +11966,7 @@ window.stopCurrentConversation = async function() {
11135
11966
  return true;
11136
11967
  };
11137
11968
 
11138
- window.submitCurrentAction = function() {
11969
+ window.submitCurrentAction = function(source) {
11139
11970
  if (currentFlowRunning() && flowTakeoverMatchesCurrent()) {
11140
11971
  if (!promptHasText()) {
11141
11972
  window.stopFlowRun();
@@ -11170,6 +12001,8 @@ window.submitCurrentAction = function() {
11170
12001
  return;
11171
12002
  }
11172
12003
  if (isCurrentConversationRunning() && !promptHasText()) {
12004
+ // 回车:运行中空输入不操作(打断仅通过 Esc 或点击打断按钮)。
12005
+ if (source === 'enter') return;
11173
12006
  window.stopCurrentConversation();
11174
12007
  return;
11175
12008
  }
@@ -11211,6 +12044,98 @@ window.renderContextWindow = function() {
11211
12044
  ring.style.mask = 'radial-gradient(farthest-side, transparent 58%, #000 60%)';
11212
12045
  var status = c.warning === 'over_limit' ? t('status.contextOverLimit') : (c.warning === 'near_limit' ? t('status.contextNearLimit') : t('status.contextTokens'));
11213
12046
  ring.title = status + ': ' + used + ' / ' + max;
12047
+ if (window.renderContextInspector) window.renderContextInspector();
12048
+ };
12049
+
12050
+ function contextInspectorValue(value) {
12051
+ var number = Number(value);
12052
+ return Number.isFinite(number) ? Math.max(0, Math.round(number)) : 0;
12053
+ }
12054
+
12055
+ function contextInspectorCell(label, value) {
12056
+ return '<div class="context-inspector-cell"><span class="context-inspector-label">' + esc(label) + '</span><span class="context-inspector-value">' + esc(value) + '</span></div>';
12057
+ }
12058
+
12059
+ window.renderContextInspector = function() {
12060
+ var panel = document.getElementById('context-inspector');
12061
+ var ring = els['context-token-ring'] || document.getElementById('context-token-ring');
12062
+ if (!panel || !ring) return;
12063
+ ring.setAttribute('aria-expanded', state.contextInspectorOpen ? 'true' : 'false');
12064
+ panel.classList.toggle('open', state.contextInspectorOpen);
12065
+ if (!state.contextInspectorOpen) {
12066
+ panel.innerHTML = '';
12067
+ return;
12068
+ }
12069
+ var c = state.contextWindow || {};
12070
+ var max = Math.max(1, contextInspectorValue(c.maxTokens) || 1);
12071
+ var used = contextInspectorValue(c.estimatedTokens);
12072
+ var active = contextInspectorValue(c.buildBlockTokens);
12073
+ var history = contextInspectorValue(c.longHistoryTokens);
12074
+ var activeTrigger = contextInspectorValue(c.buildBlockTriggerTokens);
12075
+ var historyTrigger = contextInspectorValue(c.longHistoryTriggerTokens);
12076
+ var activeRetention = contextInspectorValue(c.buildBlockRetentionTokens);
12077
+ var historyRetention = contextInspectorValue(c.longHistoryRetentionTokens);
12078
+ var percent = Math.min(100, Math.round((used / max) * 100));
12079
+ var fillColor = c.warning === 'over_limit' ? 'var(--nm-state-danger)' : (c.warning === 'near_limit' ? 'var(--nm-state-warning)' : 'var(--accent)');
12080
+ var compression = state.contextCompression || null;
12081
+ var last = compression
12082
+ ? ((compression.fallback ? t('status.contextModeFallback') : t('status.contextModeModel')) + ' · ' + String(compression.originalMessages || 0) + ' → ' + String(compression.compressedMessages || 0) + ' ' + t('status.messages'))
12083
+ : t('status.noCompressionShort');
12084
+ var busy = !!state.contextMutationPending;
12085
+ var running = typeof isCurrentConversationRunning === 'function' && isCurrentConversationRunning();
12086
+ panel.innerHTML = '<div class="context-inspector-head"><div><div class="context-inspector-title">' + esc(t('status.contextInspector')) + '</div><div class="context-inspector-subtitle">' + esc(t('status.contextInspectorHint')) + '</div></div>' +
12087
+ '<button type="button" class="context-inspector-close" onclick="window.closeContextInspector()" aria-label="' + esc(t('common.close') || 'Close') + '">×</button></div>' +
12088
+ '<div class="context-inspector-meter"><div class="context-inspector-meter-fill" style="width:' + percent + '%;background:' + fillColor + '"></div></div>' +
12089
+ '<div class="context-inspector-grid">' +
12090
+ contextInspectorCell(t('status.contextTokens'), used + ' / ' + max) +
12091
+ contextInspectorCell(t('status.activeBuild'), active + ' / ' + activeTrigger) +
12092
+ contextInspectorCell(t('status.longHistory'), history + ' / ' + historyTrigger) +
12093
+ contextInspectorCell(t('status.retention'), activeRetention + ' + ' + historyRetention) +
12094
+ contextInspectorCell(t('status.hotCache'), contextInspectorValue(c.cacheEntries) + ' entries') +
12095
+ contextInspectorCell(t('status.coldArchive'), contextInspectorValue(c.archiveEntries) + ' entries') +
12096
+ '</div>' +
12097
+ '<div class="context-inspector-section"><div class="context-inspector-section-title">' + esc(t('status.lastCompression')) + '</div><div class="context-inspector-meta">' + esc(last) + '</div>' +
12098
+ (compression && compression.model ? '<div class="context-inspector-meta">' + esc(compression.model) + '</div>' : '') + '</div>' +
12099
+ '<div class="context-inspector-actions"><button type="button" class="sec-btn primary" onclick="window.compressContextNow()"' + (busy || running ? ' disabled' : '') + '>' + esc(busy ? t('status.compressing') : t('status.compressNow')) + '</button></div>';
12100
+ };
12101
+
12102
+ window.closeContextInspector = function() {
12103
+ state.contextInspectorOpen = false;
12104
+ window.renderContextInspector();
12105
+ };
12106
+
12107
+ window.toggleContextInspector = function() {
12108
+ state.contextInspectorOpen = !state.contextInspectorOpen;
12109
+ window.hideContextWindowTooltip();
12110
+ window.renderContextInspector();
12111
+ };
12112
+
12113
+ window.compressContextNow = function() {
12114
+ if (state.contextMutationPending) return;
12115
+ if (typeof isCurrentConversationRunning === 'function' && isCurrentConversationRunning()) {
12116
+ showUiNotice(t('status.compressionBusy'), 'error', 'context-compress-busy');
12117
+ return;
12118
+ }
12119
+ if (!api.compressContext) {
12120
+ showUiNotice(t('status.compressionFailed'), 'error', 'context-compress-unavailable');
12121
+ return;
12122
+ }
12123
+ state.contextMutationPending = true;
12124
+ window.renderContextInspector();
12125
+ Promise.resolve(api.compressContext({ target: currentConversationTarget(), force: true })).then(function(result) {
12126
+ if (!result || result.ok === false) throw new Error((result && result.error) || t('status.compressionFailed'));
12127
+ if (result.contextWindow) state.contextWindow = result.contextWindow;
12128
+ if (result.contextCompression !== undefined) state.contextCompression = result.contextCompression;
12129
+ window.renderContextWindow();
12130
+ window.renderRightStatusPanel();
12131
+ showUiNotice(t('status.compressedNow'), 'success', 'context-compress-success');
12132
+ return result;
12133
+ }).catch(function(error) {
12134
+ showUiNotice(error && error.message ? error.message : t('status.compressionFailed'), 'error', 'context-compress-error');
12135
+ }).finally(function() {
12136
+ state.contextMutationPending = false;
12137
+ window.renderContextInspector();
12138
+ });
11214
12139
  };
11215
12140
 
11216
12141
  window.contextWindowTooltipHtml = function() {
@@ -11831,6 +12756,7 @@ window.renderQueuePanel = function() {
11831
12756
  }
11832
12757
  var toggle = panel.querySelector('#queue-expand-btn');
11833
12758
  if (toggle) {
12759
+ toggle.setAttribute('aria-expanded', state.queueCollapsed ? 'false' : 'true');
11834
12760
  toggle.title = state.queueCollapsed ? t('queue.expand') : t('queue.collapse');
11835
12761
  toggle.innerHTML = iconSvg(state.queueCollapsed ? 'chevron-up' : 'chevron-down', toggle.title, 'tiny');
11836
12762
  }
@@ -12120,7 +13046,14 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12120
13046
  var displayText = optimisticAttachments.length
12121
13047
  ? rawText + (rawText ? '\n\n' : '') + '[' + optimisticAttachments.length + ' image attachment' + (optimisticAttachments.length === 1 ? '' : 's') + ']'
12122
13048
  : text;
12123
- if (!text && !optimisticAttachments.length) return;
13049
+ if (!text && !optimisticAttachments.length) {
13050
+ // 空输入时发送默认句段「继续 / Continue」,按界面语言切换。
13051
+ var continuePhrase = t('input.continue');
13052
+ rawText = continuePhrase;
13053
+ text = continuePhrase;
13054
+ requestMessage = continuePhrase;
13055
+ displayText = continuePhrase;
13056
+ }
12124
13057
 
12125
13058
  var lockedConversationId = activeConversationId();
12126
13059
  var lockedTarget = currentConversationTarget(lockedConversationId);
@@ -12868,19 +13801,25 @@ window.toggleBottom = function() {
12868
13801
  };
12869
13802
 
12870
13803
  // === Right Sidebar Tabs ===
13804
+ function rightTabButtonHtml(tab, icon, label, active) {
13805
+ return '<button class="tab-btn' + (active ? ' active' : '') + '" id="right-tab-' + tab + '" role="tab" aria-selected="' + (active ? 'true' : 'false') + '" aria-controls="panel-' + tab + '" tabindex="' + (active ? '0' : '-1') + '" data-tab="' + tab + '" onclick="window.switchRightTab(&quot;' + tab + '&quot;)" onkeydown="window.handleRightTabKey(event)" title="' + escAttr(label) + '">' + iconOnly(icon, label) + '</button>';
13806
+ }
13807
+
12871
13808
  window.upgradeRightSidebar = function() {
12872
13809
  var rightTabs = els['right-tabs'] || document.getElementById('right-tabs');
12873
13810
  var rightContent = els['right-content'] || document.getElementById('right-content');
12874
13811
  if (!rightTabs || !rightContent || rightTabs.getAttribute('data-upgraded') === 'true') return;
12875
13812
  rightTabs.setAttribute('data-upgraded', 'true');
13813
+ rightTabs.setAttribute('role', 'tablist');
13814
+ rightTabs.setAttribute('aria-label', t('right.files'));
12876
13815
  rightTabs.innerHTML =
12877
- '<button class="tab-btn active" data-tab="file-tree" onclick="window.switchRightTab(&quot;file-tree&quot;)" title="' + escAttr(t('right.files')) + '">' + iconOnly('folder', t('right.files')) + '</button>' +
12878
- '<button class="tab-btn" data-tab="editor" onclick="window.switchRightTab(&quot;editor&quot;)" title="' + escAttr(t('right.editor')) + '">' + iconOnly('square-pen', t('right.editor')) + '</button>' +
12879
- '<button class="tab-btn" data-tab="plan" onclick="window.switchRightTab(&quot;plan&quot;)" title="' + escAttr(t('right.plan')) + '">' + iconOnly('list-checks', t('right.plan')) + '</button>' +
12880
- '<button class="tab-btn" data-tab="subagent" onclick="window.switchRightTab(&quot;subagent&quot;)" title="' + escAttr(t('right.subagents')) + '">' + iconOnly('bot', t('right.subagents')) + '</button>' +
12881
- '<button class="tab-btn" data-tab="browser" onclick="window.switchRightTab(&quot;browser&quot;)" title="' + escAttr(t('right.browser')) + '">' + iconOnly('globe', t('right.browser')) + '</button>' +
12882
- '<button class="tab-btn" data-tab="status" onclick="window.switchRightTab(&quot;status&quot;)" title="' + escAttr(t('right.status')) + '">' + iconOnly('activity', t('right.status')) + '</button>' +
12883
- '<button class="tab-btn" data-tab="archives" onclick="window.switchRightTab(&quot;archives&quot;)" title="' + escAttr(t('right.archives')) + '">' + iconOnly('archive', t('right.archives')) + '</button>' +
13816
+ rightTabButtonHtml('file-tree', 'folder', t('right.files'), state.rightTab === 'file-tree') +
13817
+ rightTabButtonHtml('editor', 'square-pen', t('right.editor'), state.rightTab === 'editor') +
13818
+ rightTabButtonHtml('plan', 'list-checks', t('right.plan'), state.rightTab === 'plan') +
13819
+ rightTabButtonHtml('subagent', 'bot', t('right.subagents'), state.rightTab === 'subagent') +
13820
+ rightTabButtonHtml('browser', 'globe', t('right.browser'), state.rightTab === 'browser') +
13821
+ rightTabButtonHtml('status', 'activity', t('right.status'), state.rightTab === 'status') +
13822
+ rightTabButtonHtml('archives', 'archive', t('right.archives'), state.rightTab === 'archives') +
12884
13823
  '<div class="tab-divider"></div>' +
12885
13824
  '<button class="tab-btn" onclick="window.toggleRight()" title="' + escAttr(t('right.close')) + '">' + iconOnly('x', t('right.close')) + '</button>';
12886
13825
 
@@ -12888,7 +13827,7 @@ window.upgradeRightSidebar = function() {
12888
13827
  if (filePanel && !filePanel.getAttribute('data-upgraded')) {
12889
13828
  filePanel.setAttribute('data-upgraded', 'true');
12890
13829
  var tree = document.getElementById('file-tree-container');
12891
- filePanel.innerHTML = '<div class="right-section-head"><span class="right-section-title">' + esc(t('right.fileTree')) + '</span><button class="archive-action-btn" onclick="window.loadFileTree()">' + esc(t('right.refresh')) + '</button></div><div id="file-tree-container"></div>';
13830
+ filePanel.innerHTML = '<div class="right-section-head"><span class="right-section-title">' + esc(t('right.fileTree')) + '</span><button class="archive-action-btn" onclick="window.loadFileTree()">' + esc(t('right.refresh')) + '</button></div><div id="file-tree-container" role="tree"></div>';
12892
13831
  if (tree) document.getElementById('file-tree-container').innerHTML = tree.innerHTML;
12893
13832
  }
12894
13833
  var subPanel = document.getElementById('panel-subagent');
@@ -12919,6 +13858,14 @@ window.upgradeRightSidebar = function() {
12919
13858
  archivesPanel.innerHTML = '<div class="right-section-head"><span class="right-section-title">' + esc(t('right.archives')) + '</span><button class="archive-action-btn" onclick="window.refreshRightArchives()">' + esc(t('right.refresh')) + '</button></div><div style="display:flex;gap:6px;margin-bottom:8px;"><button class="sec-btn primary" style="flex:1;" onclick="window.archiveCurrent()">' + esc(t('archive.current')) + '</button></div><div id="right-archive-list"></div>';
12920
13859
  rightContent.appendChild(archivesPanel);
12921
13860
  }
13861
+ var upgradedPanels = rightContent.querySelectorAll('.tab-panel');
13862
+ for (var upgradedIndex = 0; upgradedIndex < upgradedPanels.length; upgradedIndex++) {
13863
+ var upgradedPanel = upgradedPanels[upgradedIndex];
13864
+ var upgradedTab = upgradedPanel.id.replace(/^panel-/, '');
13865
+ upgradedPanel.setAttribute('role', 'tabpanel');
13866
+ upgradedPanel.setAttribute('aria-labelledby', 'right-tab-' + upgradedTab);
13867
+ upgradedPanel.hidden = upgradedTab !== state.rightTab;
13868
+ }
12922
13869
  };
12923
13870
 
12924
13871
  window.switchRightTab = function(tab) {
@@ -12932,11 +13879,16 @@ window.switchRightTab = function(tab) {
12932
13879
  if (!rightTabs || !rightContent) return;
12933
13880
  var btns = rightTabs.querySelectorAll('.tab-btn[data-tab]');
12934
13881
  for (var i = 0; i < btns.length; i++) {
12935
- btns[i].classList.toggle('active', btns[i].getAttribute('data-tab') === tab);
13882
+ var selected = btns[i].getAttribute('data-tab') === tab;
13883
+ btns[i].classList.toggle('active', selected);
13884
+ btns[i].setAttribute('aria-selected', selected ? 'true' : 'false');
13885
+ btns[i].tabIndex = selected ? 0 : -1;
12936
13886
  }
12937
13887
  var panels = rightContent.querySelectorAll('.tab-panel');
12938
13888
  for (var j = 0; j < panels.length; j++) {
12939
- panels[j].classList.toggle('active', panels[j].id === 'panel-' + tab);
13889
+ var panelActive = panels[j].id === 'panel-' + tab;
13890
+ panels[j].classList.toggle('active', panelActive);
13891
+ panels[j].hidden = !panelActive;
12940
13892
  }
12941
13893
  // Load content on first access
12942
13894
  if (tab === 'file-tree') window.loadFileTree();
@@ -13028,7 +13980,7 @@ window.renderConversationPlan = function() {
13028
13980
  var plan = normalizeConversationPlan(state.conversationPlan);
13029
13981
  var items = plan.items || [];
13030
13982
  var html = '<div class="plan-compose">' +
13031
- '<input id="conversation-plan-input" class="plan-input" placeholder="' + escAttr(t('plan.placeholder')) + '" onkeydown="if(event.key===&quot;Enter&quot;)window.addConversationPlanItem()">' +
13983
+ '<input id="conversation-plan-input" class="plan-input" placeholder="' + escAttr(t('plan.placeholder')) + '" onkeydown="if(event.key===&quot;Enter&quot;&amp;&amp;!event.isComposing&amp;&amp;event.key!==&quot;Process&quot;&amp;&amp;event.keyCode!==229)window.addConversationPlanItem()">' +
13032
13984
  '<button class="sec-btn primary" onclick="window.addConversationPlanItem()">' + esc(t('plan.add')) + '</button>' +
13033
13985
  '</div>';
13034
13986
  if (!items.length) {
@@ -13096,7 +14048,7 @@ window.editConversationPlanItem = function(idx) {
13096
14048
  var rows = target.querySelectorAll('.plan-row');
13097
14049
  var row = rows[idx];
13098
14050
  if (!row) return;
13099
- row.querySelector('.right-row-body').innerHTML = '<input class="plan-edit-input" id="conversation-plan-edit-' + idx + '" value="' + escAttr(item.text) + '" onkeydown="if(event.key===&quot;Enter&quot;)window.saveConversationPlanEdit(' + idx + ')">';
14051
+ row.querySelector('.right-row-body').innerHTML = '<input class="plan-edit-input" id="conversation-plan-edit-' + idx + '" value="' + escAttr(item.text) + '" onkeydown="if(event.key===&quot;Enter&quot;&amp;&amp;!event.isComposing&amp;&amp;event.key!==&quot;Process&quot;&amp;&amp;event.keyCode!==229)window.saveConversationPlanEdit(' + idx + ')">';
13100
14052
  var actions = row.querySelector('.plan-actions');
13101
14053
  if (actions) {
13102
14054
  actions.innerHTML = '<button class="archive-action-btn" onclick="window.saveConversationPlanEdit(' + idx + ')">' + esc(t('common.save')) + '</button>' +
@@ -13247,6 +14199,8 @@ window.renderRightStatusPanel = function() {
13247
14199
  if (contextWindow) {
13248
14200
  html += '<div class="right-row"><div class="right-row-body"><span class="right-row-title">' + esc(t('status.contextTokens')) + '</span><span class="right-row-meta">' +
13249
14201
  esc(String(contextWindow.estimatedTokens || 0) + ' / ' + String(contextWindow.maxTokens || 0) + ' | ' + String(contextWindow.warning || 'ok')) +
14202
+ (contextWindow.buildBlockTokens !== undefined ? '<br>' + esc(t('status.activeBuild')) + ': ' + esc(String(contextWindow.buildBlockTokens || 0) + ' / ' + String(contextWindow.buildBlockTriggerTokens || 0)) +
14203
+ ' · ' + esc(t('status.longHistory')) + ': ' + esc(String(contextWindow.longHistoryTokens || 0) + ' / ' + String(contextWindow.longHistoryTriggerTokens || 0)) : '') +
13250
14204
  '</span></div></div>';
13251
14205
  }
13252
14206
  if (compression) {
@@ -13470,6 +14424,35 @@ window.spawnTerminal = function(shellId, options) {
13470
14424
  return window.addTerminalTab(shellId, options);
13471
14425
  };
13472
14426
 
14427
+ window.handleTerminalTabKey = function(event) {
14428
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
14429
+ var tabs = Array.prototype.slice.call(document.querySelectorAll('#terminal-tabs .terminal-tab'));
14430
+ var index = tabs.indexOf(event.currentTarget);
14431
+ if (index < 0 || !tabs.length) return false;
14432
+ var next = index;
14433
+ if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
14434
+ else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
14435
+ else if (event.key === 'Home') next = 0;
14436
+ else if (event.key === 'End') next = tabs.length - 1;
14437
+ else if (event.key === 'Enter' || event.key === ' ') next = index;
14438
+ else if (event.key === 'Delete') {
14439
+ event.preventDefault();
14440
+ var closingId = parseInt(tabs[index].getAttribute('data-tab-id'), 10);
14441
+ window.closeTerminalTab(closingId);
14442
+ requestAnimationFrame(function() {
14443
+ var activeTab = document.querySelector('#terminal-tabs .terminal-tab.active') || document.querySelector('#terminal-tabs .terminal-tab');
14444
+ if (activeTab) activeTab.focus({ preventScroll:true });
14445
+ });
14446
+ return true;
14447
+ }
14448
+ else return false;
14449
+ event.preventDefault();
14450
+ var tabId = parseInt(tabs[next].getAttribute('data-tab-id'), 10);
14451
+ window.switchTerminalTab(tabId);
14452
+ tabs[next].focus({ preventScroll:true });
14453
+ return true;
14454
+ };
14455
+
13473
14456
  window.addTerminalTab = function(shellId, options) {
13474
14457
  options = options || {};
13475
14458
  shellId = normalizeTerminalShell(shellId || state._terminalShell);
@@ -13487,13 +14470,23 @@ window.addTerminalTab = function(shellId, options) {
13487
14470
  // Create tab button
13488
14471
  var tab = document.createElement('div');
13489
14472
  tab.className = 'terminal-tab active';
14473
+ tab.id = 'terminal-tab-' + tabId;
14474
+ tab.setAttribute('role', 'tab');
14475
+ tab.setAttribute('aria-selected', 'true');
14476
+ tab.setAttribute('aria-controls', 'terminal-pane-' + tabId);
14477
+ tab.tabIndex = 0;
13490
14478
  tab.setAttribute('data-tab-id', tabId);
13491
- tab.innerHTML = name + '<button class="close-tab" onclick="event.stopPropagation();window.closeTerminalTab(' + tabId + ')" title="' + escAttr(t('common.closeTab')) + '">' + iconSvg('x', t('common.closeTab'), 'tiny') + '</button>';
14479
+ tab.innerHTML = name + '<button type="button" class="close-tab" onclick="event.stopPropagation();window.closeTerminalTab(' + tabId + ')" title="' + escAttr(t('common.closeTab')) + '">' + iconSvg('x', t('common.closeTab'), 'tiny') + '</button>';
13492
14480
  tab.onclick = function() { window.switchTerminalTab(tabId); };
14481
+ tab.addEventListener('keydown', window.handleTerminalTabKey);
13493
14482
 
13494
14483
  // Deactivate all other tabs
13495
14484
  var allTabs = tabsContainer.querySelectorAll('.terminal-tab');
13496
- for (var i = 0; i < allTabs.length; i++) allTabs[i].classList.remove('active');
14485
+ for (var i = 0; i < allTabs.length; i++) {
14486
+ allTabs[i].classList.remove('active');
14487
+ allTabs[i].setAttribute('aria-selected', 'false');
14488
+ allTabs[i].tabIndex = -1;
14489
+ }
13497
14490
 
13498
14491
  tabsContainer.appendChild(tab);
13499
14492
 
@@ -13501,18 +14494,20 @@ window.addTerminalTab = function(shellId, options) {
13501
14494
  var pane = document.createElement('div');
13502
14495
  pane.className = 'terminal-pane active';
13503
14496
  pane.id = 'terminal-pane-' + tabId;
14497
+ pane.setAttribute('role', 'tabpanel');
14498
+ pane.setAttribute('aria-labelledby', tab.id);
13504
14499
  pane.setAttribute('data-tab-id', tabId);
13505
14500
  pane.setAttribute('data-session', '');
13506
14501
  pane.innerHTML =
13507
14502
  '<div class="terminal-output"><span style="color:var(--accent2);">Connecting ' + shellId + '...</span>\r\n</div>' +
13508
14503
  '<div class="terminal-input-row">' +
13509
14504
  '<span class="terminal-prompt">' + shellLabel + '></span>' +
13510
- '<input class="terminal-input" type="text" placeholder="Enter command..." onkeydown="if(event.key===\'Enter\')window.terminalSend()">' +
14505
+ '<input class="terminal-input" type="text" placeholder="Enter command..." onkeydown="if(event.key===\'Enter\'&&!event.isComposing&&event.key!==\'Process\'&&event.keyCode!==229)window.terminalSend()">' +
13511
14506
  '</div>';
13512
14507
 
13513
14508
  // Deactivate all other panes
13514
14509
  var allPanes = body.querySelectorAll('.terminal-pane');
13515
- for (var j = 0; j < allPanes.length; j++) allPanes[j].classList.remove('active');
14510
+ for (var j = 0; j < allPanes.length; j++) { allPanes[j].classList.remove('active'); allPanes[j].hidden = true; }
13516
14511
 
13517
14512
  body.appendChild(pane);
13518
14513
 
@@ -13563,21 +14558,33 @@ window.ensureTerminalTakeoverPane = function(session) {
13563
14558
 
13564
14559
  var tab = document.createElement('div');
13565
14560
  tab.className = 'terminal-tab agent-takeover marquee-border';
14561
+ tab.id = 'terminal-tab-' + tabId;
14562
+ tab.setAttribute('role', 'tab');
14563
+ tab.setAttribute('aria-selected', 'true');
14564
+ tab.setAttribute('aria-controls', 'terminal-pane-' + tabId);
14565
+ tab.tabIndex = 0;
13566
14566
  tab.setAttribute('data-tab-id', tabId);
13567
14567
  tab.setAttribute('data-takeover-session', key);
13568
14568
  tab.setAttribute('data-takeover-workspace-id', session.workspaceId || '');
13569
14569
  tab.setAttribute('data-takeover-conversation', session.conversationId || 'default');
13570
- tab.innerHTML = 'Agent:' + esc(session.name || key) + '<button class="close-tab" onclick="event.stopPropagation();window.closeTerminalTab(' + tabId + ')" title="' + escAttr(t('common.closeTab')) + '">' + iconSvg('x', t('common.closeTab'), 'tiny') + '</button>';
14570
+ tab.innerHTML = 'Agent:' + esc(session.name || key) + '<button type="button" class="close-tab" onclick="event.stopPropagation();window.closeTerminalTab(' + tabId + ')" title="' + escAttr(t('common.closeTab')) + '">' + iconSvg('x', t('common.closeTab'), 'tiny') + '</button>';
13571
14571
  tab.onclick = function() { window.switchTerminalTab(tabId); };
14572
+ tab.addEventListener('keydown', window.handleTerminalTabKey);
13572
14573
 
13573
14574
  var allTabs = tabsContainer.querySelectorAll('.terminal-tab');
13574
- for (var i = 0; i < allTabs.length; i++) allTabs[i].classList.remove('active');
14575
+ for (var i = 0; i < allTabs.length; i++) {
14576
+ allTabs[i].classList.remove('active');
14577
+ allTabs[i].setAttribute('aria-selected', 'false');
14578
+ allTabs[i].tabIndex = -1;
14579
+ }
13575
14580
  tab.classList.add('active');
13576
14581
  tabsContainer.appendChild(tab);
13577
14582
 
13578
14583
  var pane = document.createElement('div');
13579
14584
  pane.className = 'terminal-pane active agent-takeover marquee-border';
13580
14585
  pane.id = 'terminal-pane-' + tabId;
14586
+ pane.setAttribute('role', 'tabpanel');
14587
+ pane.setAttribute('aria-labelledby', tab.id);
13581
14588
  pane.setAttribute('data-tab-id', tabId);
13582
14589
  pane.setAttribute('data-session', '');
13583
14590
  pane.setAttribute('data-takeover-session', key);
@@ -13589,11 +14596,11 @@ window.ensureTerminalTakeoverPane = function(session) {
13589
14596
  '<div class="terminal-output"></div>' +
13590
14597
  '<div class="terminal-input-row">' +
13591
14598
  '<span class="terminal-prompt">Agent&gt;</span>' +
13592
- '<input class="terminal-input" type="text" placeholder="Enter command..." onkeydown="if(event.key===\'Enter\')window.terminalSend()">' +
14599
+ '<input class="terminal-input" type="text" placeholder="Enter command..." onkeydown="if(event.key===\'Enter\'&&!event.isComposing&&event.key!==\'Process\'&&event.keyCode!==229)window.terminalSend()">' +
13593
14600
  '</div>';
13594
14601
 
13595
14602
  var allPanes = body.querySelectorAll('.terminal-pane');
13596
- for (var j = 0; j < allPanes.length; j++) allPanes[j].classList.remove('active');
14603
+ for (var j = 0; j < allPanes.length; j++) { allPanes[j].classList.remove('active'); allPanes[j].hidden = true; }
13597
14604
  body.appendChild(pane);
13598
14605
  tabsContainer.scrollLeft = tabsContainer.scrollWidth;
13599
14606
  window.switchTerminalTab(tabId);
@@ -13688,12 +14695,17 @@ window.switchTerminalTab = function(tabId) {
13688
14695
  // Update tabs
13689
14696
  var allTabs = document.querySelectorAll('.terminal-tab');
13690
14697
  for (var i = 0; i < allTabs.length; i++) {
13691
- allTabs[i].classList.toggle('active', parseInt(allTabs[i].getAttribute('data-tab-id')) === tabId);
14698
+ var selected = parseInt(allTabs[i].getAttribute('data-tab-id')) === tabId;
14699
+ allTabs[i].classList.toggle('active', selected);
14700
+ allTabs[i].setAttribute('aria-selected', selected ? 'true' : 'false');
14701
+ allTabs[i].tabIndex = selected ? 0 : -1;
13692
14702
  }
13693
14703
  // Update panes
13694
14704
  var allPanes = document.querySelectorAll('.terminal-pane');
13695
14705
  for (var j = 0; j < allPanes.length; j++) {
13696
- allPanes[j].classList.toggle('active', parseInt(allPanes[j].getAttribute('data-tab-id')) === tabId);
14706
+ var active = parseInt(allPanes[j].getAttribute('data-tab-id')) === tabId;
14707
+ allPanes[j].classList.toggle('active', active);
14708
+ allPanes[j].hidden = !active;
13697
14709
  }
13698
14710
  // Update active session
13699
14711
  var activePane = document.querySelector('.terminal-pane.active');
@@ -13879,6 +14891,7 @@ function renderTodo() {
13879
14891
  label.textContent = t('goal.list') + (items.length ? ' ' + items.length : '');
13880
14892
  var toggle = wrap.querySelector('#todo-header .stack-icon-btn');
13881
14893
  if (toggle) {
14894
+ toggle.setAttribute('aria-expanded', state.todoCollapsed ? 'false' : 'true');
13882
14895
  var title = state.todoCollapsed ? t('queue.expand') : t('queue.collapse');
13883
14896
  toggle.title = title;
13884
14897
  toggle.innerHTML = iconSvg(state.todoCollapsed ? 'chevron-up' : 'chevron-down', title, 'tiny');
@@ -13891,10 +14904,10 @@ function renderTodo() {
13891
14904
  for (var i = 0; i < items.length; i++) {
13892
14905
  var item = items[i];
13893
14906
  var done = item.status === 'done';
13894
- html += '<div class="todo-item ' + (done ? 'done' : '') + '" onclick="window.checkTodo(' + i + ')" title="' + escAttr(item.text) + '">' +
14907
+ html += '<button type="button" class="todo-item ' + (done ? 'done' : '') + '" onclick="window.checkTodo(' + i + ')" title="' + escAttr(item.text) + '" aria-pressed="' + (done ? 'true' : 'false') + '">' +
13895
14908
  '<span class="todo-check">' + (done ? iconSvg('check', 'done', 'tiny') : '') + '</span>' +
13896
14909
  '<span class="todo-text">' + esc(item.text) + '</span>' +
13897
- '</div>';
14910
+ '</button>';
13898
14911
  }
13899
14912
  list.innerHTML = html;
13900
14913
  }
@@ -13995,8 +15008,61 @@ window.setInputMode = function(mode, persist) {
13995
15008
  };
13996
15009
 
13997
15010
  // === Sub-Window System ===
15011
+ var subWindowOriginFocus = null;
15012
+ function newmarkFocusableElements(container) {
15013
+ if (!container || !container.querySelectorAll) return [];
15014
+ return Array.prototype.slice.call(container.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [contenteditable="true"], [tabindex]:not([tabindex="-1"])')).filter(function(element) {
15015
+ return !element.hidden && element.getAttribute('aria-hidden') !== 'true' && element.getClientRects().length > 0;
15016
+ });
15017
+ }
15018
+
15019
+ function focusSubWindowInitial() {
15020
+ var dialog = els['sub-win'];
15021
+ if (!dialog || !els['sub-win-overlay'].classList.contains('open')) return;
15022
+ var body = els['sub-win-body'];
15023
+ var target = body && (body.querySelector('[autofocus]') || newmarkFocusableElements(body)[0]);
15024
+ if (!target) target = dialog.querySelector('.sub-win-close') || dialog;
15025
+ if (target && typeof target.focus === 'function') target.focus({ preventScroll: true });
15026
+ }
15027
+
15028
+ function updateApplicationInertState() {
15029
+ var commandOpen = !!(document.getElementById('command-surface-overlay') && document.getElementById('command-surface-overlay').classList.contains('open'));
15030
+ var subWindowOpen = !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'));
15031
+ var topbar = document.getElementById('topbar');
15032
+ var main = document.getElementById('main');
15033
+ if (topbar) topbar.inert = commandOpen || subWindowOpen;
15034
+ if (main) main.inert = commandOpen || subWindowOpen;
15035
+ if (els['sub-win-overlay']) els['sub-win-overlay'].inert = commandOpen;
15036
+ }
15037
+ window.updateApplicationInertState = updateApplicationInertState;
15038
+
15039
+ function trapNewmarkDialogFocus(event, container) {
15040
+ if (!event || event.key !== 'Tab' || !container) return false;
15041
+ var items = newmarkFocusableElements(container);
15042
+ if (!items.length) {
15043
+ event.preventDefault();
15044
+ if (typeof container.focus === 'function') container.focus();
15045
+ return true;
15046
+ }
15047
+ var first = items[0];
15048
+ var last = items[items.length - 1];
15049
+ var active = document.activeElement;
15050
+ if (event.shiftKey && (active === first || !container.contains(active))) {
15051
+ event.preventDefault();
15052
+ last.focus();
15053
+ return true;
15054
+ }
15055
+ if (!event.shiftKey && (active === last || !container.contains(active))) {
15056
+ event.preventDefault();
15057
+ first.focus();
15058
+ return true;
15059
+ }
15060
+ return false;
15061
+ }
15062
+
13998
15063
  window.openSubWin = function(title, html) {
13999
15064
  var overlayOpen = els['sub-win-overlay'].classList.contains('open');
15065
+ if (!overlayOpen && document.activeElement && typeof document.activeElement.focus === 'function') subWindowOriginFocus = document.activeElement;
14000
15066
  if (overlayOpen && !state.restoringSubWindow && state.activeSubWindowView) {
14001
15067
  if (!state.subWindowStack) state.subWindowStack = [];
14002
15068
  state.subWindowStack.push({
@@ -14010,6 +15076,25 @@ window.openSubWin = function(title, html) {
14010
15076
  els['sub-win-body'].innerHTML = html;
14011
15077
  els['sub-win'].classList.toggle('memory-lab-window', !!(state.activeSubWindowView && state.activeSubWindowView.name === 'memoryLab'));
14012
15078
  els['sub-win-overlay'].classList.add('open');
15079
+ updateApplicationInertState();
15080
+ requestAnimationFrame(focusSubWindowInitial);
15081
+ };
15082
+
15083
+ window.handleRightTabKey = function(event) {
15084
+ if (!event) return;
15085
+ var tabs = Array.prototype.slice.call((els['right-tabs'] || document).querySelectorAll('.tab-btn[data-tab]'));
15086
+ var index = tabs.indexOf(event.currentTarget);
15087
+ if (index < 0) return;
15088
+ var next = index;
15089
+ if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
15090
+ else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
15091
+ else if (event.key === 'Home') next = 0;
15092
+ else if (event.key === 'End') next = tabs.length - 1;
15093
+ else return;
15094
+ event.preventDefault();
15095
+ var tab = tabs[next].getAttribute('data-tab');
15096
+ window.switchRightTab(tab);
15097
+ tabs[next].focus();
14013
15098
  };
14014
15099
 
14015
15100
  window.closeSubWin = function() {
@@ -14023,6 +15108,7 @@ window.closeSubWin = function() {
14023
15108
  requestAnimationFrame(function() {
14024
15109
  els['sub-win-body'].scrollTop = previous.scrollTop || 0;
14025
15110
  state.restoringSubWindow = false;
15111
+ focusSubWindowInitial();
14026
15112
  });
14027
15113
  return;
14028
15114
  }
@@ -14030,6 +15116,12 @@ window.closeSubWin = function() {
14030
15116
  state.activeSubWindowView = null;
14031
15117
  var win = els['sub-win'];
14032
15118
  if (win) { win.classList.remove('memory-lab-window'); win.style.left = ''; win.style.top = ''; win.style.margin = ''; }
15119
+ var restoreFocus = subWindowOriginFocus;
15120
+ subWindowOriginFocus = null;
15121
+ updateApplicationInertState();
15122
+ requestAnimationFrame(function() {
15123
+ if (restoreFocus && restoreFocus.isConnected && typeof restoreFocus.focus === 'function') restoreFocus.focus({ preventScroll: true });
15124
+ });
14033
15125
  };
14034
15126
 
14035
15127
  function rerenderActiveSubWindowForLanguage() {
@@ -14078,19 +15170,19 @@ window.openSettings = function(tab) {
14078
15170
  var activeTab = tab || state.settingsActiveTab || 'general';
14079
15171
  var refreshingSettings = els['sub-win-overlay'].classList.contains('open') && state.activeSubWindowView && state.activeSubWindowView.name === 'settings';
14080
15172
  window._settingsTabCache = window._settingsTabCache || {};
14081
- var html = '<div class="settings-tabs">' +
14082
- '<button class="stab-btn" data-stab="general" onclick="window.settingsTab(\'general\')">' + esc(t('settings.general')) + '</button>' +
14083
- '<button class="stab-btn" data-stab="models" onclick="window.settingsTab(\'models\')">' + esc(t('settings.models')) + '</button>' +
14084
- '<button class="stab-btn" data-stab="tools" onclick="window.settingsTab(\'tools\')">' + esc(t('settings.tools')) + '</button>' +
14085
- '<button class="stab-btn" data-stab="archive" onclick="window.settingsTab(\'archive\')">' + esc(t('settings.archive')) + '</button>' +
14086
- '<button class="stab-btn" data-stab="updates" onclick="window.settingsTab(\'updates\')">' + esc(t('settings.updates')) + '</button>' +
15173
+ var html = '<div class="settings-tabs" role="tablist" aria-label="' + escAttr(t('settings.title')) + '">' +
15174
+ '<button class="stab-btn" id="settings-tab-general" role="tab" aria-controls="stab-general" data-stab="general" onclick="window.settingsTab(\'general\')" onkeydown="window.handleSettingsTabKey(event)">' + esc(t('settings.general')) + '</button>' +
15175
+ '<button class="stab-btn" id="settings-tab-models" role="tab" aria-controls="stab-models" data-stab="models" onclick="window.settingsTab(\'models\')" onkeydown="window.handleSettingsTabKey(event)">' + esc(t('settings.models')) + '</button>' +
15176
+ '<button class="stab-btn" id="settings-tab-tools" role="tab" aria-controls="stab-tools" data-stab="tools" onclick="window.settingsTab(\'tools\')" onkeydown="window.handleSettingsTabKey(event)">' + esc(t('settings.tools')) + '</button>' +
15177
+ '<button class="stab-btn" id="settings-tab-archive" role="tab" aria-controls="stab-archive" data-stab="archive" onclick="window.settingsTab(\'archive\')" onkeydown="window.handleSettingsTabKey(event)">' + esc(t('settings.archive')) + '</button>' +
15178
+ '<button class="stab-btn" id="settings-tab-updates" role="tab" aria-controls="stab-updates" data-stab="updates" onclick="window.settingsTab(\'updates\')" onkeydown="window.handleSettingsTabKey(event)">' + esc(t('settings.updates')) + '</button>' +
14087
15179
  '</div>' +
14088
- '<div class="stab-panel" id="stab-general" data-lazy="1"></div>' +
15180
+ '<div class="stab-panel" id="stab-general" role="tabpanel" aria-labelledby="settings-tab-general" data-lazy="1"></div>' +
14089
15181
  // Lazy: only render active tab on open; others render on first click
14090
- (activeTab === 'models' ? '<div class="stab-panel" id="stab-models">' + renderModelSettings() + '</div>' : '<div class="stab-panel" id="stab-models" data-lazy="1"></div>') +
14091
- (activeTab === 'tools' ? '<div class="stab-panel" id="stab-tools">' + renderToolSettings() + '</div>' : '<div class="stab-panel" id="stab-tools" data-lazy="1"></div>') +
14092
- (activeTab === 'archive' ? '<div class="stab-panel" id="stab-archive">' + renderArchiveSettings() + '</div>' : '<div class="stab-panel" id="stab-archive" data-lazy="1"></div>') +
14093
- (activeTab === 'updates' ? '<div class="stab-panel" id="stab-updates">' + renderUpdateSettings() + '</div>' : '<div class="stab-panel" id="stab-updates" data-lazy="1"></div>');
15182
+ (activeTab === 'models' ? '<div class="stab-panel" id="stab-models" role="tabpanel" aria-labelledby="settings-tab-models">' + renderModelSettings() + '</div>' : '<div class="stab-panel" id="stab-models" role="tabpanel" aria-labelledby="settings-tab-models" data-lazy="1"></div>') +
15183
+ (activeTab === 'tools' ? '<div class="stab-panel" id="stab-tools" role="tabpanel" aria-labelledby="settings-tab-tools">' + renderToolSettings() + '</div>' : '<div class="stab-panel" id="stab-tools" role="tabpanel" aria-labelledby="settings-tab-tools" data-lazy="1"></div>') +
15184
+ (activeTab === 'archive' ? '<div class="stab-panel" id="stab-archive" role="tabpanel" aria-labelledby="settings-tab-archive">' + renderArchiveSettings() + '</div>' : '<div class="stab-panel" id="stab-archive" role="tabpanel" aria-labelledby="settings-tab-archive" data-lazy="1"></div>') +
15185
+ (activeTab === 'updates' ? '<div class="stab-panel" id="stab-updates" role="tabpanel" aria-labelledby="settings-tab-updates">' + renderUpdateSettings() + '</div>' : '<div class="stab-panel" id="stab-updates" role="tabpanel" aria-labelledby="settings-tab-updates" data-lazy="1"></div>');
14094
15186
  state.activeSubWindowView = { name: 'settings', tab: activeTab };
14095
15187
  if (refreshingSettings) state.restoringSubWindow = true;
14096
15188
  window.openSubWin(t('settings.title'), html);
@@ -14457,7 +15549,12 @@ window.settingsTab = function(name) {
14457
15549
  state.settingsActiveTab = name || 'general';
14458
15550
  var btns = document.querySelectorAll('.stab-btn');
14459
15551
  for (var i = 0; i < btns.length; i++) {
14460
- btns[i].classList.toggle('active', btns[i].getAttribute('data-stab') === name);
15552
+ var selected = btns[i].getAttribute('data-stab') === name;
15553
+ btns[i].classList.toggle('active', selected);
15554
+ if (btns[i].closest('.settings-tabs:not(.plugin-tabs)')) {
15555
+ btns[i].setAttribute('aria-selected', selected ? 'true' : 'false');
15556
+ btns[i].tabIndex = selected ? 0 : -1;
15557
+ }
14461
15558
  }
14462
15559
  // Render lazy panels on first access for faster initial open
14463
15560
  var panelId = 'stab-' + name;
@@ -14483,6 +15580,7 @@ window.settingsTab = function(name) {
14483
15580
  var panels = document.querySelectorAll('.stab-panel');
14484
15581
  for (var j = 0; j < panels.length; j++) {
14485
15582
  panels[j].classList.toggle('active', panels[j].id === panelId);
15583
+ panels[j].hidden = panels[j].id !== panelId;
14486
15584
  }
14487
15585
  if (name === 'models' && panel && panel.getAttribute('data-lazy') !== '1') window.loadGlobalAgentPrompt();
14488
15586
  };
@@ -14501,8 +15599,8 @@ window.refreshGlobalConfigFile = function() {
14501
15599
  if (!api.reloadGlobalConfig) return;
14502
15600
  api.reloadGlobalConfig().then(function(result) {
14503
15601
  if (result && result.error) throw new Error(result.error);
14504
- showUiNotice(t('settings.configReloaded'), 'success', 'global-config-refresh');
14505
- setTimeout(function() { window.location.reload(); }, 180);
15602
+ try { sessionStorage.setItem('newmark-config-reloading', '1'); } catch (_) {}
15603
+ window.location.reload();
14506
15604
  }).catch(function(error) {
14507
15605
  showUiNotice(error && error.message ? error.message : String(error), 'error', 'global-config-refresh');
14508
15606
  });
@@ -15179,7 +16277,13 @@ window.validateAllModels = function() {
15179
16277
  return state.modelValidationResults;
15180
16278
  }).catch(function(err) {
15181
16279
  document.body.classList.remove('model-evaluating');
15182
- window.openSubWin(t('model.validationTitle'), '<div style="color:#ff7777;font-size:12px;">' + esc(t('model.validationFailed')) + ': ' + esc(err.message) + '</div>');
16280
+ var failedHtml = '<div style="color:#ff7777;font-size:12px;">' + esc(t('model.validationFailed')) + ': ' + esc(err.message) + '</div>';
16281
+ var body = document.getElementById('sub-win-body');
16282
+ if (body && document.getElementById('model-validation-progress') && els['sub-win-overlay'].classList.contains('open')) {
16283
+ body.innerHTML = failedHtml;
16284
+ } else {
16285
+ addMsg('assistant', redactSensitiveText('[Error] ' + t('model.validationFailed') + ': ' + err.message), 'error', '');
16286
+ }
15183
16287
  return [];
15184
16288
  }).finally(function() {
15185
16289
  window.stopModelValidationProgressPolling();
@@ -15195,16 +16299,45 @@ window.renderModelValidationProgress = function(progress) {
15195
16299
  var completedModels = Math.max(0, Number(progress.completedModels || 0));
15196
16300
  var totalModels = Math.max(0, Number(progress.totalModels || 0));
15197
16301
  var current = [String(progress.currentModel || ''), String(progress.currentCheck || '')].filter(Boolean).join(' · ');
15198
- return '<div class="provider-card marquee-border">' +
15199
- '<div style="display:flex;justify-content:space-between;gap:10px;"><span>' + esc(t('model.validating')) + '</span><strong>' + esc(String(percent)) + '%</strong></div>' +
15200
- '<div role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + esc(String(percent)) + '" style="height:7px;margin-top:10px;border-radius:999px;background:var(--surface-strong);overflow:hidden;"><div style="height:100%;width:' + esc(String(percent)) + '%;background:var(--accent);transition:width .18s ease;"></div></div>' +
15201
- '<div style="margin-top:8px;color:var(--text-dim);font-size:11px;display:flex;justify-content:space-between;gap:8px;"><span>' + esc(String(completedChecks)) + '/' + esc(String(totalChecks)) + ' ' + esc(t('model.validationChecks')) + '</span><span>' + esc(String(completedModels)) + '/' + esc(String(totalModels)) + ' ' + esc(t('model.validationModels')) + '</span></div>' +
15202
- (current ? '<div style="margin-top:6px;color:var(--text-dim);font-size:11px;overflow-wrap:anywhere;">' + esc(t('model.validationCurrent')) + ': ' + esc(current) + '</div>' : '') +
16302
+ return '<div class="provider-card marquee-border" id="model-validation-progress">' +
16303
+ '<div style="display:flex;justify-content:space-between;gap:10px;"><span>' + esc(t('model.validating')) + '</span><strong id="mv-percent">' + esc(String(percent)) + '%</strong></div>' +
16304
+ '<div id="mv-progressbar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="' + esc(String(percent)) + '" style="height:7px;margin-top:10px;border-radius:999px;background:var(--surface-strong);overflow:hidden;"><div id="mv-bar" style="height:100%;width:' + esc(String(percent)) + '%;background:var(--accent);transition:width .18s ease;"></div></div>' +
16305
+ '<div style="margin-top:8px;color:var(--text-dim);font-size:11px;display:flex;justify-content:space-between;gap:8px;"><span id="mv-checks">' + esc(String(completedChecks)) + '/' + esc(String(totalChecks)) + ' ' + esc(t('model.validationChecks')) + '</span><span id="mv-models">' + esc(String(completedModels)) + '/' + esc(String(totalModels)) + ' ' + esc(t('model.validationModels')) + '</span></div>' +
16306
+ '<div id="mv-current" style="margin-top:6px;color:var(--text-dim);font-size:11px;overflow-wrap:anywhere;' + (current ? '' : 'display:none;') + '">' + esc(t('model.validationCurrent')) + ': ' + esc(current) + '</div>' +
15203
16307
  '</div><div style="margin-top:6px;color:var(--text-dim);font-size:11px;">' + esc(t('model.validationBackgroundNote')) + '</div>';
15204
16308
  };
15205
16309
 
16310
+ window.updateModelValidationProgress = function(progress) {
16311
+ progress = progress || {};
16312
+ var body = document.getElementById('sub-win-body');
16313
+ if (!body) return;
16314
+ var percent = Math.max(0, Math.min(100, Number(progress.percent || 0)));
16315
+ var completedChecks = Math.max(0, Number(progress.completedChecks || 0));
16316
+ var totalChecks = Math.max(0, Number(progress.totalChecks || 0));
16317
+ var completedModels = Math.max(0, Number(progress.completedModels || 0));
16318
+ var totalModels = Math.max(0, Number(progress.totalModels || 0));
16319
+ var current = [String(progress.currentModel || ''), String(progress.currentCheck || '')].filter(Boolean).join(' · ');
16320
+ var percentEl = document.getElementById('mv-percent');
16321
+ if (!percentEl) return;
16322
+ percentEl.textContent = String(percent) + '%';
16323
+ var barEl = document.getElementById('mv-bar');
16324
+ if (barEl) barEl.style.width = String(percent) + '%';
16325
+ var progressbarEl = document.getElementById('mv-progressbar');
16326
+ if (progressbarEl) progressbarEl.setAttribute('aria-valuenow', String(percent));
16327
+ var checksEl = document.getElementById('mv-checks');
16328
+ if (checksEl) checksEl.textContent = String(completedChecks) + '/' + String(totalChecks) + ' ' + t('model.validationChecks');
16329
+ var modelsEl = document.getElementById('mv-models');
16330
+ if (modelsEl) modelsEl.textContent = String(completedModels) + '/' + String(totalModels) + ' ' + t('model.validationModels');
16331
+ var currentEl = document.getElementById('mv-current');
16332
+ if (currentEl) {
16333
+ currentEl.textContent = t('model.validationCurrent') + ': ' + current;
16334
+ currentEl.style.display = current ? '' : 'none';
16335
+ }
16336
+ };
16337
+
15206
16338
  window.showModelValidationProgress = function(progress) {
15207
16339
  document.body.classList.add('model-evaluating');
16340
+ if (document.getElementById('model-validation-progress')) return;
15208
16341
  window.openSubWin(t('model.validationTitle'), window.renderModelValidationProgress(progress));
15209
16342
  };
15210
16343
 
@@ -15220,7 +16353,7 @@ window.startModelValidationProgressPolling = function() {
15220
16353
  Promise.resolve(api.modelValidationStatus()).then(function(progress) {
15221
16354
  if (!state.modelValidationPromise) return;
15222
16355
  var body = document.getElementById('sub-win-body');
15223
- if (body && els['sub-win-overlay'].classList.contains('open')) body.innerHTML = window.renderModelValidationProgress(progress);
16356
+ if (body && els['sub-win-overlay'].classList.contains('open')) window.updateModelValidationProgress(progress);
15224
16357
  state.modelValidationProgressTimer = setTimeout(poll, 150);
15225
16358
  }).catch(function() {
15226
16359
  if (state.modelValidationPromise) state.modelValidationProgressTimer = setTimeout(poll, 300);
@@ -15247,7 +16380,21 @@ window.showModelEvaluationResults = function(results) {
15247
16380
  }
15248
16381
  }
15249
16382
  html += '</div>';
15250
- window.openSubWin(t('model.validationTitle'), html);
16383
+ var body = document.getElementById('sub-win-body');
16384
+ if (body && document.getElementById('model-validation-progress') && els['sub-win-overlay'].classList.contains('open')) {
16385
+ // The progress window is still visible: replace it in place so closing
16386
+ // the results restores whatever window was open before validation.
16387
+ body.innerHTML = html;
16388
+ return;
16389
+ }
16390
+ // The user closed the progress window or opened another window while
16391
+ // validation was running. Do not hijack the current window; summarize in chat.
16392
+ var okCount = 0;
16393
+ for (var j = 0; j < (results || []).length; j++) {
16394
+ var status = String((results[j] && results[j].status) || '');
16395
+ if (status === 'verified' || status === 'degraded' || status === 'available') okCount++;
16396
+ }
16397
+ addMsg('assistant', '[System] ' + t('model.validationTitle') + ': ' + okCount + '/' + (results || []).length + ' ' + t('model.validationModels'), okCount === (results || []).length ? 'success' : 'warning', '');
15251
16398
  };
15252
16399
  renderArchiveSettings = function() {
15253
16400
  if (api.listArchives && !state._allArchiveSettingsLoading) {
@@ -15290,6 +16437,23 @@ window.archiveCurrent = function() {
15290
16437
  return window.archiveConv(currentId);
15291
16438
  };
15292
16439
 
16440
+ window.handleSettingsTabKey = function(event) {
16441
+ if (!event) return;
16442
+ var tabs = Array.prototype.slice.call(document.querySelectorAll('.settings-tabs:not(.plugin-tabs) [role="tab"]'));
16443
+ var index = tabs.indexOf(event.currentTarget);
16444
+ if (index < 0) return;
16445
+ var next = index;
16446
+ if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
16447
+ else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
16448
+ else if (event.key === 'Home') next = 0;
16449
+ else if (event.key === 'End') next = tabs.length - 1;
16450
+ else return;
16451
+ event.preventDefault();
16452
+ var name = tabs[next].getAttribute('data-stab');
16453
+ window.settingsTab(name);
16454
+ tabs[next].focus();
16455
+ };
16456
+
15293
16457
  window.loadArchive = function(idx, scope) {
15294
16458
  var item = (scope === 'all' ? state.allArchives : state.workspaceArchives)[idx];
15295
16459
  if (!item || !api.readArchive) return;
@@ -15624,10 +16788,10 @@ function renderFlowItem(work, idx, expanded) {
15624
16788
  componentsHtml += '</div>';
15625
16789
  }
15626
16790
  return '<div class="flow-item">' +
15627
- '<div class="flow-item-header' + (expanded ? '' : ' collapsed') + '" onclick="this.classList.toggle(\'collapsed\')">' +
16791
+ '<button type="button" class="flow-item-header' + (expanded ? '' : ' collapsed') + '" aria-expanded="' + (expanded ? 'true' : 'false') + '" onclick="this.classList.toggle(\'collapsed\');this.setAttribute(\'aria-expanded\',this.classList.contains(\'collapsed\')?\'false\':\'true\')">' +
15628
16792
  '<span class="arrow">></span><span class="flow-item-name">' + esc(work.name || t('flow.untitled')) + '</span>' +
15629
16793
  '<span class="flow-item-type">' + (work.components ? work.components.length : 0) + ' ' + esc(t('flow.components')) + '</span>' +
15630
- '</div>' +
16794
+ '</button>' +
15631
16795
  '<div class="flow-item-children" style="margin-bottom:4px;">' +
15632
16796
  '<div style="display:flex;gap:4px;padding:4px 0;">' +
15633
16797
  '<button class="sec-btn" style="flex:1;font-size:10px;" onclick="window.addFlowComp(' + idx + ',\'dialog\')">+ ' + esc(t('flow.dialog')) + '</button>' +
@@ -15642,7 +16806,7 @@ function renderFlowItem(work, idx, expanded) {
15642
16806
  window.newFlowWork = function() {
15643
16807
  var html = '<div style="padding:10px;">' +
15644
16808
  '<label style="display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;">' + esc(t('flow.workflowName')) + '</label>' +
15645
- '<input id="new-flow-input" style="width:100%;padding:8px 10px;border:1px solid var(--glass-border-2);border-radius:var(--radius-sm);background:var(--glass-bg-1);color:var(--text);font-size:13px;outline:none;box-sizing:border-box;" placeholder="' + escAttr(t('flow.enterName')) + '" onkeydown="if(event.key===\'Enter\')window.doNewFlowWork()">' +
16809
+ '<input id="new-flow-input" style="width:100%;padding:8px 10px;border:1px solid var(--glass-border-2);border-radius:var(--radius-sm);background:var(--glass-bg-1);color:var(--text);font-size:13px;outline:none;box-sizing:border-box;" placeholder="' + escAttr(t('flow.enterName')) + '" onkeydown="if(event.key===\'Enter\'&&!event.isComposing&&event.key!==\'Process\'&&event.keyCode!==229)window.doNewFlowWork()">' +
15646
16810
  '<div style="margin-top:12px;display:flex;gap:8px;">' +
15647
16811
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewFlowWork()">' + esc(t('common.create')) + '</button>' +
15648
16812
  '<button class="sec-btn" onclick="window.showFlowEditor()">' + esc(t('common.cancel')) + '</button>' +
@@ -16351,14 +17515,25 @@ function renderTreeNodes(nodes, parent, depth) {
16351
17515
  var n = nodes[i];
16352
17516
  var div = document.createElement('div');
16353
17517
  div.className = 'ft-item';
17518
+ div.setAttribute('role', 'treeitem');
17519
+ var treeRoot = parent.closest ? (parent.closest('[role="tree"]') || parent) : parent;
17520
+ div.tabIndex = treeRoot.querySelector && treeRoot.querySelector('[role="treeitem"][tabindex="0"]') ? -1 : 0;
17521
+ div.addEventListener('keydown', window.handleFileTreeKey);
17522
+ div.addEventListener('focus', function() {
17523
+ var root = this.closest('[role="tree"]');
17524
+ if (!root) return;
17525
+ Array.prototype.forEach.call(root.querySelectorAll('[role="treeitem"]'), function(item) { item.tabIndex = item === document.activeElement ? 0 : -1; });
17526
+ });
16354
17527
  div.style.paddingLeft = (8 + depth * 14) + 'px';
16355
17528
  var icon = n.type === 'directory' ? iconSvg('folder', 'Folder', 'small') : iconSvg('file', 'File', 'small');
16356
17529
  if (n.type === 'directory') {
17530
+ div.setAttribute('aria-expanded', 'false');
16357
17531
  div.innerHTML = '<span class="ft-toggle collapsed">' + iconSvg('chevron-right', t('fileTree.toggleFolder'), 'tiny') + '</span>' +
16358
17532
  '<span class="ft-icon">' + icon + '</span><span class="ft-name">' + esc(n.name) + '</span>';
16359
17533
  parent.appendChild(div);
16360
17534
  var childContainer = document.createElement('div');
16361
17535
  childContainer.className = 'ft-children';
17536
+ childContainer.setAttribute('role', 'group');
16362
17537
  childContainer.style.display = 'none';
16363
17538
  parent.appendChild(childContainer);
16364
17539
  div.onclick = function(node, toggle, children, childDepth) {
@@ -16367,6 +17542,7 @@ function renderTreeNodes(nodes, parent, depth) {
16367
17542
  var opening = children.style.display === 'none';
16368
17543
  children.style.display = opening ? 'block' : 'none';
16369
17544
  toggle.classList.toggle('collapsed', !opening);
17545
+ this.setAttribute('aria-expanded', opening ? 'true' : 'false');
16370
17546
  if (!opening || children.getAttribute('data-loaded') === 'true') return;
16371
17547
  children.setAttribute('data-loading', 'true');
16372
17548
  try {
@@ -16474,8 +17650,7 @@ window.renderNativeEditor = function() {
16474
17650
  }
16475
17651
  window.renderEditorGutter(code);
16476
17652
  if (els['editor-language']) els['editor-language'].textContent = language;
16477
- var before = code.slice(0, ta.selectionStart || 0).split('\n');
16478
- if (els['editor-position']) els['editor-position'].textContent = before.length + ':' + (before[before.length - 1].length + 1);
17653
+ window.renderEditorCaretStatus();
16479
17654
  if (els['editor-dirty']) els['editor-dirty'].textContent = code !== state.editorOriginal ? 'modified' : '';
16480
17655
  if (els['editor-vim-mode']) els['editor-vim-mode'].textContent = state.editorVimEnabled ? state.editorVimMode.toUpperCase() : 'INSERT';
16481
17656
  if (state.editorPreview && els['editor-md-preview']) els['editor-md-preview'].innerHTML = renderMessageContent(code);
@@ -16517,6 +17692,7 @@ window.editorSetValue = function(value, preserveUndo) {
16517
17692
  if (!ta) return;
16518
17693
  if (!preserveUndo && ta.value !== value) { state.editorUndo.push(ta.value); if (state.editorUndo.length > 100) state.editorUndo.shift(); state.editorRedo = []; }
16519
17694
  ta.value = value;
17695
+ state.editorCompletionCache = [];
16520
17696
  window.renderNativeEditor();
16521
17697
  };
16522
17698
 
@@ -16545,23 +17721,100 @@ window.editorReplaceSelection = function(text) {
16545
17721
  window.renderNativeEditor();
16546
17722
  };
16547
17723
 
16548
- window.requestEditorCompletion = async function() {
17724
+ window.applyEditorCompletionDelta = function(payload) {
17725
+ payload = payload || {};
17726
+ if (String(payload.requestId || '') !== String(state.editorCompletionRequest || '')) return;
17727
+ var delta = String(payload.text || '');
17728
+ if (!delta || !state.editorCompletionAnchor) return;
17729
+ state.editorCompletionStreamText = (state.editorCompletionStreamText || '') + delta;
17730
+ state.editorCompletionText = state.editorCompletionStreamText.slice(0, 1200);
17731
+ window.renderEditorGhostText();
17732
+ if (els['editor-completion']) { els['editor-completion'].textContent = ''; els['editor-completion'].classList.remove('open'); }
17733
+ };
17734
+
17735
+ function editorCompletionValueKey(value) {
17736
+ var text = String(value || '');
17737
+ var hash = 2166136261;
17738
+ for (var i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);
17739
+ return text.length + ':' + (hash >>> 0);
17740
+ }
17741
+
17742
+ function editorCompletionAnchorKey(anchor) {
17743
+ return [anchor.path, anchor.start, anchor.end, editorCompletionValueKey(anchor.value)].join('\u0000');
17744
+ }
17745
+
17746
+ function editorCompletionCacheFind(anchor) {
17747
+ var cache = Array.isArray(state.editorCompletionCache) ? state.editorCompletionCache : [];
17748
+ state.editorCompletionCache = cache;
17749
+ var now = Date.now();
17750
+ var key = editorCompletionAnchorKey(anchor);
17751
+ for (var i = cache.length - 1; i >= 0; i--) {
17752
+ var item = cache[i];
17753
+ if (!item || item.expiresAt <= now) { cache.splice(i, 1); continue; }
17754
+ if (item.key === key) {
17755
+ cache.splice(i, 1);
17756
+ cache.unshift(item);
17757
+ return item;
17758
+ }
17759
+ }
17760
+ return null;
17761
+ }
17762
+
17763
+ function editorCompletionCachePut(anchor, text, ttl) {
17764
+ var cache = Array.isArray(state.editorCompletionCache) ? state.editorCompletionCache : [];
17765
+ var key = editorCompletionAnchorKey(anchor);
17766
+ state.editorCompletionCache = cache.filter(function(item) { return !item || item.key !== key; });
17767
+ state.editorCompletionCache.unshift({ key: key, text: text, expiresAt: Date.now() + ttl });
17768
+ if (state.editorCompletionCache.length > 16) state.editorCompletionCache.length = 16;
17769
+ }
17770
+
17771
+ window.requestEditorCompletion = async function(options) {
17772
+ options = options || {};
17773
+ var force = options.force === true;
16549
17774
  var ta = els['editor-textarea']; if (!ta || !state.editorPath || !api.editorComplete) return;
16550
17775
  var requestId = ++state.editorCompletionRequest;
16551
17776
  var pos = ta.selectionStart;
16552
17777
  var anchor = { path: state.editorPath, value: ta.value, start: pos, end: ta.selectionEnd };
16553
17778
  state.editorCompletionAnchor = anchor;
17779
+ var cached = force ? null : editorCompletionCacheFind(anchor);
17780
+ if (cached) {
17781
+ state.editorCompletionInFlight = false;
17782
+ state.editorCompletionStreamText = '';
17783
+ state.editorCompletionText = cached.text || '';
17784
+ state.editorCompletionAnchor = state.editorCompletionText ? anchor : null;
17785
+ window.renderEditorGhostText();
17786
+ if (els['editor-completion']) { els['editor-completion'].textContent = ''; els['editor-completion'].classList.remove('open'); }
17787
+ return;
17788
+ }
16554
17789
  if (els['editor-completion']) { els['editor-completion'].textContent = 'Predicting...'; els['editor-completion'].classList.add('open'); }
16555
- var beforeStart = Math.max(0, pos - 6000);
16556
- var afterEnd = Math.min(ta.value.length, pos + 1600);
16557
- var result = await api.editorComplete({ path: state.editorPath, before: ta.value.slice(beforeStart, pos), after: ta.value.slice(pos, afterEnd) });
17790
+ state.editorCompletionInFlight = true;
17791
+ state.editorCompletionStreamText = '';
17792
+ var beforeStart = Math.max(0, pos - 3200);
17793
+ var afterEnd = Math.min(ta.value.length, pos + 800);
17794
+ var result;
17795
+ try {
17796
+ result = await api.editorComplete({ requestId: String(requestId), path: state.editorPath, before: ta.value.slice(beforeStart, pos), after: ta.value.slice(pos, afterEnd) });
17797
+ } catch (error) {
17798
+ result = { ok: false, text: '', error: error && error.message ? error.message : String(error) };
17799
+ } finally {
17800
+ if (requestId === state.editorCompletionRequest) state.editorCompletionInFlight = false;
17801
+ }
16558
17802
  if (requestId !== state.editorCompletionRequest) return;
16559
- if (!window.editorAnchorMatches(anchor)) { window.dismissEditorCompletion(); window.scheduleEditorCompletion(); return; }
16560
- state.editorCompletionText = result && result.ok ? String(result.text || '') : '';
17803
+ // Input/caret handlers already schedule the replacement request. Do not
17804
+ // recursively schedule here: that doubled provider traffic on fast edits.
17805
+ if (!window.editorAnchorMatches(anchor)) return;
17806
+ var suggestion = result && result.ok ? String(result.text || '').slice(0, 1200) : '';
17807
+ if (!suggestion.trim()) suggestion = '';
17808
+ state.editorCompletionText = suggestion;
17809
+ state.editorCompletionStreamText = '';
17810
+ state.editorCompletionAnchor = suggestion ? anchor : null;
17811
+ editorCompletionCachePut(anchor, suggestion, suggestion ? 15000 : 2000);
16561
17812
  window.renderEditorGhostText();
16562
17813
  if (els['editor-completion']) {
16563
- els['editor-completion'].textContent = state.editorCompletionText ? '' : (result.error || 'No completion');
16564
- els['editor-completion'].classList.toggle('open', !state.editorCompletionText);
17814
+ // Empty/aborted/timeout responses are normal for an inline predictor. The
17815
+ // editor should stay quiet instead of flashing an error on every keystroke.
17816
+ els['editor-completion'].textContent = '';
17817
+ els['editor-completion'].classList.remove('open');
16565
17818
  }
16566
17819
  };
16567
17820
 
@@ -16590,7 +17843,7 @@ window.scheduleEditorCompletion = function() {
16590
17843
  state.editorCompletionTimer = setTimeout(function() {
16591
17844
  state.editorCompletionTimer = null;
16592
17845
  window.requestEditorCompletion();
16593
- }, 180);
17846
+ }, 300);
16594
17847
  };
16595
17848
 
16596
17849
  window.editorCaretSignature = function() {
@@ -16609,7 +17862,8 @@ window.handleEditorCaretChange = function() {
16609
17862
  var signature = window.editorCaretSignature();
16610
17863
  if (signature === state.editorCaretSignature) return;
16611
17864
  state.editorCaretSignature = signature;
16612
- window.renderNativeEditor();
17865
+ window.renderEditorCaretStatus();
17866
+ window.renderEditorGhostText();
16613
17867
  window.scheduleEditorCompletion();
16614
17868
  };
16615
17869
 
@@ -16617,7 +17871,24 @@ window.acceptEditorCompletion = function() {
16617
17871
  if (state.editorCompletionText && window.editorCompletionAnchorIsCurrent()) window.editorReplaceSelection(state.editorCompletionText);
16618
17872
  window.dismissEditorCompletion();
16619
17873
  };
16620
- window.dismissEditorCompletion = function() { state.editorCompletionText = ''; state.editorCompletionAnchor = null; state.editorCompletionRequest++; if (els['editor-completion']) els['editor-completion'].classList.remove('open'); window.renderEditorGhostText(); };
17874
+ window.dismissEditorCompletion = function() {
17875
+ state.editorCompletionText = '';
17876
+ state.editorCompletionStreamText = '';
17877
+ state.editorCompletionAnchor = null;
17878
+ state.editorCompletionRequest++;
17879
+ if (state.editorCompletionInFlight && api.editorCompleteCancel) {
17880
+ try { Promise.resolve(api.editorCompleteCancel()).catch(function() {}); } catch (_) {}
17881
+ }
17882
+ if (els['editor-completion']) { els['editor-completion'].textContent = ''; els['editor-completion'].classList.remove('open'); }
17883
+ window.renderEditorGhostText();
17884
+ };
17885
+
17886
+ window.renderEditorCaretStatus = function() {
17887
+ var ta = els['editor-textarea'];
17888
+ if (!ta) return;
17889
+ var before = String(ta.value || '').slice(0, ta.selectionStart || 0).split('\n');
17890
+ if (els['editor-position']) els['editor-position'].textContent = before.length + ':' + (before[before.length - 1].length + 1);
17891
+ };
16621
17892
 
16622
17893
  window.requestEditorAssist = async function() {
16623
17894
  var ta = els['editor-textarea']; if (!ta || !state.editorPath || !api.editorAssist) return;
@@ -17223,6 +18494,42 @@ function rememberBrowserUrl(url, force, target) {
17223
18494
  }
17224
18495
  }
17225
18496
 
18497
+ window.handleFileTreeKey = function(event) {
18498
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
18499
+ var item = event.currentTarget;
18500
+ var root = item.closest('[role="tree"]');
18501
+ if (!root) return false;
18502
+ if (event.key === 'Enter' || event.key === ' ') return window.activateButtonLike(event);
18503
+ if (event.key === 'ArrowRight') {
18504
+ if (item.hasAttribute('aria-expanded') && item.getAttribute('aria-expanded') !== 'true') {
18505
+ event.preventDefault(); item.click(); return true;
18506
+ }
18507
+ var group = item.nextElementSibling;
18508
+ var child = group && group.matches('[role="group"]') ? group.querySelector('[role="treeitem"]') : null;
18509
+ if (child) { event.preventDefault(); child.focus({ preventScroll:true }); return true; }
18510
+ return false;
18511
+ }
18512
+ if (event.key === 'ArrowLeft') {
18513
+ if (item.getAttribute('aria-expanded') === 'true') { event.preventDefault(); item.click(); return true; }
18514
+ var ownerGroup = item.parentElement && item.parentElement.matches('[role="group"]') ? item.parentElement : null;
18515
+ var parentItem = ownerGroup && ownerGroup.previousElementSibling && ownerGroup.previousElementSibling.matches('[role="treeitem"]') ? ownerGroup.previousElementSibling : null;
18516
+ if (parentItem) { event.preventDefault(); parentItem.focus({ preventScroll:true }); return true; }
18517
+ return false;
18518
+ }
18519
+ var visible = Array.prototype.slice.call(root.querySelectorAll('[role="treeitem"]')).filter(function(node) { return node.getClientRects().length > 0; });
18520
+ var index = visible.indexOf(item);
18521
+ var next = index;
18522
+ if (event.key === 'ArrowDown') next = Math.min(visible.length - 1, index + 1);
18523
+ else if (event.key === 'ArrowUp') next = Math.max(0, index - 1);
18524
+ else if (event.key === 'Home') next = 0;
18525
+ else if (event.key === 'End') next = visible.length - 1;
18526
+ else return false;
18527
+ if (next < 0 || !visible[next]) return false;
18528
+ event.preventDefault();
18529
+ visible[next].focus({ preventScroll:true });
18530
+ return true;
18531
+ };
18532
+
17226
18533
  function waitForBrowserCreationFloor() {
17227
18534
  var remaining = NEWMARK_BROWSER_MIN_CREATE_DELAY_MS - (Date.now() - newmarkRendererStartedAt);
17228
18535
  if (remaining <= 0) return Promise.resolve();
@@ -17555,7 +18862,8 @@ function applyBackendConversations(items, activeId, workspaceId) {
17555
18862
  updatedAt: item.updatedAt || '',
17556
18863
  pinned: !!item.pinned,
17557
18864
  pinnedAt: item.pinnedAt || '',
17558
- order: Number(item.order || 0)
18865
+ order: Number(item.order || 0),
18866
+ branchCommunication: !!item.branchCommunication
17559
18867
  });
17560
18868
  }
17561
18869
  // A stale list response can arrive between creating a conversation locally
@@ -17682,16 +18990,26 @@ function renderConversations() {
17682
18990
  }
17683
18991
  var div = document.createElement('div');
17684
18992
  div.className = 'conv-item' + (conv.active ? ' active' : '');
18993
+ div.setAttribute('role', 'listitem');
18994
+ div.setAttribute('tabindex', conv.active ? '0' : '-1');
18995
+ div.setAttribute('aria-current', conv.active ? 'true' : 'false');
17685
18996
  div.setAttribute('draggable', 'true');
17686
18997
  div.setAttribute('data-conversation-id', String(conv.id || ''));
17687
18998
  if (runtimeState && ['running', 'stopping', 'force_restarting'].indexOf(String(runtimeState.status || '')) >= 0) div.classList.add('marquee-border');
17688
18999
  var runtimeBadge = runtimeState && runtimeState.status && runtimeState.status !== 'idle'
17689
19000
  ? '<span class="conv-runtime-badge ' + escAttr(String(runtimeState.status)) + '">' + esc(String(runtimeState.status)) + '</span>' : '';
17690
- div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + runtimeBadge +
19001
+ var branchCommIcon = conv.branchCommunication
19002
+ ? '<span class="conv-branch-comm-icon" title="' + escAttr(t('conversation.branchCommunicationBadge')) + '" aria-label="' + escAttr(t('conversation.branchCommunicationBadge')) + '">' + iconSvg('git-branch', t('conversation.branchCommunicationBadge')) + '</span>' : '';
19003
+ var archivePendingKey = currentWorkspaceKey() + '::' + String(conv.id);
19004
+ var archiveBtn = state.conversationArchivePending[archivePendingKey]
19005
+ ? '<button class="conv-archive-btn archiving" title="' + escAttr(t('archive.archiving')) + '" disabled><span class="conv-archive-spinner"></span></button>'
19006
+ : '<button class="conv-archive-btn" onclick="event.stopPropagation();window.archiveConv(this.closest(&quot;.conv-item&quot;).getAttribute(&quot;data-conversation-id&quot;))" title="' + escAttr(t('conversation.archive')) + '">' + iconOnly('archive', t('conversation.archive')) + '</button>';
19007
+ div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + branchCommIcon + runtimeBadge +
17691
19008
  '<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
17692
- '<button class="conv-archive-btn" onclick="event.stopPropagation();window.archiveConv(this.closest(&quot;.conv-item&quot;).getAttribute(&quot;data-conversation-id&quot;))" title="' + escAttr(t('conversation.archive')) + '">' + iconOnly('archive', t('conversation.archive')) + '</button>' +
19009
+ archiveBtn +
17693
19010
  '<button class="conv-pin-btn' + (conv.pinned ? ' active' : '') + '" onclick="event.stopPropagation();window.toggleConversationPinned(' + i + ')" title="' + escAttr(conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '">' + iconOnly('pin', conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '</button>';
17694
19011
  div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
19012
+ div.addEventListener('keydown', window.handleConversationKey);
17695
19013
  div.addEventListener('dragstart', function(event) {
17696
19014
  this.classList.add('dragging');
17697
19015
  if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', this.getAttribute('data-conversation-id') || ''); }
@@ -17721,6 +19039,30 @@ function renderConversations() {
17721
19039
  updateWorkspaceGate();
17722
19040
  }
17723
19041
 
19042
+ window.handleConversationKey = function(event) {
19043
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
19044
+ if (event.key === 'Enter' || event.key === ' ') return window.activateButtonLike(event);
19045
+ var items = Array.prototype.slice.call(document.querySelectorAll('#conversation-list .conv-item'));
19046
+ var index = items.indexOf(event.currentTarget);
19047
+ if (index < 0 || !items.length) return false;
19048
+ var next = index;
19049
+ if (event.key === 'ArrowDown') next = (index + 1) % items.length;
19050
+ else if (event.key === 'ArrowUp') next = (index - 1 + items.length) % items.length;
19051
+ else if (event.key === 'Home') next = 0;
19052
+ else if (event.key === 'End') next = items.length - 1;
19053
+ else return false;
19054
+ event.preventDefault();
19055
+ var nextId = items[next].getAttribute('data-conversation-id');
19056
+ items[next].click();
19057
+ requestAnimationFrame(function() {
19058
+ var replacement = Array.prototype.slice.call(document.querySelectorAll('#conversation-list .conv-item')).find(function(item) {
19059
+ return item.getAttribute('data-conversation-id') === nextId;
19060
+ });
19061
+ if (replacement) replacement.focus({ preventScroll:true });
19062
+ });
19063
+ return true;
19064
+ };
19065
+
17724
19066
  window.newConversationOld = function() {
17725
19067
  var id = 'conv-' + Date.now();
17726
19068
  var summary = t('left.newChat') + ' ' + (state.conversations.length + 1);
@@ -17754,7 +19096,7 @@ window.archiveConvOld = function(idx) {
17754
19096
  }
17755
19097
  };
17756
19098
 
17757
- window.newConversation = function(workspaceReference) {
19099
+ window.newConversation = function(workspaceReference, branchCommunication) {
17758
19100
  if (workspaceReference && workspaceReference !== state.currentWorkspaceId) {
17759
19101
  window.selectWorkspace(workspaceReference);
17760
19102
  }
@@ -17784,6 +19126,13 @@ window.newConversation = function(workspaceReference) {
17784
19126
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
17785
19127
  state.activeBackendConversationId = String((s && s.conversationId) || id);
17786
19128
  if (s) applyConversationSnapshot(s, id);
19129
+ if (branchCommunication && api.setConversationBranchCommunication) {
19130
+ api.setConversationBranchCommunication(target, true).then(function() {
19131
+ var convsNow = currentWorkspaceConversations();
19132
+ var created = convsNow.find(function(item){ return String(item && item.id) === id; });
19133
+ if (created) { created.branchCommunication = true; renderConversations(); }
19134
+ }).catch(function(){});
19135
+ }
17787
19136
  }).then(function() {
17788
19137
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
17789
19138
  state.foregroundConversationHoldId = '';
@@ -17810,6 +19159,7 @@ window.showNewConversationPage = function() {
17810
19159
  var html = '<div style="display:flex;flex-direction:column;gap:12px;padding:4px 2px;">' +
17811
19160
  '<div style="font-size:12px;color:var(--text-dim);line-height:1.5;">' + esc(t('workspace.newConversationDesc')) + '</div>' +
17812
19161
  '<div class="auto-input-group"><label>' + esc(t('status.workspace')) + '</label><select id="new-conv-ws">' + options + '</select></div>' +
19162
+ '<div class="auto-input-group checkbox-row"><label><input type="checkbox" id="new-conv-branch-comm"><span>' + esc(t('conversation.branchCommunication')) + '</span></label></div>' +
17813
19163
  '<div style="display:flex;gap:8px;">' +
17814
19164
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewConversationFromPage()">' + esc(t('workspace.createConversation')) + '</button>' +
17815
19165
  '<button class="sec-btn" style="flex:1;" onclick="window.showNewWorkspaceDialog()">' + esc(t('workspace.createAction')) + '</button>' +
@@ -17826,11 +19176,13 @@ window.doNewConversationFromPage = function() {
17826
19176
  window.showNewWorkspaceDialog();
17827
19177
  return;
17828
19178
  }
19179
+ var branchComm = document.getElementById('new-conv-branch-comm');
19180
+ var branchCommunication = !!(branchComm && branchComm.checked);
17829
19181
  window.switchToWorkspace(identity).then(function(ws) {
17830
19182
  state.currentWorkspace = (ws && ws.name) || state.currentWorkspace;
17831
19183
  state.currentWorkspaceId = workspaceIdentity(ws) || state.currentWorkspaceId || identity;
17832
19184
  window.closeSubWin();
17833
- window.newConversation(state.currentWorkspaceId);
19185
+ window.newConversation(state.currentWorkspaceId, branchCommunication);
17834
19186
  }).catch(function(err) {
17835
19187
  showUiNotice(t('workspace.selectAction') + ': ' + err.message, 'error', 'workspace-new-conversation-' + identity);
17836
19188
  });
@@ -17951,26 +19303,45 @@ window.archiveConv = function(conversationId) {
17951
19303
  orderIds: rollbackOrder,
17952
19304
  chatHtml: priorActiveId === targetId && els['chat-area'] ? els['chat-area'].innerHTML : ''
17953
19305
  };
17954
- convs.splice(idx, 1);
17955
- if (!convs.length) convs.push({ id: 'default', summary: t('workspace.defaultConversation'), archived: false, active: true });
17956
- var nextActiveId = priorActiveId && priorActiveId !== targetId ? priorActiveId : String(convs[Math.min(idx, convs.length - 1)].id || 'default');
17957
- var nextActiveIndex = Math.max(0, convs.findIndex(function(item) { return String(item && item.id || '') === nextActiveId; }));
17958
- for (var i = 0; i < convs.length; i++) convs[i].active = i === nextActiveIndex;
17959
- state.workspaceActiveConversation[workspaceKey] = nextActiveIndex;
17960
- state.activeConversation = nextActiveIndex;
17961
- state.conversations = convs;
17962
- if (priorActiveId === targetId) {
17963
- state.activeBackendConversationId = nextActiveId;
17964
- if (els['chat-area']) els['chat-area'].innerHTML = '';
17965
- }
17966
- setConversationRuntimeState(targetRuntime, 'idle', '');
17967
- setWorking(!!runningConversationRecord(activeConversationId()));
19306
+ // 归档中:不立即剔除行,按钮转圈等待;后端完成归档后再剔除前端。
17968
19307
  renderConversations();
17969
- if (priorActiveId === targetId) scheduleConversationArchiveActiveSync(workspaceKey);
17970
19308
  var archivePromise = api.archive ? api.archive(targetRuntime) : Promise.reject(new Error('Archive API unavailable'));
17971
19309
  archivePromise.then(function(receipt) {
17972
19310
  if (!receipt || receipt.ok !== true) throw new Error((receipt && receipt.error) || 'Archive failed');
17973
19311
  delete state.conversationArchivePending[pendingKey];
19312
+ // 后端完成归档:现在才剔除前端行 + 切换 active + 清理 Flow takeover。
19313
+ var currentConvs = currentWorkspaceConversations();
19314
+ var currentIdx = currentConvs.findIndex(function(item) { return String(item && item.id || 'default') === targetId; });
19315
+ if (currentIdx >= 0) currentConvs.splice(currentIdx, 1);
19316
+ if (!currentConvs.length) currentConvs.push({ id: 'default', summary: t('workspace.defaultConversation'), archived: false, active: true });
19317
+ var nextActiveId = priorActiveId && priorActiveId !== targetId ? priorActiveId : String(currentConvs[Math.min(Math.max(currentIdx, 0), currentConvs.length - 1)].id || 'default');
19318
+ var nextActiveIndex = Math.max(0, currentConvs.findIndex(function(item) { return String(item && item.id || '') === nextActiveId; }));
19319
+ for (var i = 0; i < currentConvs.length; i++) currentConvs[i].active = i === nextActiveIndex;
19320
+ state.workspaceActiveConversation[workspaceKey] = nextActiveIndex;
19321
+ state.activeConversation = nextActiveIndex;
19322
+ state.conversations = currentConvs;
19323
+ if (priorActiveId === targetId) {
19324
+ state.activeBackendConversationId = nextActiveId;
19325
+ if (els['chat-area']) els['chat-area'].innerHTML = '';
19326
+ }
19327
+ if (state.flowTakeovers) {
19328
+ var archivedFlowKey = runtimeKeyFor(targetRuntime.workspaceId, targetRuntime.conversationId);
19329
+ var archivedFlowRecord = state.flowTakeovers[archivedFlowKey];
19330
+ if (archivedFlowRecord) {
19331
+ archivedFlowRecord.running = false;
19332
+ archivedFlowRecord.paused = false;
19333
+ archivedFlowRecord.runtimeLease = null;
19334
+ archivedFlowRecord.queueLease = null;
19335
+ delete state.flowTakeovers[archivedFlowKey];
19336
+ }
19337
+ if (priorActiveId === targetId && window.renderFlowTakeover) {
19338
+ window.renderFlowTakeover(false, '', { target: targetRuntime });
19339
+ }
19340
+ }
19341
+ setConversationRuntimeState(targetRuntime, 'idle', '');
19342
+ setWorking(!!runningConversationRecord(activeConversationId()));
19343
+ renderConversations();
19344
+ if (priorActiveId === targetId) scheduleConversationArchiveActiveSync(workspaceKey);
17974
19345
  var optimisticArchive = {
17975
19346
  id: receipt.fileName,
17976
19347
  name: receipt.fileName,
@@ -17998,10 +19369,8 @@ window.archiveConv = function(conversationId) {
17998
19369
  scheduleConversationArchiveRefresh(workspaceKey);
17999
19370
  }).catch(function(err) {
18000
19371
  delete state.conversationArchivePending[pendingKey];
18001
- // Keep the optimistic removal authoritative for this renderer session.
18002
- // A failed IPC receipt is surfaced, but never resurrects the row under
18003
- // the user's pointer; the next explicit workspace refresh is the only
18004
- // path allowed to reconcile a failed destructive operation.
19372
+ // 归档失败:恢复按钮,保留行(不再乐观剔除)。
19373
+ renderConversations();
18005
19374
  showUiNotice('[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', 'archive-failed-' + targetId);
18006
19375
  });
18007
19376
  }
@@ -18809,37 +20178,117 @@ window.reindexMemoryLab = function() {
18809
20178
  });
18810
20179
  };
18811
20180
 
18812
- // === Plugin List Placeholder ===
18813
- window.showPluginList = function() {
18814
- window.openSubWin(t('plugins.managerTitle'), '<div style="font-size:12px;color:var(--text-dim);text-align:center;padding:20px;">' + esc(t('plugins.noPlugins')) + '<br><br><button class="sec-btn primary" onclick="window.closeSubWin()">' + esc(t('common.close')) + '</button></div>');
20181
+ // === Plugin / Skills Market ===
20182
+ var PLUGIN_TABS = ['mcp', 'dsh', 'installed', 'market', 'github'];
20183
+
20184
+ function pluginText(key, values) {
20185
+ var output = t(key);
20186
+ Object.keys(values || {}).forEach(function(name) {
20187
+ output = output.replace(new RegExp('\\{' + name + '\\}', 'g'), function() { return String(values[name]); });
20188
+ });
20189
+ return output;
20190
+ }
20191
+
20192
+ function pluginRequestActive(tab, generation) {
20193
+ return generation === state.pluginPanelGeneration
20194
+ && state.pluginActiveTab === tab
20195
+ && !!(state.activeSubWindowView && state.activeSubWindowView.name === 'plugins')
20196
+ && !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'))
20197
+ && !!document.getElementById('plugin-panel');
20198
+ }
20199
+
20200
+ function pluginPanelState(message, kind, retryCall) {
20201
+ return '<div class="plugin-panel-state' + (kind === 'error' ? ' error' : '') + '" role="' + (kind === 'error' ? 'alert' : 'status') + '"' + (kind === 'loading' ? ' aria-live="polite" aria-busy="true"' : '') + '>' +
20202
+ '<span>' + esc(message) + '</span>' +
20203
+ (retryCall ? '<button type="button" class="sec-btn" onclick="' + retryCall + '">' + esc(t('plugins.retry')) + '</button>' : '') +
20204
+ '</div>';
20205
+ }
20206
+
20207
+ function pluginTabMarkup(tab, label) {
20208
+ var selected = state.pluginActiveTab === tab;
20209
+ return '<button type="button" class="stab-btn' + (selected ? ' active' : '') + '" id="plugin-tab-' + tab + '" role="tab" aria-selected="' + (selected ? 'true' : 'false') + '" aria-controls="plugin-panel" tabindex="' + (selected ? '0' : '-1') + '" data-plugin-tab="' + tab + '" onclick="window.selectPluginTab(\'' + tab + '\')" onkeydown="window.handlePluginTabKey(event)">' + esc(label) + '</button>';
20210
+ }
20211
+
20212
+ window.syncPluginTabs = function(focusTab) {
20213
+ var labels = { mcp: t('plugins.tabMcp'), dsh: t('plugins.tabDsh'), installed: t('plugins.tabSkills'), market: t('plugins.tabMarket'), github: t('plugins.tabGithub') };
20214
+ PLUGIN_TABS.forEach(function(tab) {
20215
+ var button = document.getElementById('plugin-tab-' + tab);
20216
+ if (!button) return;
20217
+ var selected = tab === state.pluginActiveTab;
20218
+ button.classList.toggle('active', selected);
20219
+ button.setAttribute('aria-selected', selected ? 'true' : 'false');
20220
+ button.tabIndex = selected ? 0 : -1;
20221
+ button.textContent = labels[tab];
20222
+ });
20223
+ var tablist = document.querySelector('.plugin-tabs[role="tablist"]');
20224
+ if (tablist) tablist.setAttribute('aria-label', t('plugins.title'));
20225
+ if (els['sub-win-title']) els['sub-win-title'].textContent = t('plugins.title');
20226
+ var panel = document.getElementById('plugin-panel');
20227
+ if (panel) panel.setAttribute('aria-labelledby', 'plugin-tab-' + state.pluginActiveTab);
20228
+ if (focusTab) {
20229
+ var activeButton = document.getElementById('plugin-tab-' + state.pluginActiveTab);
20230
+ if (activeButton) activeButton.focus({ preventScroll: true });
20231
+ }
18815
20232
  };
18816
20233
 
18817
- // === Plugin / Skills Market ===
18818
- window.showPluginList = function(tab) {
18819
- var activeTab = tab || state.pluginActiveTab || 'mcp';
18820
- var refreshingPlugins = els['sub-win-overlay'].classList.contains('open') && state.activeSubWindowView && state.activeSubWindowView.name === 'plugins';
18821
- state.activeSubWindowView = { name: 'plugins', tab: activeTab };
18822
- state.pluginActiveTab = activeTab;
18823
- var html = '<div class="settings-tabs">' +
18824
- '<button class="stab-btn' + (state.pluginActiveTab === 'mcp' ? ' active' : '') + '" onclick="window.showPluginList(\'mcp\')">' + esc(t('plugins.mcp')) + '</button>' +
18825
- '<button class="stab-btn' + (state.pluginActiveTab === 'installed' ? ' active' : '') + '" onclick="window.showPluginList(\'installed\')">' + esc(t('plugins.management')) + '</button>' +
18826
- '<button class="stab-btn' + (state.pluginActiveTab === 'market' ? ' active' : '') + '" onclick="window.showPluginList(\'market\')">' + esc(t('plugins.market')) + '</button>' +
18827
- '<button class="stab-btn' + (state.pluginActiveTab === 'github' ? ' active' : '') + '" onclick="window.showPluginList(\'github\')">' + esc(t('plugins.github')) + '</button>' +
18828
- '</div><div id="plugin-panel" style="margin-top:10px;">' + esc(t('common.loading')) + '</div>';
18829
- if (refreshingPlugins) state.restoringSubWindow = true;
18830
- window.openSubWin(t('plugins.title'), html);
18831
- state.restoringSubWindow = false;
18832
- if (state.pluginActiveTab === 'mcp') window.renderMcpManager();
18833
- else if (state.pluginActiveTab === 'market') window.renderSkillsMarket();
18834
- else if (state.pluginActiveTab === 'github') window.renderGithubCliPanel();
20234
+ window.handlePluginTabKey = function(event) {
20235
+ if (!event || PLUGIN_TABS.indexOf(state.pluginActiveTab) < 0) return;
20236
+ var index = PLUGIN_TABS.indexOf(state.pluginActiveTab);
20237
+ if (event.key === 'ArrowRight') index = (index + 1) % PLUGIN_TABS.length;
20238
+ else if (event.key === 'ArrowLeft') index = (index - 1 + PLUGIN_TABS.length) % PLUGIN_TABS.length;
20239
+ else if (event.key === 'Home') index = 0;
20240
+ else if (event.key === 'End') index = PLUGIN_TABS.length - 1;
20241
+ else return;
20242
+ event.preventDefault();
20243
+ window.showPluginList(PLUGIN_TABS[index], { focusTab: true });
20244
+ };
20245
+
20246
+ window.selectPluginTab = function(tab) {
20247
+ window.showPluginList(tab, { focusTab: false });
20248
+ };
20249
+
20250
+ window.showPluginList = function(tab, options) {
20251
+ var requested = PLUGIN_TABS.indexOf(tab) >= 0 ? tab : (PLUGIN_TABS.indexOf(state.pluginActiveTab) >= 0 ? state.pluginActiveTab : 'mcp');
20252
+ var overlayOpen = !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'));
20253
+ var pluginOpen = overlayOpen && state.activeSubWindowView && state.activeSubWindowView.name === 'plugins' && !!document.getElementById('plugin-panel');
20254
+ state.pluginActiveTab = requested;
20255
+ state.pluginPanelGeneration++;
20256
+ if (!pluginOpen) {
20257
+ var html = '<div class="settings-tabs plugin-tabs" role="tablist" aria-label="' + escAttr(t('plugins.title')) + '">' +
20258
+ pluginTabMarkup('mcp', t('plugins.tabMcp')) +
20259
+ pluginTabMarkup('dsh', t('plugins.tabDsh')) +
20260
+ pluginTabMarkup('installed', t('plugins.tabSkills')) +
20261
+ pluginTabMarkup('market', t('plugins.tabMarket')) +
20262
+ pluginTabMarkup('github', t('plugins.tabGithub')) +
20263
+ '</div><div id="plugin-panel" class="plugin-panel" role="tabpanel" tabindex="0" aria-labelledby="plugin-tab-' + requested + '"></div>';
20264
+ if (overlayOpen && state.activeSubWindowView) {
20265
+ window.openSubWin(t('plugins.title'), html);
20266
+ state.activeSubWindowView = { name: 'plugins', tab: requested };
20267
+ } else {
20268
+ state.activeSubWindowView = { name: 'plugins', tab: requested };
20269
+ window.openSubWin(t('plugins.title'), html);
20270
+ }
20271
+ if (els['sub-win']) els['sub-win'].classList.remove('memory-lab-window');
20272
+ } else {
20273
+ state.activeSubWindowView = { name: 'plugins', tab: requested };
20274
+ }
20275
+ window.syncPluginTabs(!pluginOpen || !!(options && options.focusTab));
20276
+ var panel = document.getElementById('plugin-panel');
20277
+ if (panel) panel.replaceChildren();
20278
+ if (requested === 'mcp') window.renderMcpManager();
20279
+ else if (requested === 'dsh') window.renderDshPlugin();
20280
+ else if (requested === 'market') window.renderSkillsMarket();
20281
+ else if (requested === 'github') window.renderGithubCliPanel();
18835
20282
  else window.renderInstalledSkills();
18836
20283
  };
18837
20284
 
18838
20285
  window.renderInstalledSkills = function() {
18839
20286
  var panel = document.getElementById('plugin-panel');
18840
- if (!panel) return;
20287
+ if (!panel || state.pluginActiveTab !== 'installed') return;
20288
+ var generation = ++state.pluginPanelGeneration;
18841
20289
  var render = function(items) {
18842
20290
  state.skills = items || [];
20291
+ if (!pluginRequestActive('installed', generation)) return;
18843
20292
  if (!items || !items.length) {
18844
20293
  panel.innerHTML = '<div style="font-size:12px;color:var(--text-dim);padding:18px;text-align:center;">' + esc(t('plugins.noInstalled')) + '</div>';
18845
20294
  return;
@@ -18880,14 +20329,15 @@ window.refreshSkillsRuntime = function(next) {
18880
20329
 
18881
20330
  window.renderSkillsMarket = function() {
18882
20331
  var panel = document.getElementById('plugin-panel');
18883
- if (!panel) return;
20332
+ if (!panel || state.pluginActiveTab !== 'market') return;
20333
+ var generation = ++state.pluginPanelGeneration;
18884
20334
  panel.innerHTML = '<div class="provider-card marquee-border">' + esc(t('plugins.discovering')) + '</div>';
18885
20335
  state._skillMarketAll = [];
18886
20336
  state._skillMarketSources = [];
18887
20337
  var done = 0;
18888
20338
  var finish = function() {
18889
20339
  done++;
18890
- if (done >= 2) window.renderSkillsMarketList();
20340
+ if (done >= 2 && pluginRequestActive('market', generation)) window.renderSkillsMarketList();
18891
20341
  };
18892
20342
  if (api.marketSkillSources) api.marketSkillSources().then(function(sources) {
18893
20343
  state._skillMarketSources = sources || [];
@@ -18901,88 +20351,466 @@ window.renderSkillsMarket = function() {
18901
20351
  else finish();
18902
20352
  };
18903
20353
 
20354
+ var DSH_OFFICIAL_URLS = {
20355
+ repo: 'https://github.com/deepseek-ai/deepseek-harness',
20356
+ docs: 'https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md',
20357
+ npm: 'https://www.npmjs.com/package/@deepseek-ai/dsh'
20358
+ };
20359
+
20360
+ function dshCollection(value) {
20361
+ if (Array.isArray(value)) return value;
20362
+ if (value && typeof value === 'object') return Object.keys(value).map(function(key) {
20363
+ var item = value[key];
20364
+ if (item && typeof item === 'object' && !Array.isArray(item)) return Object.assign({ name: key }, item);
20365
+ return { name: key, value: item };
20366
+ });
20367
+ return [];
20368
+ }
20369
+
20370
+ function dshDisplayValue(value) {
20371
+ if (value === undefined || value === null || value === '') return t('plugins.dshNotAvailable');
20372
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return String(value);
20373
+ try { return JSON.stringify(value); } catch (_) { return String(value); }
20374
+ }
20375
+
20376
+ function dshUniqueStrings(values) {
20377
+ var seen = Object.create(null);
20378
+ return dshCollection(values).map(function(value) {
20379
+ return typeof value === 'string' ? value : dshDisplayValue(value);
20380
+ }).filter(function(value) {
20381
+ var key = String(value || '');
20382
+ if (!key || seen[key]) return false;
20383
+ seen[key] = true;
20384
+ return true;
20385
+ });
20386
+ }
20387
+
20388
+ function dshListMarkup(items, emptyText, warning) {
20389
+ var values = dshCollection(items);
20390
+ if (!values.length) return '<div class="plugin-empty">' + esc(emptyText || t('plugins.dshNone')) + '</div>';
20391
+ return '<div class="dsh-list">' + values.map(function(item) {
20392
+ var label = item && typeof item === 'object'
20393
+ ? (item.name || item.id || item.path || item.file || item.key || dshDisplayValue(item))
20394
+ : item;
20395
+ var detail = item && typeof item === 'object' ? (item.version || item.source || item.value || item.reason || '') : '';
20396
+ return '<div class="dsh-list-item' + (warning ? ' warning' : '') + '"><strong>' + esc(redactSensitiveText(dshDisplayValue(label))) + '</strong>' + (detail && String(detail) !== String(label) ? '<div class="provider-meta">' + esc(redactSensitiveText(dshDisplayValue(detail))) + '</div>' : '') + '</div>';
20397
+ }).join('') + '</div>';
20398
+ }
20399
+
20400
+ function dshLayerMarkup(profile) {
20401
+ var layers = dshCollection(profile && profile.layers);
20402
+ if (!layers.length) return '<div class="plugin-empty">' + esc(t('plugins.dshNone')) + '</div>';
20403
+ return '<div class="dsh-list">' + layers.map(function(layer, index) {
20404
+ var kind = layer && layer.kind ? String(layer.kind) : 'layer';
20405
+ var order = Number.isFinite(Number(layer && layer.order)) ? Number(layer.order) + 1 : index + 1;
20406
+ var file = layer && (layer.path || layer.file) || '';
20407
+ return '<div class="dsh-list-item"><strong>' + esc(order + '. ' + kind + (layer && layer.name ? ' · ' + layer.name : '')) + '</strong>' +
20408
+ (file ? '<div class="provider-meta">' + esc(redactSensitiveText(file)) + '</div>' : '') + '</div>';
20409
+ }).join('') + '</div>';
20410
+ }
20411
+
20412
+ window.openDshOfficial = function(kind) {
20413
+ var url = DSH_OFFICIAL_URLS[kind];
20414
+ if (!url || !api.openWebUrl) {
20415
+ showUiNotice(t('plugins.dshUnavailable'), 'error', 'dsh-web-url');
20416
+ return;
20417
+ }
20418
+ var generation = state.pluginPanelGeneration;
20419
+ Promise.resolve(api.openWebUrl(url)).then(function(result) {
20420
+ if (result && (result.ok === false || result.success === false || result.error)) throw new Error(result.error || t('plugins.dshUnavailable'));
20421
+ }).catch(function(error) {
20422
+ if (pluginRequestActive('dsh', generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'dsh-web-url');
20423
+ });
20424
+ };
20425
+
20426
+ window.renderDshPlugin = function() {
20427
+ var panel = document.getElementById('plugin-panel');
20428
+ if (!panel || state.pluginActiveTab !== 'dsh') return;
20429
+ var generation = ++state.pluginPanelGeneration;
20430
+ panel.innerHTML = pluginPanelState(t('plugins.dshScanning'), 'loading');
20431
+ if (!api.discoverDshCompatibility) {
20432
+ panel.innerHTML = pluginPanelState(t('plugins.dshUnavailable'), 'error');
20433
+ return;
20434
+ }
20435
+ Promise.resolve(api.discoverDshCompatibility()).then(function(snapshot) {
20436
+ if (!pluginRequestActive('dsh', generation)) return;
20437
+ state.dshCompatibility = snapshot && typeof snapshot === 'object' ? snapshot : {};
20438
+ window.renderDshPluginContent();
20439
+ }).catch(function(error) {
20440
+ if (!pluginRequestActive('dsh', generation)) return;
20441
+ var message = t('plugins.dshLoadError') + (error && error.message ? ' ' + error.message : '');
20442
+ panel.innerHTML = pluginPanelState(message, 'error', 'window.renderDshPlugin()');
20443
+ });
20444
+ };
20445
+
20446
+ window.renderDshPluginContent = function() {
20447
+ var panel = document.getElementById('plugin-panel');
20448
+ if (!panel || state.pluginActiveTab !== 'dsh') return;
20449
+ var dsh = state.dshCompatibility || {};
20450
+ var cli = dsh.cli && typeof dsh.cli === 'object' ? dsh.cli : {};
20451
+ var pkg = dsh.package && typeof dsh.package === 'object' ? dsh.package : {};
20452
+ var update = dsh.update && typeof dsh.update === 'object' ? dsh.update : {};
20453
+ var cliPath = cli.path || cli.executable || cli.commandPath || dsh.cliPath || '';
20454
+ var cliVersion = cli.version || dsh.cliVersion || '';
20455
+ var packageVersion = pkg.version || (cli.package && cli.package.version) || cli.packageVersion || dsh.packageVersion || '';
20456
+ var updateChannel = update.channel || update.tag || dsh.updateChannel || '';
20457
+ var updateLocked = update.locked === true || update.versionLocked === true || update.isLocked === true;
20458
+ if (String(updateChannel).toLowerCase() === 'latest' && !updateLocked) updateChannel = t('plugins.dshLatestChannel');
20459
+ var candidates = dshCollection(dsh.mcpCandidates);
20460
+ var configFiles = dshCollection(dsh.configFiles);
20461
+ dshCollection(dsh.profiles).forEach(function(profile) {
20462
+ if (profile && typeof profile === 'object') configFiles = configFiles.concat(dshCollection(profile.configFiles));
20463
+ });
20464
+ configFiles = dshUniqueStrings(configFiles);
20465
+ var html = '<section class="dsh-hero" id="dsh-hero" aria-labelledby="dsh-title">' +
20466
+ '<div class="dsh-hero-head"><div class="dsh-hero-copy"><div class="dsh-title" id="dsh-title">' + esc(t('plugins.dshTitle')) + '</div>' +
20467
+ '<div class="dsh-description">' + esc(t('plugins.dshHelp')) + '</div></div><span class="plugin-status-badge preview">' + esc(t('plugins.dshPreview')) + '</span></div>' +
20468
+ '<div class="dsh-actions"><button type="button" class="sec-btn primary" id="dsh-rescan" onclick="window.renderDshPlugin()">' + esc(t('plugins.dshRescan')) + '</button>' +
20469
+ '<button type="button" class="sec-btn" id="dsh-official-repo" onclick="window.openDshOfficial(\'repo\')">' + esc(t('plugins.dshOfficialRepo')) + '</button>' +
20470
+ '<button type="button" class="sec-btn" id="dsh-official-docs" onclick="window.openDshOfficial(\'docs\')">' + esc(t('plugins.dshOfficialDocs')) + '</button>' +
20471
+ '<button type="button" class="sec-btn" id="dsh-official-npm" onclick="window.openDshOfficial(\'npm\')">' + esc(t('plugins.dshOfficialNpm')) + '</button></div></section>';
20472
+ html += '<div class="dsh-grid">' +
20473
+ '<section class="dsh-card" id="dsh-cli-card"><div class="dsh-card-title">' + esc(t('plugins.dshCli')) + '</div><dl class="dsh-kv">' +
20474
+ '<dt>' + esc(t('plugins.dshCliPath')) + '</dt><dd id="dsh-cli-path">' + esc(redactSensitiveText(dshDisplayValue(cliPath))) + '</dd>' +
20475
+ '<dt>' + esc(t('plugins.dshCliVersion')) + '</dt><dd id="dsh-cli-version">' + esc(dshDisplayValue(cliVersion)) + '</dd>' +
20476
+ '<dt>' + esc(t('plugins.dshPackageVersion')) + '</dt><dd id="dsh-package-version">' + esc(dshDisplayValue(packageVersion)) + '</dd></dl></section>' +
20477
+ '<section class="dsh-card" id="dsh-runtime-card"><div class="dsh-card-title">' + esc(t('plugins.dshReadonly')) + '</div><dl class="dsh-kv">' +
20478
+ '<dt>' + esc(t('plugins.dshHome')) + '</dt><dd id="dsh-home">' + esc(redactSensitiveText(dshDisplayValue(dsh.dshHome || dsh.home))) + '</dd>' +
20479
+ '<dt>' + esc(t('plugins.dshHomeSource')) + '</dt><dd id="dsh-home-source">' + esc(redactSensitiveText(dshDisplayValue(dsh.dshHomeSource || dsh.homeSource || (dsh.home && dsh.home.source)))) + '</dd>' +
20480
+ '<dt>' + esc(t('plugins.dshUpdateChannel')) + '</dt><dd id="dsh-update-channel">' + esc(dshDisplayValue(updateChannel)) + '</dd></dl>' +
20481
+ '<div class="dsh-description">' + esc(t('plugins.dshReadonlyDetail')) + '</div></section>' +
20482
+ '<section class="dsh-card" id="dsh-profiles-card"><div class="dsh-card-title">' + esc(t('plugins.dshProfiles')) + '</div>' + dshListMarkup(dsh.profiles) + '</section>' +
20483
+ '<section class="dsh-card" id="dsh-bundles-card"><div class="dsh-card-title">' + esc(t('plugins.dshBundles')) + '</div>' + dshListMarkup(dsh.bundles) + '</section>' +
20484
+ '<section class="dsh-card" id="dsh-warnings-card"><div class="dsh-card-title">' + esc(t('plugins.dshWarnings')) + '</div>' + dshListMarkup(dsh.warnings, t('plugins.dshNone'), true) + '</section>' +
20485
+ '<section class="dsh-card" id="dsh-unknown-keys-card"><div class="dsh-card-title">' + esc(t('plugins.dshUnknownKeys')) + '</div>' + dshListMarkup(dsh.unknownKeys) + '</section></div>';
20486
+ html += '<section class="plugin-section" id="dsh-config-files"><div class="plugin-section-head"><div class="plugin-section-title">' + esc(t('plugins.dshConfigFiles')) + '</div><span class="plugin-count-badge">' + configFiles.length + '</span></div>' + dshListMarkup(configFiles) + '</section>';
20487
+ html += '<section class="plugin-section" id="dsh-layers"><div class="plugin-section-head"><div class="plugin-section-title">' + esc(t('plugins.dshLayers')) + '</div></div><div class="mcp-field-help">' + esc(t('plugins.dshLayerOrder')) + '</div>';
20488
+ dshCollection(dsh.profiles).forEach(function(profile) {
20489
+ html += '<div class="dsh-layer-profile"><div class="plugin-section-title">' + esc(profile && (profile.name || profile.source) || t('plugins.dshNotAvailable')) + '</div>' + dshLayerMarkup(profile) + '</div>';
20490
+ });
20491
+ var homeFiles = dshCollection(dsh.homeConfigFiles);
20492
+ if (homeFiles.length) html += '<div class="dsh-layer-profile"><div class="plugin-section-title">' + esc(t('plugins.dshHomePatches')) + '</div>' + dshListMarkup(homeFiles) + '</div>';
20493
+ html += '<div class="mcp-field-help">' + esc(t('plugins.dshUpdateability')) + '</div></section>';
20494
+ html += '<section class="plugin-section" id="dsh-mcp-candidates"><div class="plugin-section-head"><div class="plugin-section-title">' + esc(t('plugins.dshMcpCandidates')) + '</div><span class="plugin-count-badge">' + candidates.length + '</span></div>' +
20495
+ '<div class="mcp-field-help">' + esc(t('plugins.mcpCandidateHelp')) + '</div>';
20496
+ if (!candidates.length) html += '<div class="plugin-empty">' + esc(t('plugins.dshNone')) + '</div>';
20497
+ candidates.forEach(function(candidate, index) {
20498
+ var name = candidate && (candidate.name || candidate.id) || ('MCP ' + (index + 1));
20499
+ var source = candidate && (candidate.source || candidate.path) || '';
20500
+ var reason = candidate && candidate.reason || '';
20501
+ var importable = !!(candidate && candidate.importable && candidate.template);
20502
+ html += '<div class="mcp-server-row" id="dsh-candidate-' + index + '" data-dsh-candidate-index="' + index + '"><div class="mcp-server-copy"><div class="mcp-server-title">' + esc(name) +
20503
+ '<span class="plugin-status-badge">' + esc(importable ? t('plugins.mcpReviewImport') : t('plugins.mcpReadonly')) + '</span></div>' +
20504
+ '<div class="mcp-server-meta">' + esc(redactSensitiveText(dshDisplayValue(source))) + '</div>' +
20505
+ (reason ? '<div class="mcp-server-meta">' + esc(redactSensitiveText(dshDisplayValue(reason))) + '</div>' : '') + '</div>' +
20506
+ (importable ? '<div class="mcp-row-actions"><button type="button" class="sec-btn" id="dsh-candidate-review-' + index + '" onclick="window.reviewDshMcpCandidate(' + index + ')">' + esc(t('plugins.mcpReviewImport')) + '</button></div>' : '') + '</div>';
20507
+ });
20508
+ html += '</section>';
20509
+ panel.innerHTML = html;
20510
+ };
20511
+
20512
+ function newMcpDraft(input, source) {
20513
+ input = input && typeof input === 'object' ? input : {};
20514
+ var transport = input.transport === 'http' ? 'http' : 'stdio';
20515
+ return {
20516
+ id: input.id || '',
20517
+ source: source || 'add',
20518
+ name: String(input.name || ''),
20519
+ transport: transport,
20520
+ command: String(input.command || ''),
20521
+ url: String(input.url || ''),
20522
+ argsText: JSON.stringify(Array.isArray(input.args) ? input.args : [], null, 2),
20523
+ cwd: String(input.cwd || ''),
20524
+ envText: input.env && typeof input.env === 'object' ? JSON.stringify(input.env, null, 2) : '',
20525
+ headersText: input.headers && typeof input.headers === 'object' ? JSON.stringify(input.headers, null, 2) : '',
20526
+ enabled: source === 'candidate' ? false : input.enabled !== false,
20527
+ envKeys: Array.isArray(input.envKeys) ? input.envKeys.slice() : [],
20528
+ headerKeys: Array.isArray(input.headerKeys) ? input.headerKeys.slice() : []
20529
+ };
20530
+ }
20531
+
20532
+ window.reviewDshMcpCandidate = function(index) {
20533
+ var candidates = dshCollection(state.dshCompatibility && state.dshCompatibility.mcpCandidates);
20534
+ var candidate = candidates[index];
20535
+ if (!candidate || !candidate.importable || !candidate.template) return;
20536
+ state.mcpEditingId = '';
20537
+ state.mcpDraft = newMcpDraft(Object.assign({}, candidate.template, {
20538
+ enabled: false,
20539
+ envKeys: Array.isArray(candidate.envKeys) ? candidate.envKeys : [],
20540
+ headerKeys: Array.isArray(candidate.headerKeys) ? candidate.headerKeys : []
20541
+ }), 'candidate');
20542
+ state.mcpFormFocusRequested = true;
20543
+ window.showPluginList('mcp');
20544
+ };
20545
+
18904
20546
  window.renderMcpManager = function() {
18905
20547
  var panel = document.getElementById('plugin-panel');
18906
- if (!panel) return;
18907
- panel.innerHTML = '<div class="provider-card marquee-border">' + esc(t('common.loading')) + '</div>';
20548
+ if (!panel || state.pluginActiveTab !== 'mcp') return;
20549
+ var generation = ++state.pluginPanelGeneration;
20550
+ panel.innerHTML = pluginPanelState(t('plugins.mcpRefreshing'), 'loading');
18908
20551
  if (!api.listMcpServers) {
18909
- panel.innerHTML = '<div class="settings-empty">' + esc(t('plugins.ghUnavailable')) + '</div>';
20552
+ panel.innerHTML = pluginPanelState(t('plugins.mcpUnavailable'), 'error');
18910
20553
  return;
18911
20554
  }
18912
- api.listMcpServers().then(function(result) {
20555
+ Promise.resolve(api.listMcpServers()).then(function(result) {
20556
+ if (!pluginRequestActive('mcp', generation)) return;
18913
20557
  state.mcpServers = Array.isArray(result && result.servers) ? result.servers : [];
18914
20558
  state.mcpDiscovered = Array.isArray(result && result.discovered) ? result.discovered : [];
18915
20559
  window.renderMcpManagerContent();
18916
20560
  }).catch(function(error) {
18917
- panel.innerHTML = '<div class="settings-empty">' + esc(String(error && error.message ? error.message : error)) + '</div>';
20561
+ if (!pluginRequestActive('mcp', generation)) return;
20562
+ var message = t('plugins.mcpLoadError') + (error && error.message ? ' ' + error.message : '');
20563
+ panel.innerHTML = pluginPanelState(message, 'error', 'window.renderMcpManager()');
18918
20564
  });
18919
20565
  };
18920
20566
 
20567
+ function mcpSearchMatch(server, query) {
20568
+ server = server || {};
20569
+ if (!query) return true;
20570
+ var text = [server.name, server.transport, server.command, server.url, server.plugin, server.ecosystem, server.root]
20571
+ .concat(server.args || []).join(' ').toLowerCase();
20572
+ return text.indexOf(query) >= 0;
20573
+ }
20574
+
20575
+ function mcpSecretHelp(keys, keyName) {
20576
+ return keys && keys.length
20577
+ ? pluginText(keyName, { keys: keys.join(', ') })
20578
+ : t('plugins.mcpNoSavedKeys');
20579
+ }
20580
+
20581
+ function renderMcpForm() {
20582
+ var draft = state.mcpDraft;
20583
+ if (!draft) return '';
20584
+ var pending = !!state.mcpMutationPending;
20585
+ var titleKey = draft.source === 'candidate' ? 'plugins.mcpReviewTitle' : (draft.id ? 'plugins.mcpEditTitle' : 'plugins.mcpAddTitle');
20586
+ var stdio = draft.transport !== 'http';
20587
+ return '<section class="mcp-form-card" id="mcp-form" aria-labelledby="mcp-form-title" aria-busy="' + (pending ? 'true' : 'false') + '">' +
20588
+ '<div class="mcp-form-head"><div class="mcp-form-copy"><div class="mcp-form-title" id="mcp-form-title">' + esc(t(titleKey)) + '</div><div class="mcp-form-subtitle">' + esc(t('plugins.mcpFormHelp')) + '</div></div></div>' +
20589
+ '<div class="mcp-field-grid"><div class="mcp-field"><label for="mcp-name">' + esc(t('plugins.mcpName')) + '</label><input class="mcp-input" id="mcp-name" value="' + escAttr(draft.name) + '" autocomplete="off"></div>' +
20590
+ '<div class="mcp-field"><label for="mcp-transport">' + esc(t('plugins.mcpTransport')) + '</label><select class="mcp-input" id="mcp-transport" onchange="window.updateMcpTransportForm()"><option value="stdio"' + (stdio ? ' selected' : '') + '>stdio</option><option value="http"' + (!stdio ? ' selected' : '') + '>http</option></select></div></div>' +
20591
+ '<div id="mcp-stdio-fields"' + (stdio ? '' : ' hidden') + '><div class="mcp-field"><label for="mcp-command">' + esc(t('plugins.mcpEndpointStdio')) + '</label><input class="mcp-input" id="mcp-command" value="' + escAttr(draft.command) + '" autocomplete="off"></div>' +
20592
+ '<div class="mcp-field"><label for="mcp-args">' + esc(t('plugins.mcpArgs')) + '</label><textarea class="mcp-input" id="mcp-args" spellcheck="false">' + esc(draft.argsText) + '</textarea></div>' +
20593
+ '<div class="mcp-field"><label for="mcp-cwd">' + esc(t('plugins.mcpCwd')) + '</label><input class="mcp-input" id="mcp-cwd" value="' + escAttr(draft.cwd) + '" autocomplete="off"></div>' +
20594
+ '<div class="mcp-field"><label for="mcp-env">' + esc(t('plugins.mcpEnv')) + '</label><textarea class="mcp-input" id="mcp-env" spellcheck="false" placeholder="{}">' + esc(draft.envText) + '</textarea><div class="mcp-field-help">' + esc(mcpSecretHelp(draft.envKeys, 'plugins.mcpSavedEnvKeys')) + ' ' + esc(t('plugins.mcpSecretsHelp')) + '</div></div></div>' +
20595
+ '<div id="mcp-http-fields"' + (!stdio ? '' : ' hidden') + '><div class="mcp-field"><label for="mcp-url">' + esc(t('plugins.mcpEndpointHttp')) + '</label><input class="mcp-input" id="mcp-url" value="' + escAttr(draft.url) + '" inputmode="url" autocomplete="url"></div>' +
20596
+ '<div class="mcp-field"><label for="mcp-headers">' + esc(t('plugins.mcpHeaders')) + '</label><textarea class="mcp-input" id="mcp-headers" spellcheck="false" placeholder="{}">' + esc(draft.headersText) + '</textarea><div class="mcp-field-help">' + esc(mcpSecretHelp(draft.headerKeys, 'plugins.mcpSavedHeaderKeys')) + ' ' + esc(t('plugins.mcpSecretsHelp')) + '</div></div></div>' +
20597
+ '<label class="mcp-enabled-check" for="mcp-enabled"><input id="mcp-enabled" type="checkbox"' + (draft.enabled ? ' checked' : '') + (pending ? ' disabled' : '') + '> ' + esc(t('plugins.mcpEnabled')) + '</label>' +
20598
+ '<div class="mcp-form-actions"><button type="button" class="sec-btn" id="mcp-cancel" onclick="window.cancelMcpForm()"' + (pending ? ' disabled' : '') + '>' + esc(t('common.cancel')) + '</button>' +
20599
+ '<button type="button" class="sec-btn primary" id="mcp-save" onclick="window.saveMcpServer()"' + (pending ? ' disabled' : '') + '>' + esc(pending ? t('plugins.mcpSaving') : t('plugins.mcpSave')) + '</button></div></section>';
20600
+ }
20601
+
18921
20602
  window.renderMcpManagerContent = function() {
18922
20603
  var panel = document.getElementById('plugin-panel');
18923
- if (!panel) return;
18924
- var servers = state.mcpServers || [];
18925
- var discovered = state.mcpDiscovered || [];
18926
- var editing = servers.find(function(server) { return server.id === state.mcpEditingId; }) || {};
18927
- var transport = editing.transport === 'http' ? 'http' : 'stdio';
18928
- var html = '<div style="font-size:11px;color:var(--text-dim);margin-bottom:10px;">' + esc(t('plugins.mcpHelp')) + '</div>' +
18929
- '<div class="provider-card" style="margin-bottom:12px;">' +
18930
- '<div style="display:grid;grid-template-columns:1fr 120px;gap:8px;margin-bottom:8px;">' +
18931
- '<input id="mcp-name" value="' + escAttr(editing.name || '') + '" placeholder="' + escAttr(t('plugins.mcpName')) + '" class="github-repo-select">' +
18932
- '<select id="mcp-transport" class="github-repo-select" onchange="window.updateMcpTransportForm()"><option value="stdio"' + (transport === 'stdio' ? ' selected' : '') + '>stdio</option><option value="http"' + (transport === 'http' ? ' selected' : '') + '>http</option></select></div>' +
18933
- '<input id="mcp-command" value="' + escAttr(transport === 'http' ? (editing.url || '') : (editing.command || '')) + '" placeholder="' + escAttr(t('plugins.mcpCommand')) + '" class="github-repo-select" style="margin-bottom:8px;">' +
18934
- '<textarea id="mcp-args" placeholder="' + escAttr(transport === 'http' ? t('plugins.mcpHeaders') : t('plugins.mcpArgs')) + '" style="width:100%;min-height:72px;border:1px solid var(--glass-border-2);border-radius:var(--radius-md);background:var(--control-bg);color:var(--text);padding:9px;font:11px var(--font-mono);resize:vertical;">' + esc(transport === 'http' ? '' : JSON.stringify(editing.args || [], null, 2)) + '</textarea>' +
18935
- '<textarea id="mcp-env" placeholder="' + escAttr(t('plugins.mcpEnv')) + '" style="display:' + (transport === 'stdio' ? 'block' : 'none') + ';width:100%;min-height:72px;margin-top:8px;border:1px solid var(--glass-border-2);border-radius:var(--radius-md);background:var(--control-bg);color:var(--text);padding:9px;font:11px var(--font-mono);resize:vertical;"></textarea>' +
18936
- '<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px;"><button class="sec-btn" onclick="window.resetMcpForm()">' + esc(t('common.clear')) + '</button><button class="sec-btn primary" onclick="window.saveMcpServer()">' + esc(editing.id ? t('plugins.mcpSave') : t('plugins.mcpAdd')) + '</button></div></div>' +
18937
- '<h4 style="margin:0 0 8px;">' + esc(t('plugins.mcpStored')) + '</h4>';
18938
- if (!servers.length) html += '<div class="settings-empty">' + esc(t('plugins.mcpNoServers')) + '</div>';
20604
+ if (!panel || state.pluginActiveTab !== 'mcp') return;
20605
+ var allServers = state.mcpServers || [];
20606
+ var allDiscovered = state.mcpDiscovered || [];
20607
+ var query = String(state.mcpSearchQuery || '').trim().toLowerCase();
20608
+ var servers = allServers.filter(function(server) { return mcpSearchMatch(server, query); });
20609
+ var discovered = allDiscovered.filter(function(server) { return mcpSearchMatch(server, query); });
20610
+ var pending = !!state.mcpMutationPending;
20611
+ var html = '<div class="plugin-toolbar"><div class="plugin-toolbar-copy"><div class="plugin-toolbar-title">' + esc(t('plugins.mcp')) + '</div><div class="plugin-toolbar-meta">' + esc(t('plugins.mcpHelp')) + '</div></div>' +
20612
+ '<input class="plugin-search" id="mcp-search" type="search" value="' + escAttr(state.mcpSearchQuery || '') + '" placeholder="' + escAttr(t('plugins.mcpSearch')) + '" aria-label="' + escAttr(t('plugins.mcpSearch')) + '" oninput="window.updateMcpSearch(this.value)">' +
20613
+ '<button type="button" class="sec-btn" id="mcp-refresh" onclick="window.renderMcpManager()"' + (pending ? ' disabled' : '') + '>' + esc(t('plugins.mcpRefresh')) + '</button>' +
20614
+ '<button type="button" class="sec-btn primary" id="mcp-add" onclick="window.openMcpAddForm()"' + (pending ? ' disabled' : '') + '>' + esc(t('plugins.mcpAdd')) + '</button></div>';
20615
+ html += renderMcpForm();
20616
+ html += '<section class="plugin-section" id="mcp-stored-section"><div class="plugin-section-head"><div class="plugin-section-title">' + esc(t('plugins.mcpStored')) + '</div><span class="plugin-count-badge">' + esc(pluginText('plugins.mcpConfiguredCount', { count: allServers.length })) + '</span></div>';
20617
+ if (!allServers.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpStoredEmpty')) + '</div>';
20618
+ else if (!servers.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpNoMatch')) + '</div>';
18939
20619
  servers.forEach(function(server) {
18940
- var endpoint = server.transport === 'http' ? server.url : [server.command].concat(server.args || []).join(' ');
18941
- html += '<div class="provider-card" style="display:flex;align-items:center;gap:8px;"><div style="flex:1;min-width:0;"><div style="font-weight:650;">' + esc(server.name) + ' <span class="settings-badge">' + esc(server.transport) + '</span></div><div class="provider-meta">' + esc(endpoint || '') + '</div></div>' +
18942
- '<button class="sec-btn" onclick="window.editMcpServer(\'' + escAttr(server.id) + '\')">' + esc(t('common.edit')) + '</button>' +
18943
- '<button class="sec-btn" onclick="window.toggleMcpServer(\'' + escAttr(server.id) + '\',' + (!server.enabled) + ')">' + esc(server.enabled ? t('common.disable') : t('common.enable')) + '</button>' +
18944
- '<button class="sec-btn" onclick="window.removeMcpServerFromUi(\'' + escAttr(server.id) + '\')">' + esc(t('common.remove')) + '</button></div>';
20620
+ var index = allServers.indexOf(server);
20621
+ var endpoint = server.transport === 'http' ? server.url : [server.command].concat(server.args || []).filter(Boolean).join(' ');
20622
+ html += '<div class="mcp-server-row" data-mcp-index="' + index + '"><div class="mcp-server-copy"><div class="mcp-server-title">' + esc(server.name || '') +
20623
+ '<span class="plugin-status-badge">' + esc(server.transport || 'stdio') + '</span><span class="plugin-status-badge' + (server.enabled ? ' enabled' : '') + '">' + esc(server.enabled ? t('plugins.mcpEnabledStatus') : t('plugins.mcpDisabledStatus')) + '</span></div>' +
20624
+ '<div class="mcp-server-meta">' + esc(endpoint || '') + '</div></div><div class="mcp-row-actions">' +
20625
+ '<button type="button" class="sec-btn" onclick="window.editMcpServerAt(' + index + ')"' + (pending ? ' disabled' : '') + '>' + esc(t('common.edit')) + '</button>' +
20626
+ '<button type="button" class="sec-btn" onclick="window.toggleMcpServerAt(' + index + ',' + (!server.enabled) + ')"' + (pending ? ' disabled' : '') + '>' + esc(server.enabled ? t('common.disable') : t('common.enable')) + '</button>' +
20627
+ '<button type="button" class="sec-btn" onclick="window.removeMcpServerAt(' + index + ')"' + (pending ? ' disabled' : '') + '>' + esc(t('common.remove')) + '</button></div></div>';
18945
20628
  });
18946
- html += '<h4 style="margin:14px 0 8px;">' + esc(t('plugins.mcpDiscovered')) + '</h4>';
18947
- if (!discovered.length) html += '<div class="settings-empty">' + esc(t('plugins.noItems')) + '</div>';
20629
+ html += '</section><section class="plugin-section" id="mcp-discovered-section"><div class="plugin-section-head"><div class="plugin-section-title">' + esc(t('plugins.mcpDiscovered')) + '</div><span class="plugin-count-badge">' + allDiscovered.length + '</span></div>';
20630
+ if (!allDiscovered.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpDiscoveredEmpty')) + '</div>';
20631
+ else if (!discovered.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpNoMatch')) + '</div>';
18948
20632
  discovered.forEach(function(server) {
18949
- html += '<div class="provider-card"><div style="font-weight:650;">' + esc(server.name) + ' <span class="settings-badge">' + esc(server.ecosystem || '') + '</span></div><div class="provider-meta">' + esc((server.plugin || '') + ' · ' + (server.root || '')) + '</div></div>';
20633
+ html += '<div class="mcp-server-row"><div class="mcp-server-copy"><div class="mcp-server-title">' + esc(server.name || '') +
20634
+ '<span class="plugin-status-badge">' + esc(server.ecosystem || t('plugins.mcpReadonly')) + '</span>' +
20635
+ '<span class="plugin-status-badge' + (server.enabled ? ' enabled' : '') + '">' + esc(server.enabled ? t('plugins.mcpEnabledStatus') : t('plugins.mcpDisabledStatus')) + '</span></div><div class="mcp-server-meta">' +
20636
+ esc([server.plugin, server.root].filter(Boolean).join(' · ')) + '</div></div><span class="plugin-status-badge">' + esc(t('plugins.mcpReadonly')) + '</span></div>';
18950
20637
  });
20638
+ html += '</section>';
18951
20639
  panel.innerHTML = html;
20640
+ window.updateMcpTransportForm(false);
20641
+ if (state.mcpFormFocusRequested) {
20642
+ state.mcpFormFocusRequested = false;
20643
+ requestAnimationFrame(function() {
20644
+ var form = document.getElementById('mcp-form');
20645
+ var input = document.getElementById('mcp-name');
20646
+ if (form) form.scrollIntoView({ block: 'nearest' });
20647
+ if (input) input.focus({ preventScroll: true });
20648
+ });
20649
+ }
18952
20650
  };
18953
20651
 
18954
- window.updateMcpTransportForm = function() {
18955
- var transport = document.getElementById('mcp-transport').value;
18956
- var args = document.getElementById('mcp-args');
18957
- var env = document.getElementById('mcp-env');
18958
- if (args) { args.value = ''; args.placeholder = transport === 'http' ? t('plugins.mcpHeaders') : t('plugins.mcpArgs'); }
18959
- if (env) env.style.display = transport === 'stdio' ? 'block' : 'none';
20652
+ window.captureMcpDraftFromForm = function() {
20653
+ if (!state.mcpDraft || !document.getElementById('mcp-form')) return state.mcpDraft;
20654
+ var value = function(id) { var element = document.getElementById(id); return element ? element.value : ''; };
20655
+ state.mcpDraft.name = value('mcp-name');
20656
+ state.mcpDraft.transport = value('mcp-transport') === 'http' ? 'http' : 'stdio';
20657
+ state.mcpDraft.command = value('mcp-command');
20658
+ state.mcpDraft.url = value('mcp-url');
20659
+ state.mcpDraft.argsText = value('mcp-args');
20660
+ state.mcpDraft.cwd = value('mcp-cwd');
20661
+ state.mcpDraft.envText = value('mcp-env');
20662
+ state.mcpDraft.headersText = value('mcp-headers');
20663
+ var enabled = document.getElementById('mcp-enabled');
20664
+ state.mcpDraft.enabled = !!(enabled && enabled.checked);
20665
+ return state.mcpDraft;
20666
+ };
20667
+
20668
+ window.updateMcpTransportForm = function(capture) {
20669
+ if (capture !== false) window.captureMcpDraftFromForm();
20670
+ var transport = document.getElementById('mcp-transport');
20671
+ var stdio = document.getElementById('mcp-stdio-fields');
20672
+ var http = document.getElementById('mcp-http-fields');
20673
+ var isHttp = !!(transport && transport.value === 'http');
20674
+ if (state.mcpDraft) state.mcpDraft.transport = isHttp ? 'http' : 'stdio';
20675
+ if (stdio) stdio.hidden = isHttp;
20676
+ if (http) http.hidden = !isHttp;
20677
+ };
20678
+
20679
+ window.openMcpAddForm = function() {
20680
+ if (state.mcpMutationPending) return;
20681
+ state.mcpEditingId = '';
20682
+ state.mcpDraft = newMcpDraft({ enabled: false, transport: 'stdio', args: [] }, 'add');
20683
+ state.mcpFormFocusRequested = true;
20684
+ window.renderMcpManagerContent();
20685
+ };
20686
+
20687
+ window.editMcpServerAt = function(index) {
20688
+ if (state.mcpMutationPending) return;
20689
+ var server = (state.mcpServers || [])[index];
20690
+ if (!server) return;
20691
+ state.mcpEditingId = server.id || '';
20692
+ state.mcpDraft = newMcpDraft(server, 'edit');
20693
+ state.mcpFormFocusRequested = true;
20694
+ window.renderMcpManagerContent();
20695
+ };
20696
+
20697
+ window.cancelMcpForm = function() {
20698
+ if (state.mcpMutationPending) return;
20699
+ state.mcpEditingId = '';
20700
+ state.mcpDraft = null;
20701
+ window.renderMcpManagerContent();
20702
+ var addButton = document.getElementById('mcp-add');
20703
+ if (addButton) addButton.focus({ preventScroll: true });
20704
+ };
20705
+
20706
+ window.updateMcpSearch = function(value) {
20707
+ window.captureMcpDraftFromForm();
20708
+ state.mcpSearchQuery = String(value || '');
20709
+ window.renderMcpManagerContent();
20710
+ var search = document.getElementById('mcp-search');
20711
+ if (search) {
20712
+ search.focus({ preventScroll: true });
20713
+ search.setSelectionRange(search.value.length, search.value.length);
20714
+ }
18960
20715
  };
18961
- window.resetMcpForm = function() { state.mcpEditingId = ''; window.renderMcpManagerContent(); };
18962
- window.editMcpServer = function(id) { state.mcpEditingId = id; window.renderMcpManagerContent(); };
20716
+
20717
+ function parseMcpObject(text, fieldLabel) {
20718
+ if (!String(text || '').trim()) return undefined;
20719
+ var parsed = JSON.parse(text);
20720
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error(pluginText('plugins.mcpInvalidObject', { field: fieldLabel }));
20721
+ return parsed;
20722
+ }
20723
+
20724
+ function mcpMutationUiActive(generation) {
20725
+ return generation === state.mcpMutationGeneration
20726
+ && state.pluginActiveTab === 'mcp'
20727
+ && !!(state.activeSubWindowView && state.activeSubWindowView.name === 'plugins')
20728
+ && !!document.getElementById('plugin-panel');
20729
+ }
20730
+
18963
20731
  window.saveMcpServer = function() {
18964
- if (!api.upsertMcpServer) return;
18965
- var name = document.getElementById('mcp-name').value.trim();
18966
- var transport = document.getElementById('mcp-transport').value;
18967
- var endpoint = document.getElementById('mcp-command').value.trim();
18968
- var argsText = document.getElementById('mcp-args').value.trim();
18969
- var envText = document.getElementById('mcp-env').value.trim();
18970
- var input = { id: state.mcpEditingId || undefined, name: name, transport: transport };
20732
+ if (!api.upsertMcpServer || state.mcpMutationPending) return;
20733
+ var draft = window.captureMcpDraftFromForm();
20734
+ if (!draft) return;
20735
+ var name = String(draft.name || '').trim();
20736
+ var transport = draft.transport === 'http' ? 'http' : 'stdio';
20737
+ if (!name) { showUiNotice(t('plugins.mcpRequiredName'), 'error', 'mcp-save'); return; }
20738
+ var input = { id: draft.id || state.mcpEditingId || undefined, name: name, transport: transport, enabled: !!draft.enabled };
18971
20739
  try {
18972
- if (transport === 'http') { input.url = endpoint; if (argsText) input.headers = JSON.parse(argsText); }
18973
- else { input.command = endpoint; input.args = argsText ? JSON.parse(argsText) : []; if (envText) input.env = JSON.parse(envText); }
20740
+ if (transport === 'http') {
20741
+ var url = String(draft.url || '').trim();
20742
+ if (!/^https?:\/\//i.test(url)) throw new Error(t('plugins.mcpRequiredUrl'));
20743
+ input.url = url;
20744
+ var headers = parseMcpObject(draft.headersText, t('plugins.mcpHeaders'));
20745
+ if (headers !== undefined) input.headers = headers;
20746
+ } else {
20747
+ var command = String(draft.command || '').trim();
20748
+ if (!command) throw new Error(t('plugins.mcpRequiredCommand'));
20749
+ var args = String(draft.argsText || '').trim() ? JSON.parse(draft.argsText) : [];
20750
+ if (!Array.isArray(args)) throw new Error(t('plugins.mcpInvalidArgs'));
20751
+ input.command = command;
20752
+ input.args = args;
20753
+ input.cwd = String(draft.cwd || '').trim() || undefined;
20754
+ var env = parseMcpObject(draft.envText, t('plugins.mcpEnv'));
20755
+ if (env !== undefined) input.env = env;
20756
+ }
18974
20757
  } catch (error) {
18975
- showUiNotice(error.message || String(error), 'error', 'mcp-json');
20758
+ showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-save');
18976
20759
  return;
18977
20760
  }
18978
- api.upsertMcpServer(input).then(function(result) {
18979
- if (!result || !result.ok) throw new Error(result && result.error || 'MCP update failed');
20761
+ var generation = ++state.mcpMutationGeneration;
20762
+ state.mcpMutationPending = true;
20763
+ window.renderMcpManagerContent();
20764
+ Promise.resolve(api.upsertMcpServer(input)).then(function(result) {
20765
+ if (!result || result.ok !== true) throw new Error(result && result.error || t('plugins.mcpMutationFailed'));
20766
+ if (Array.isArray(result.servers)) state.mcpServers = result.servers;
18980
20767
  state.mcpEditingId = '';
18981
- window.renderMcpManager();
18982
- }).catch(function(error) { showUiNotice(error.message || String(error), 'error', 'mcp-save'); });
20768
+ state.mcpDraft = null;
20769
+ if (mcpMutationUiActive(generation)) showUiNotice(t('plugins.mcpSaved'), 'success', 'mcp-save');
20770
+ }).catch(function(error) {
20771
+ if (mcpMutationUiActive(generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-save');
20772
+ }).finally(function() {
20773
+ state.mcpMutationPending = false;
20774
+ if (mcpMutationUiActive(generation)) window.renderMcpManager();
20775
+ });
20776
+ };
20777
+
20778
+ window.toggleMcpServerAt = function(index, enabled) {
20779
+ if (!api.setMcpServerEnabled || state.mcpMutationPending) return;
20780
+ var server = (state.mcpServers || [])[index];
20781
+ if (!server) return;
20782
+ var generation = ++state.mcpMutationGeneration;
20783
+ state.mcpMutationPending = true;
20784
+ window.renderMcpManagerContent();
20785
+ Promise.resolve(api.setMcpServerEnabled(server.id, !!enabled)).then(function(result) {
20786
+ if (!result || result.ok !== true) throw new Error(result && result.error || t('plugins.mcpMutationFailed'));
20787
+ if (Array.isArray(result.servers)) state.mcpServers = result.servers;
20788
+ }).catch(function(error) {
20789
+ if (mcpMutationUiActive(generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-toggle');
20790
+ }).finally(function() {
20791
+ state.mcpMutationPending = false;
20792
+ if (mcpMutationUiActive(generation)) window.renderMcpManager();
20793
+ });
20794
+ };
20795
+
20796
+ window.removeMcpServerAt = function(index) {
20797
+ if (!api.removeMcpServer || state.mcpMutationPending) return;
20798
+ var server = (state.mcpServers || [])[index];
20799
+ if (!server || !confirm(pluginText('plugins.mcpRemoveConfirm', { name: server.name || '' }))) return;
20800
+ var generation = ++state.mcpMutationGeneration;
20801
+ state.mcpMutationPending = true;
20802
+ window.renderMcpManagerContent();
20803
+ Promise.resolve(api.removeMcpServer(server.id)).then(function(result) {
20804
+ if (!result || result.ok !== true) throw new Error(result && result.error || t('plugins.mcpMutationFailed'));
20805
+ if (Array.isArray(result.servers)) state.mcpServers = result.servers;
20806
+ if (state.mcpEditingId === server.id) { state.mcpEditingId = ''; state.mcpDraft = null; }
20807
+ }).catch(function(error) {
20808
+ if (mcpMutationUiActive(generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-remove');
20809
+ }).finally(function() {
20810
+ state.mcpMutationPending = false;
20811
+ if (mcpMutationUiActive(generation)) window.renderMcpManager();
20812
+ });
18983
20813
  };
18984
- window.toggleMcpServer = function(id, enabled) { if (api.setMcpServerEnabled) api.setMcpServerEnabled(id, enabled).then(window.renderMcpManager); };
18985
- window.removeMcpServerFromUi = function(id) { if (api.removeMcpServer) api.removeMcpServer(id).then(window.renderMcpManager); };
18986
20814
 
18987
20815
  window.filteredSkillMarket = function() {
18988
20816
  var market = state._skillMarketAll || [];
@@ -19003,7 +20831,7 @@ window.filteredSkillMarket = function() {
19003
20831
 
19004
20832
  window.renderSkillsMarketList = function() {
19005
20833
  var panel = document.getElementById('plugin-panel');
19006
- if (!panel) return;
20834
+ if (!panel || state.pluginActiveTab !== 'market' || !state.activeSubWindowView || state.activeSubWindowView.name !== 'plugins') return;
19007
20835
  var market = window.filteredSkillMarket();
19008
20836
  var total = (state._skillMarketAll || []).length;
19009
20837
  var query = state.skillMarketQuery || '';
@@ -19272,13 +21100,14 @@ window.renderLeftWsList = function() {
19272
21100
  var container = document.getElementById('left-ws-list');
19273
21101
  if (!container) return;
19274
21102
  var workspaces = state.workspaces || [];
21103
+ var hasActiveWorkspace = workspaces.some(function(item) { return workspaceIdentity(item) === String(state.currentWorkspaceId || ''); });
19275
21104
  var html = '';
19276
21105
  for (var i = 0; i < workspaces.length; i++) {
19277
21106
  var ws = workspaces[i];
19278
21107
  var identity = workspaceIdentity(ws);
19279
21108
  var active = identity === String(state.currentWorkspaceId || '');
19280
21109
  var runtimeStatus = workspaceRuntimeStatus(ws);
19281
- html += '<div class="left-ws-item' + (active ? ' active' : '') + '" onclick="window.switchToWorkspace(\'' + escAttr(identity) + '\')">' +
21110
+ html += '<div class="left-ws-item' + (active ? ' active' : '') + '" role="listitem" tabindex="' + (active || (!hasActiveWorkspace && i === 0) ? '0' : '-1') + '" aria-current="' + (active ? 'true' : 'false') + '" onclick="window.switchToWorkspace(\'' + escAttr(identity) + '\')" onkeydown="window.handleWorkspaceKey(event)">' +
19282
21111
  '<span class="ws-icon">' + esc((ws.name || '?')[0].toUpperCase()) + '</span>' +
19283
21112
  '<span class="ws-label">' + esc(ws.name || t('workspace.untitled')) + '</span>' +
19284
21113
  (runtimeStatus ? '<span class="ws-runtime-dot ' + escAttr(runtimeStatus) + '" title="' + escAttr(runtimeStatus) + '"></span>' : '') +
@@ -19291,6 +21120,24 @@ window.renderLeftWsList = function() {
19291
21120
  appendDomNodesInBatches(container, Array.from(holder.children), 'workspace-list', 24);
19292
21121
  };
19293
21122
 
21123
+ window.handleWorkspaceKey = function(event) {
21124
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
21125
+ if (event.key === 'Enter' || event.key === ' ') return window.activateButtonLike(event);
21126
+ var items = Array.prototype.slice.call(document.querySelectorAll('#left-ws-list .left-ws-item'));
21127
+ var index = items.indexOf(event.currentTarget);
21128
+ if (index < 0 || !items.length) return false;
21129
+ var next = index;
21130
+ if (event.key === 'ArrowDown' || event.key === 'ArrowRight') next = (index + 1) % items.length;
21131
+ else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length;
21132
+ else if (event.key === 'Home') next = 0;
21133
+ else if (event.key === 'End') next = items.length - 1;
21134
+ else return false;
21135
+ event.preventDefault();
21136
+ items[next].focus({ preventScroll:true });
21137
+ items[next].click();
21138
+ return true;
21139
+ };
21140
+
19294
21141
  window.toggleWorkspacePinned = function(identity) {
19295
21142
  if (!identity || !api.setWorkspacePinned) return;
19296
21143
  var nextPinned = false;
@@ -19395,7 +21242,7 @@ window.showNewWorkspaceDialog = function() {
19395
21242
  '<label style="display:flex;align-items:center;gap:4px;font-size:12px;cursor:pointer;"><input type="radio" name="ws-type" value="ssh" onchange="window.toggleWsType()"> ' + esc(t('workspace.ssh')) + '</label>' +
19396
21243
  '</div>' +
19397
21244
  '<label id="ws-name-label-dlg" style="display:block;font-size:12px;color:var(--text-dim);margin-bottom:8px;">' + esc(t('workspace.name')) + '</label>' +
19398
- '<input id="new-ws-input" style="width:100%;padding:8px 10px;border:1px solid var(--glass-border-2);border-radius:var(--radius-sm);background:var(--glass-bg-1);color:var(--text);font-size:13px;outline:none;box-sizing:border-box;" placeholder="' + escAttr(t('workspace.enterName')) + '" onkeydown="if(event.key===\'Enter\')window.doCreateWorkspace()">' +
21245
+ '<input id="new-ws-input" style="width:100%;padding:8px 10px;border:1px solid var(--glass-border-2);border-radius:var(--radius-sm);background:var(--glass-bg-1);color:var(--text);font-size:13px;outline:none;box-sizing:border-box;" placeholder="' + escAttr(t('workspace.enterName')) + '" onkeydown="if(event.key===\'Enter\'&&!event.isComposing&&event.key!==\'Process\'&&event.keyCode!==229)window.doCreateWorkspace()">' +
19399
21246
  '<div id="ws-ext-path" style="display:none;margin-top:8px;">' +
19400
21247
  '<label style="display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;">' + esc(t('workspace.folderPath')) + '</label>' +
19401
21248
  '<div style="display:flex;gap:6px;">' +
@@ -19651,7 +21498,7 @@ window.openWorkspaceManager = async function() {
19651
21498
  var ws = workspaces[i];
19652
21499
  var identity = workspaceIdentity(ws);
19653
21500
  var isActive = identity === String(state.currentWorkspaceId || '');
19654
- listHtml += '<div style="display:flex;align-items:center;gap:8px;padding:8px 10px;margin:2px 0;border-radius:var(--radius-sm);background:' + (isActive ? 'var(--accent-glow)' : 'var(--glass-bg-1)') + ';cursor:pointer;" onclick="window.switchToWorkspace(\'' + escAttr(identity) + '\')">' +
21501
+ listHtml += '<div role="button" tabindex="0" style="display:flex;align-items:center;gap:8px;padding:8px 10px;margin:2px 0;border-radius:var(--radius-sm);background:' + (isActive ? 'var(--accent-glow)' : 'var(--glass-bg-1)') + ';cursor:pointer;" onclick="window.switchToWorkspace(\'' + escAttr(identity) + '\')" onkeydown="window.activateButtonLike(event)">' +
19655
21502
  '<span style="width:24px;height:24px;border-radius:var(--radius-sm);background:linear-gradient(135deg,var(--accent),#7b93ff);color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;">' + esc((ws.name || '?')[0].toUpperCase()) + '</span>' +
19656
21503
  '<span style="flex:1;font-size:12px;color:var(--text);">' + esc(ws.name || t('workspace.untitled')) + '</span>' +
19657
21504
  (isActive ? '<span style="font-size:10px;color:var(--accent2);">' + esc(t('workspace.current')) + '</span>' : '') +
@@ -19888,6 +21735,549 @@ function stopMarqueeJS() {
19888
21735
  if (marqueeRAF) { cancelAnimationFrame(marqueeRAF); marqueeRAF = null; }
19889
21736
  }
19890
21737
 
21738
+ // === Unified GUI Command Registry & Keyboard Routing ===
21739
+ function guiCommand(id, titleKey, category, options) {
21740
+ return Object.assign({ id: id, titleKey: titleKey, category: category, bindings: [], scope: 'palette', keywords: [] }, options || {});
21741
+ }
21742
+
21743
+ function guiIsMac() {
21744
+ return String(state.platform || navigator.platform || '').toLowerCase().indexOf('mac') >= 0;
21745
+ }
21746
+
21747
+ function guiBindingRecord(command, input) {
21748
+ return typeof input === 'string'
21749
+ ? { keys: input, scope: command.scope || 'palette' }
21750
+ : { keys: String((input && input.keys) || ''), scope: String((input && input.scope) || command.scope || 'palette') };
21751
+ }
21752
+
21753
+ function guiDisplayBinding(binding) {
21754
+ var keys = typeof binding === 'string' ? binding : String((binding && binding.keys) || '');
21755
+ return keys.replace(/\bMod\b/g, guiIsMac() ? 'Cmd' : 'Ctrl').replace(/ /g, ' then ');
21756
+ }
21757
+
21758
+ function guiSetSelectValue(id, value) {
21759
+ var select = document.getElementById(id);
21760
+ if (!select) return false;
21761
+ select.value = value;
21762
+ select.dispatchEvent(new Event('change', { bubbles: true }));
21763
+ return true;
21764
+ }
21765
+
21766
+ function guiSubWindowOpen() {
21767
+ return !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'));
21768
+ }
21769
+
21770
+ function guiFocusPrompt() {
21771
+ if (guiSubWindowOpen()) return false;
21772
+ if (els.prompt) {
21773
+ els.prompt.focus({ preventScroll: true });
21774
+ return true;
21775
+ }
21776
+ return false;
21777
+ }
21778
+
21779
+ function guiFocusTerminal() {
21780
+ if (guiSubWindowOpen()) return false;
21781
+ if (state.bottomCollapsed) window.toggleBottom();
21782
+ var input = document.querySelector('#terminal-body .terminal-pane.active .terminal-input') || document.querySelector('#terminal-body .terminal-input');
21783
+ if (input) {
21784
+ input.focus({ preventScroll: true });
21785
+ return true;
21786
+ }
21787
+ return false;
21788
+ }
21789
+
21790
+ function guiFocusBrowserAddress() {
21791
+ if (guiSubWindowOpen()) return false;
21792
+ window.switchRightTab('browser');
21793
+ requestAnimationFrame(function() {
21794
+ var input = document.getElementById('browser-url');
21795
+ if (input) { input.focus({ preventScroll: true }); input.select(); }
21796
+ });
21797
+ return true;
21798
+ }
21799
+
21800
+ function guiCycleConversation(direction) {
21801
+ var conversations = currentWorkspaceConversations ? currentWorkspaceConversations() : [];
21802
+ if (!conversations.length) return false;
21803
+ var current = Math.max(0, Math.min(conversations.length - 1, Number(state.activeConversation) || 0));
21804
+ var next = (current + direction + conversations.length) % conversations.length;
21805
+ window.switchConversation(next);
21806
+ return true;
21807
+ }
21808
+
21809
+ function guiHasSwitchableBranch() {
21810
+ var groups = state.conversationBranchGroups || [];
21811
+ var branches = state.conversationBranches || [];
21812
+ if (groups.length) return groups.some(function(group) { return (group.branches || []).length > 1; });
21813
+ return branches.length > 1;
21814
+ }
21815
+
21816
+ function guiToggleTheme() {
21817
+ var current = state.theme === 'light' ? 'light' : 'dark';
21818
+ window.setTheme(current === 'light' ? 'dark' : 'light');
21819
+ }
21820
+
21821
+ var NEWMARK_GUI_COMMANDS = [
21822
+ guiCommand('app.commandPalette', 'shortcuts.openPalette', 'general', { bindings: [{ keys:'Mod+Shift+P', scope:'global' }], run:function(){ window.openCommandSurface('palette'); } }),
21823
+ guiCommand('help.keyboardShortcuts', 'shortcuts.openHelp', 'general', { bindings: [{ keys:'F1', scope:'global' }], run:function(){ window.openCommandSurface('help'); } }),
21824
+ guiCommand('settings.open', 'settings.title', 'general', { bindings: [{ keys:'Mod+,', scope:'global' }, { keys:'Mod+K S', scope:'whenNotEditing' }], run:function(){ window.openSettings(); } }),
21825
+ guiCommand('window.minimize', 'top.minimize', 'general', { run:function(){ api.minimize(); } }),
21826
+ guiCommand('window.maximize', 'top.maximize', 'general', { run:function(){ api.maximize(); } }),
21827
+ guiCommand('view.toggleTheme', 'shortcuts.toggleTheme', 'general', { bindings:[{ keys:'Mod+K Q', scope:'whenNotEditing' }], run:guiToggleTheme }),
21828
+
21829
+ guiCommand('conversation.new', 'left.newChat', 'workspace', { bindings:[{ keys:'Mod+K N', scope:'whenNotEditing' }], run:function(){ if (state.currentWorkspaceId) window.newConversation(); else window.showNewConversationPage(); } }),
21830
+ guiCommand('conversation.next', 'shortcuts.nextConversation', 'workspace', { bindings:[{ keys:'Mod+Shift+ArrowRight', scope:'whenNotEditing' }], run:function(){ guiCycleConversation(1); }, available:function(){ return currentWorkspaceConversations().length > 1; } }),
21831
+ guiCommand('conversation.previous', 'shortcuts.previousConversation', 'workspace', { bindings:[{ keys:'Mod+Shift+ArrowLeft', scope:'whenNotEditing' }], run:function(){ guiCycleConversation(-1); }, available:function(){ return currentWorkspaceConversations().length > 1; } }),
21832
+ guiCommand('conversation.archive', 'archive.current', 'workspace', { run:function(){ window.archiveCurrent(); }, available:function(){ return !!state.currentWorkspaceId; } }),
21833
+ guiCommand('branch.previous', 'shortcuts.previousBranch', 'workspace', { bindings:[{ keys:'Alt+ArrowUp', scope:'whenNotEditing' }], run:function(){ window.switchConversationBranch(-1); }, available:function(){ return guiHasSwitchableBranch(); } }),
21834
+ guiCommand('branch.next', 'shortcuts.nextBranch', 'workspace', { bindings:[{ keys:'Alt+ArrowDown', scope:'whenNotEditing' }], run:function(){ window.switchConversationBranch(1); }, available:function(){ return guiHasSwitchableBranch(); } }),
21835
+ guiCommand('workspace.manager', 'workspace.manager', 'workspace', { bindings:[{ keys:'Mod+K W', scope:'whenNotEditing' }], run:function(){ window.openWorkspaceManager(); } }),
21836
+ guiCommand('workspace.new', 'workspace.new', 'workspace', { run:function(){ window.showNewWorkspaceDialog(); } }),
21837
+ guiCommand('workspace.settings', 'workspace.settingsTitle', 'workspace', { run:function(){ window.openWsSettings(); }, available:function(){ return !!state.currentWorkspaceId; } }),
21838
+
21839
+ guiCommand('view.plugins', 'plugins.title', 'navigation', { bindings:[{ keys:'Mod+K P', scope:'whenNotEditing' }], run:function(){ window.showPluginList(); } }),
21840
+ guiCommand('view.plugins.mcp', 'plugins.mcp', 'navigation', { run:function(){ window.showPluginList('mcp'); } }),
21841
+ guiCommand('view.plugins.dsh', 'plugins.dsh', 'navigation', { bindings:[{ keys:'Mod+K D', scope:'whenNotEditing' }], run:function(){ window.showPluginList('dsh'); } }),
21842
+ guiCommand('view.plugins.skills', 'plugins.management', 'navigation', { run:function(){ window.showPluginList('installed'); } }),
21843
+ guiCommand('view.plugins.market', 'plugins.market', 'navigation', { run:function(){ window.showPluginList('market'); } }),
21844
+ guiCommand('view.plugins.github', 'plugins.github', 'navigation', { run:function(){ window.showPluginList('github'); } }),
21845
+ guiCommand('view.memoryLab', 'memoryLab.title', 'navigation', { bindings:[{ keys:'Mod+K M', scope:'whenNotEditing' }], run:function(){ window.showMemoryLab(); } }),
21846
+ guiCommand('view.automation', 'automation.title', 'navigation', { bindings:[{ keys:'Mod+K A', scope:'whenNotEditing' }], run:function(){ window.showAutomationWindow(); } }),
21847
+ guiCommand('automation.new', 'automation.new', 'navigation', { run:function(){ window.showNewAutomationForm(); } }),
21848
+ guiCommand('view.flowEditor', 'flow.title', 'navigation', { bindings:[{ keys:'Mod+K F', scope:'whenNotEditing' }], run:function(){ window.showFlowEditor(); } }),
21849
+ guiCommand('flow.new', 'flow.new', 'navigation', { run:function(){ window.newFlowWork(); } }),
21850
+
21851
+ guiCommand('focus.primaryInput', 'shortcuts.focusPrimary', 'layout', { bindings:[{ keys:'Mod+K C', scope:'whenNotEditing' }], run:guiFocusPrompt, available:function(){ return !guiSubWindowOpen(); } }),
21852
+ guiCommand('focus.nextRegion', 'shortcuts.focusNext', 'layout', { bindings:[{ keys:'F6', scope:'global' }], run:function(){ window.cycleGuiRegionFocus(1); }, available:function(){ return !guiSubWindowOpen() && !window.commandSurfaceIsOpen(); } }),
21853
+ guiCommand('focus.previousRegion', 'shortcuts.focusPrevious', 'layout', { bindings:[{ keys:'Shift+F6', scope:'global' }], run:function(){ window.cycleGuiRegionFocus(-1); }, available:function(){ return !guiSubWindowOpen() && !window.commandSurfaceIsOpen(); } }),
21854
+ guiCommand('view.toggleLeft', 'shortcuts.toggleLeft', 'layout', { bindings:[{ keys:'Mod+B', scope:'whenNotEditing' }], run:function(){ window.toggleLeft(); } }),
21855
+ guiCommand('view.toggleWorkspacePanel', 'shortcuts.toggleWorkspacePanel', 'layout', { run:function(){ window.toggleSecondarySidebar(); } }),
21856
+ guiCommand('view.toggleRight', 'shortcuts.toggleRight', 'layout', { run:function(){ window.toggleRight(); } }),
21857
+ guiCommand('view.toggleTerminal', 'shortcuts.toggleTerminal', 'layout', { bindings:[{ keys:'Mod+`', scope:'whenNotEditing' }], run:function(){ window.toggleBottom(); } }),
21858
+ guiCommand('chat.scrollBottom', 'shortcuts.scrollBottom', 'layout', { bindings:[{ keys:'Mod+End', scope:'whenNotEditing' }], run:function(){ window.scrollToBottom(); } }),
21859
+
21860
+ guiCommand('right.files', 'right.files', 'navigation', { bindings:[{ keys:'Mod+K 1', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('file-tree'); } }),
21861
+ guiCommand('right.editor', 'right.editor', 'navigation', { bindings:[{ keys:'Mod+K 2', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('editor'); } }),
21862
+ guiCommand('right.plan', 'right.plan', 'navigation', { bindings:[{ keys:'Mod+K 3', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('plan'); } }),
21863
+ guiCommand('right.subagents', 'right.subagents', 'navigation', { run:function(){ window.switchRightTab('subagent'); } }),
21864
+ guiCommand('right.browser', 'right.browser', 'navigation', { bindings:[{ keys:'Mod+K 4', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('browser'); } }),
21865
+ guiCommand('right.status', 'right.status', 'navigation', { run:function(){ window.switchRightTab('status'); } }),
21866
+ guiCommand('right.archives', 'right.archives', 'navigation', { run:function(){ window.switchRightTab('archives'); } }),
21867
+
21868
+ guiCommand('mode.build', 'mode.build', 'input', { bindings:[{ keys:'Mod+1', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','build'); } }),
21869
+ guiCommand('mode.plan', 'mode.plan', 'input', { bindings:[{ keys:'Mod+2', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','plan'); } }),
21870
+ guiCommand('mode.goal', 'mode.goal', 'input', { bindings:[{ keys:'Mod+3', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','goal'); } }),
21871
+ guiCommand('mode.flow', 'mode.flow', 'input', { bindings:[{ keys:'Mod+4', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','flow'); } }),
21872
+ guiCommand('input.guide', 'input.guide', 'input', { bindings:[{ keys:'Mod+K G', scope:'whenNotEditing' }], run:function(){ window.setInputMode('guide'); } }),
21873
+ guiCommand('input.next', 'input.next', 'input', { bindings:[{ keys:'Mod+K E', scope:'whenNotEditing' }], run:function(){ window.setInputMode('next'); } }),
21874
+ guiCommand('goal.edit', 'goal.edit', 'input', { run:function(){ window.editGoal(); } }),
21875
+ guiCommand('goal.pauseResume', 'goal.pause', 'input', { bindings:[{ keys:'Mod+K Shift+G', scope:'whenNotEditing' }], run:function(){ window.toggleGoalPause(); }, available:function(){ return !!state.goalVisible; } }),
21876
+
21877
+ guiCommand('editor.save', 'common.save', 'editor', { bindings:[{ keys:'Mod+S', scope:'editor' }], run:function(){ window.saveEditor(); }, available:function(){ return !!state.editorPath; } }),
21878
+ guiCommand('editor.close', 'common.close', 'editor', { run:function(){ window.closeEditor(); }, available:function(){ return !!state.editorPath; } }),
21879
+ guiCommand('editor.markdownPreview', 'right.md', 'editor', { run:function(){ window.toggleEditorMarkdownPreview(); }, available:function(){ return !!state.editorPath; } }),
21880
+ guiCommand('editor.prediction', 'model.thinking', 'editor', { run:function(){ window.toggleEditorPrediction(); }, available:function(){ return !!state.editorPath; } }),
21881
+ guiCommand('editor.completion', 'model.validationCurrent', 'editor', { run:function(){ window.requestEditorCompletion({ force:true }); }, available:function(){ return !!state.editorPath; } }),
21882
+
21883
+ guiCommand('terminal.focus', 'shortcuts.focusTerminal', 'terminal', { bindings:[{ keys:'Mod+K T', scope:'whenNotEditing' }], run:guiFocusTerminal, available:function(){ return !guiSubWindowOpen(); } }),
21884
+ guiCommand('terminal.new', 'shortcuts.newTerminal', 'terminal', { run:function(){ window.addTerminalTab(); } }),
21885
+ guiCommand('terminal.clear', 'terminal.clear', 'terminal', { run:function(){ window.clearTerminal(); } }),
21886
+
21887
+ guiCommand('browser.focusAddress', 'shortcuts.focusBrowserAddress', 'browser', { bindings:[{ keys:'Mod+L', scope:'browser' }, { keys:'Mod+K B', scope:'whenNotEditing' }], run:guiFocusBrowserAddress, available:function(){ return !guiSubWindowOpen(); } }),
21888
+ guiCommand('browser.back', 'browser.back', 'browser', { bindings:[{ keys:'Alt+ArrowLeft', scope:'browser' }], run:function(){ window.browserBack(); } }),
21889
+ guiCommand('browser.forward', 'browser.forward', 'browser', { bindings:[{ keys:'Alt+ArrowRight', scope:'browser' }], run:function(){ window.browserForward(); } }),
21890
+ guiCommand('browser.reload', 'browser.reload', 'browser', { bindings:[{ keys:'Mod+R', scope:'browser' }, { keys:'F5', scope:'browser' }], run:function(){ window.browserReload(); } }),
21891
+ guiCommand('browser.computerUse', 'right.browser', 'browser', { run:function(){ window.toggleComputerUse(); } }),
21892
+
21893
+ guiCommand('settings.general', 'settings.general', 'context', { run:function(){ window.openSettings('general'); } }),
21894
+ guiCommand('settings.models', 'settings.models', 'context', { run:function(){ window.openSettings('models'); } }),
21895
+ guiCommand('settings.tools', 'settings.tools', 'context', { run:function(){ window.openSettings('tools'); } }),
21896
+ guiCommand('settings.archive', 'settings.archive', 'context', { run:function(){ window.openSettings('archive'); } }),
21897
+ guiCommand('settings.updates', 'settings.updates', 'context', { run:function(){ window.openSettings('updates'); } })
21898
+ ];
21899
+ window.NEWMARK_GUI_COMMANDS = NEWMARK_GUI_COMMANDS;
21900
+
21901
+ window.activateButtonLike = function(event) {
21902
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229) return false;
21903
+ if (event.key !== 'Enter' && event.key !== ' ') return false;
21904
+ if (event.target !== event.currentTarget) return false;
21905
+ event.preventDefault();
21906
+ event.currentTarget.click();
21907
+ return true;
21908
+ };
21909
+
21910
+ function guiCommandTitle(command) {
21911
+ return t(command.titleKey || command.id);
21912
+ }
21913
+
21914
+ function guiCommandCategory(command) {
21915
+ return t('shortcuts.category.' + (command.category || 'general'));
21916
+ }
21917
+
21918
+ function guiCommandAvailable(command) {
21919
+ try { return !command.available || command.available() !== false; }
21920
+ catch(e) { return false; }
21921
+ }
21922
+
21923
+ function guiKeyboardContext(event) {
21924
+ var target = event && event.target && event.target.nodeType === 1 ? event.target : document.activeElement;
21925
+ var closest = function(selector) { return target && target.closest ? target.closest(selector) : null; };
21926
+ var editable = !!(target && ((target.matches && target.matches('input, textarea, select, [role="textbox"], [role="combobox"]')) || target.isContentEditable || closest('[contenteditable]:not([contenteditable="false"])')));
21927
+ return {
21928
+ target: target,
21929
+ editable: editable,
21930
+ editor: !!closest('#native-editor, #panel-editor'),
21931
+ terminal: !!closest('#bottom, .terminal-pane'),
21932
+ browser: !!closest('#panel-browser, .browser-url-bar') || state.rightTab === 'browser' && !!closest('#right'),
21933
+ prompt: !!(els.prompt && (target === els.prompt || (els.prompt.contains && els.prompt.contains(target)))),
21934
+ dialog: !!closest('[role="dialog"]')
21935
+ };
21936
+ }
21937
+
21938
+ function guiBindingScopeMatches(scope, context) {
21939
+ if (scope === 'global') return true;
21940
+ if (scope === 'whenNotEditing') return !context.editable && !context.editor && !context.terminal && !context.browser && !context.dialog;
21941
+ if (scope === 'editor') return context.editor;
21942
+ if (scope === 'terminal') return context.terminal;
21943
+ if (scope === 'browser') return context.browser;
21944
+ if (scope === 'prompt') return context.prompt;
21945
+ if (scope === 'dialog') return context.dialog;
21946
+ return false;
21947
+ }
21948
+
21949
+ function guiEventSegment(event) {
21950
+ var key = String(event.key || '');
21951
+ if (!key || ['Control','Shift','Alt','Meta','AltGraph'].indexOf(key) >= 0) return '';
21952
+ var parts = [];
21953
+ var mac = guiIsMac();
21954
+ if ((mac && event.metaKey) || (!mac && event.ctrlKey)) parts.push('Mod');
21955
+ if ((mac && event.ctrlKey) || (!mac && event.metaKey)) parts.push(mac ? 'Ctrl' : 'Meta');
21956
+ if (event.altKey) parts.push('Alt');
21957
+ if (event.shiftKey) parts.push('Shift');
21958
+ var named = { ' ':'Space', 'Esc':'Escape', 'Left':'ArrowLeft', 'Right':'ArrowRight', 'Up':'ArrowUp', 'Down':'ArrowDown' };
21959
+ key = named[key] || key;
21960
+ if (key.length === 1 && /[a-z]/i.test(key)) key = key.toUpperCase();
21961
+ parts.push(key);
21962
+ return parts.join('+');
21963
+ }
21964
+
21965
+ var guiCommandSurfaceOriginFocus = null;
21966
+ var guiCommandSurfaceMode = 'palette';
21967
+ var guiCommandSurfaceMatches = [];
21968
+ var guiCommandSurfaceSelection = 0;
21969
+ var guiPendingChord = null;
21970
+ var guiPendingChordTimer = 0;
21971
+
21972
+ window.commandSurfaceIsOpen = function() {
21973
+ var overlay = document.getElementById('command-surface-overlay');
21974
+ return !!(overlay && overlay.classList.contains('open'));
21975
+ };
21976
+
21977
+ function guiClearChord() {
21978
+ guiPendingChord = null;
21979
+ if (guiPendingChordTimer) clearTimeout(guiPendingChordTimer);
21980
+ guiPendingChordTimer = 0;
21981
+ var hint = document.getElementById('shortcut-chord-hint');
21982
+ if (hint) hint.classList.remove('open');
21983
+ }
21984
+
21985
+ function guiShowChord(prefix, candidates) {
21986
+ guiClearChord();
21987
+ guiPendingChord = { prefix:prefix, candidates:candidates };
21988
+ var seconds = candidates.map(function(item){ return item.binding.keys.split(' ')[1]; }).filter(function(key,index,array){ return array.indexOf(key) === index; });
21989
+ var hint = document.getElementById('shortcut-chord-hint');
21990
+ if (hint) {
21991
+ hint.textContent = t('shortcuts.chordHint').replace('{prefix}', guiDisplayBinding(prefix)).replace('{keys}', seconds.join(' · '));
21992
+ hint.classList.add('open');
21993
+ }
21994
+ guiPendingChordTimer = setTimeout(guiClearChord, 1800);
21995
+ }
21996
+
21997
+ function guiNormalizeCommandSearchText(value) {
21998
+ return String(value || '')
21999
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
22000
+ .replace(/[._:/\\-]+/g, ' ')
22001
+ .replace(/\s+/g, ' ')
22002
+ .trim()
22003
+ .toLocaleLowerCase(uiLocale());
22004
+ }
22005
+
22006
+ function guiCommandSearchText(command) {
22007
+ var bindings = (command.bindings || []).map(guiDisplayBinding).join(' ');
22008
+ return guiNormalizeCommandSearchText([command.id, guiCommandTitle(command), guiCommandCategory(command), bindings].concat(command.keywords || []).join(' '));
22009
+ }
22010
+
22011
+ function guiCommandContextLabel(command) {
22012
+ var scopes = (command.bindings || []).map(function(binding){ return guiBindingRecord(command,binding).scope; });
22013
+ var scope = scopes[0] || command.scope || 'palette';
22014
+ if (scope === 'editor') return t('shortcuts.context.editor');
22015
+ if (scope === 'browser') return t('shortcuts.context.browser');
22016
+ if (scope === 'terminal') return t('shortcuts.context.terminal');
22017
+ if (scope === 'prompt') return t('shortcuts.context.prompt');
22018
+ if (scope === 'dialog') return t('shortcuts.context.dialog');
22019
+ return t('shortcuts.context.global');
22020
+ }
22021
+
22022
+ window.renderCommandSurface = function() {
22023
+ var list = document.getElementById('command-list');
22024
+ var search = document.getElementById('command-search');
22025
+ if (!list || !search) return;
22026
+ var query = guiNormalizeCommandSearchText(search.value);
22027
+ guiCommandSurfaceMatches = NEWMARK_GUI_COMMANDS.filter(function(command){ return !query || guiCommandSearchText(command).indexOf(query) >= 0; });
22028
+ if (guiCommandSurfaceSelection >= guiCommandSurfaceMatches.length) guiCommandSurfaceSelection = Math.max(0, guiCommandSurfaceMatches.length - 1);
22029
+ if (!guiCommandSurfaceMatches.length) {
22030
+ list.innerHTML = '<div class="command-empty">' + esc(t('shortcuts.noResults')) + '</div>';
22031
+ search.removeAttribute('aria-activedescendant');
22032
+ return;
22033
+ }
22034
+ var html = '';
22035
+ var priorCategory = '';
22036
+ for (var i = 0; i < guiCommandSurfaceMatches.length; i++) {
22037
+ var command = guiCommandSurfaceMatches[i];
22038
+ var category = guiCommandCategory(command);
22039
+ if (category !== priorCategory) {
22040
+ html += '<div class="command-category">' + esc(category) + '</div>';
22041
+ priorCategory = category;
22042
+ }
22043
+ var available = guiCommandAvailable(command);
22044
+ var keys = (command.bindings || []).map(function(binding){ return '<span class="command-key">' + esc(guiDisplayBinding(binding)) + '</span>'; }).join('');
22045
+ html += '<button type="button" class="command-option' + (i === guiCommandSurfaceSelection ? ' active' : '') + '" id="command-option-' + i + '" role="option" aria-selected="' + (i === guiCommandSurfaceSelection ? 'true' : 'false') + '" aria-disabled="' + (available ? 'false' : 'true') + '" tabindex="-1" onclick="window.executeCommandSurfaceIndex(' + i + ')">' +
22046
+ '<span><span class="command-option-title">' + esc(guiCommandTitle(command)) + '</span><span class="command-option-context">' + esc(guiCommandContextLabel(command)) + '</span></span>' +
22047
+ '<span class="command-shortcuts">' + keys + '</span></button>';
22048
+ }
22049
+ list.innerHTML = html;
22050
+ search.setAttribute('aria-activedescendant', 'command-option-' + guiCommandSurfaceSelection);
22051
+ var active = document.getElementById('command-option-' + guiCommandSurfaceSelection);
22052
+ if (active) active.scrollIntoView({ block:'nearest' });
22053
+ };
22054
+
22055
+ function guiMoveCommandSelection(delta, edge) {
22056
+ if (!guiCommandSurfaceMatches.length) return;
22057
+ if (edge === 'start') guiCommandSurfaceSelection = 0;
22058
+ else if (edge === 'end') guiCommandSurfaceSelection = guiCommandSurfaceMatches.length - 1;
22059
+ else guiCommandSurfaceSelection = (guiCommandSurfaceSelection + delta + guiCommandSurfaceMatches.length) % guiCommandSurfaceMatches.length;
22060
+ window.renderCommandSurface();
22061
+ }
22062
+
22063
+ window.openCommandSurface = function(mode) {
22064
+ var overlay = document.getElementById('command-surface-overlay');
22065
+ var search = document.getElementById('command-search');
22066
+ if (!overlay || !search) return;
22067
+ guiClearChord();
22068
+ if (!window.commandSurfaceIsOpen() && document.activeElement && typeof document.activeElement.focus === 'function') guiCommandSurfaceOriginFocus = document.activeElement;
22069
+ guiCommandSurfaceMode = mode === 'help' ? 'help' : 'palette';
22070
+ document.getElementById('command-surface-title').textContent = t(guiCommandSurfaceMode === 'help' ? 'shortcuts.helpTitle' : 'shortcuts.paletteTitle');
22071
+ document.getElementById('command-surface-subtitle').textContent = t(guiCommandSurfaceMode === 'help' ? 'shortcuts.helpSubtitle' : 'shortcuts.paletteSubtitle');
22072
+ document.getElementById('command-surface-footer').textContent = t('shortcuts.footer');
22073
+ search.setAttribute('placeholder', t(guiCommandSurfaceMode === 'help' ? 'shortcuts.searchShortcuts' : 'shortcuts.searchCommands'));
22074
+ search.value = '';
22075
+ guiCommandSurfaceSelection = 0;
22076
+ overlay.classList.add('open');
22077
+ updateApplicationInertState();
22078
+ window.renderCommandSurface();
22079
+ requestAnimationFrame(function(){ search.focus({ preventScroll:true }); });
22080
+ };
22081
+
22082
+ window.closeCommandSurface = function(options) {
22083
+ options = options || {};
22084
+ var overlay = document.getElementById('command-surface-overlay');
22085
+ if (!overlay || !overlay.classList.contains('open')) return;
22086
+ overlay.classList.remove('open');
22087
+ updateApplicationInertState();
22088
+ var restore = guiCommandSurfaceOriginFocus;
22089
+ guiCommandSurfaceOriginFocus = null;
22090
+ if (options.restoreFocus !== false) requestAnimationFrame(function(){ if (restore && restore.isConnected && typeof restore.focus === 'function') restore.focus({ preventScroll:true }); });
22091
+ return restore;
22092
+ };
22093
+
22094
+ window.executeGuiCommand = function(id, options) {
22095
+ var command = NEWMARK_GUI_COMMANDS.find(function(item){ return item.id === id; });
22096
+ if (!command || !guiCommandAvailable(command) || typeof command.run !== 'function') return false;
22097
+ try {
22098
+ var result = command.run(options || {});
22099
+ if (result && typeof result.catch === 'function') result.catch(function(error){ showUiNotice(error && error.message ? error.message : String(error), 'error', 'gui-command-' + id); });
22100
+ return true;
22101
+ } catch(error) {
22102
+ showUiNotice(error && error.message ? error.message : String(error), 'error', 'gui-command-' + id);
22103
+ return false;
22104
+ }
22105
+ };
22106
+
22107
+ window.executeCommandSurfaceIndex = function(index) {
22108
+ var command = guiCommandSurfaceMatches[index];
22109
+ if (!command || !guiCommandAvailable(command)) return false;
22110
+ var restore = window.closeCommandSurface({ restoreFocus:false });
22111
+ setTimeout(function(){
22112
+ window.executeGuiCommand(command.id, { source:'commandSurface' });
22113
+ requestAnimationFrame(function() {
22114
+ if (!guiSubWindowOpen() && !window.commandSurfaceIsOpen() && restore && restore.isConnected && typeof restore.focus === 'function') restore.focus({ preventScroll:true });
22115
+ });
22116
+ }, 0);
22117
+ return true;
22118
+ };
22119
+
22120
+ function guiVisible(element) {
22121
+ return !!(element && element.isConnected && element.getClientRects().length && !element.closest('[inert]'));
22122
+ }
22123
+
22124
+ function guiRegionTargets() {
22125
+ var activeTerminalInput = document.querySelector('#terminal-body .terminal-pane.active .terminal-input') || document.getElementById('bottom-toggle-btn');
22126
+ var activeRightTab = document.querySelector('#right-tabs .tab-btn[data-tab].active') || document.querySelector('#right-tabs .tab-btn[data-tab]');
22127
+ return [
22128
+ { container:document.getElementById('left'), target:document.querySelector('#left-content .left-nav-icon') || document.querySelector('#left-thumb .lt-item') },
22129
+ { container:document.getElementById('left-secondary'), target:document.querySelector('#left-secondary button, #conversation-list button, #conversation-list [tabindex="0"]') },
22130
+ { container:document.getElementById('center'), target:els.prompt },
22131
+ { container:document.getElementById('bottom'), target:activeTerminalInput },
22132
+ { container:document.getElementById('right'), target:activeRightTab }
22133
+ ].filter(function(region){ return guiVisible(region.container) && guiVisible(region.target); });
22134
+ }
22135
+
22136
+ window.cycleGuiRegionFocus = function(direction) {
22137
+ var regions = guiRegionTargets();
22138
+ if (!regions.length) return false;
22139
+ var active = document.activeElement;
22140
+ var current = regions.findIndex(function(region){ return region.container === active || region.container.contains(active); });
22141
+ var next = current < 0 ? (direction > 0 ? 0 : regions.length - 1) : (current + direction + regions.length) % regions.length;
22142
+ regions[next].target.focus({ preventScroll:true });
22143
+ return true;
22144
+ };
22145
+
22146
+ window.validateGuiCommandRegistry = function() {
22147
+ var errors = [];
22148
+ var ids = Object.create(null);
22149
+ var bindings = Object.create(null);
22150
+ for (var i = 0; i < NEWMARK_GUI_COMMANDS.length; i++) {
22151
+ var command = NEWMARK_GUI_COMMANDS[i];
22152
+ if (!command.id || ids[command.id]) errors.push('duplicate command id: ' + command.id);
22153
+ ids[command.id] = true;
22154
+ if (typeof command.run !== 'function') errors.push('missing command handler: ' + command.id);
22155
+ for (var j = 0; j < (command.bindings || []).length; j++) {
22156
+ var binding = guiBindingRecord(command, command.bindings[j]);
22157
+ if (!binding.keys) { errors.push('empty binding: ' + command.id); continue; }
22158
+ var identity = binding.scope + '::' + binding.keys;
22159
+ if (bindings[identity]) errors.push('binding conflict: ' + binding.keys + ' (' + binding.scope + ')');
22160
+ bindings[identity] = command.id;
22161
+ if (/\bMeta\b/.test(binding.keys) || /(?:^|\+)Mod\+Alt\+/.test(binding.keys)) errors.push('reserved modifier: ' + binding.keys);
22162
+ if (/^(Alt\+F4|Alt\+Tab|Mod\+Alt\+Delete|Mod\+Shift\+Escape)$/.test(binding.keys)) errors.push('reserved shortcut: ' + binding.keys);
22163
+ }
22164
+ }
22165
+ return errors;
22166
+ };
22167
+
22168
+ function guiManifestHash(value) {
22169
+ var input = String(value || '');
22170
+ var hash = 2166136261;
22171
+ for (var i = 0; i < input.length; i++) {
22172
+ hash ^= input.charCodeAt(i);
22173
+ hash = Math.imul(hash, 16777619);
22174
+ }
22175
+ return ('00000000' + (hash >>> 0).toString(16)).slice(-8);
22176
+ }
22177
+
22178
+ window.getGuiCommandManifest = function() {
22179
+ var commands = NEWMARK_GUI_COMMANDS.map(function(command) {
22180
+ return {
22181
+ id: command.id,
22182
+ category: command.category || 'general',
22183
+ bindings: (command.bindings || []).map(function(binding) {
22184
+ var record = guiBindingRecord(command, binding);
22185
+ return { keys: record.keys, scope: record.scope };
22186
+ })
22187
+ };
22188
+ });
22189
+ var serialized = JSON.stringify(commands);
22190
+ return {
22191
+ schemaVersion: 1,
22192
+ revision: 'fnv1a-' + guiManifestHash(serialized),
22193
+ commands: commands,
22194
+ errors: window.validateGuiCommandRegistry()
22195
+ };
22196
+ };
22197
+
22198
+ function guiHandleCommandKeydown(event) {
22199
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229) return false;
22200
+ if (window.commandSurfaceIsOpen()) {
22201
+ if (event.key === 'Escape' && !event.repeat) { event.preventDefault(); event.stopPropagation(); window.closeCommandSurface(); return true; }
22202
+ if (event.key === 'Tab') return trapNewmarkDialogFocus(event, document.getElementById('command-surface'));
22203
+ return false;
22204
+ }
22205
+ if (event.repeat) return false;
22206
+ var segment = guiEventSegment(event);
22207
+ if (!segment) return false;
22208
+ if (guiPendingChord) {
22209
+ var match = guiPendingChord.candidates.find(function(item){ return item.binding.keys.split(' ')[1] === segment; });
22210
+ guiClearChord();
22211
+ event.preventDefault();
22212
+ event.stopPropagation();
22213
+ if (match) window.executeGuiCommand(match.command.id, { source:'shortcut' });
22214
+ return true;
22215
+ }
22216
+ var context = guiKeyboardContext(event);
22217
+ var direct = [];
22218
+ var chords = [];
22219
+ for (var i = 0; i < NEWMARK_GUI_COMMANDS.length; i++) {
22220
+ var command = NEWMARK_GUI_COMMANDS[i];
22221
+ if (!guiCommandAvailable(command)) continue;
22222
+ for (var j = 0; j < (command.bindings || []).length; j++) {
22223
+ var binding = guiBindingRecord(command, command.bindings[j]);
22224
+ if (!guiBindingScopeMatches(binding.scope, context)) continue;
22225
+ var pieces = binding.keys.split(' ');
22226
+ if (pieces[0] !== segment) continue;
22227
+ if (pieces.length === 1) direct.push({ command:command, binding:binding });
22228
+ else if (pieces.length === 2) chords.push({ command:command, binding:binding });
22229
+ }
22230
+ }
22231
+ if (direct.length) {
22232
+ event.preventDefault();
22233
+ event.stopPropagation();
22234
+ window.executeGuiCommand(direct[0].command.id, { source:'shortcut' });
22235
+ return true;
22236
+ }
22237
+ if (chords.length) {
22238
+ event.preventDefault();
22239
+ event.stopPropagation();
22240
+ guiShowChord(segment, chords);
22241
+ return true;
22242
+ }
22243
+ return false;
22244
+ }
22245
+
22246
+ function setupGuiKeyboard() {
22247
+ if (state._guiKeyboardReady) return;
22248
+ state._guiKeyboardReady = true;
22249
+ var errors = window.validateGuiCommandRegistry();
22250
+ if (errors.length) console.error('[Keyboard] command registry conflicts:', errors);
22251
+ var search = document.getElementById('command-search');
22252
+ if (search) {
22253
+ search.addEventListener('input', function(){ guiCommandSurfaceSelection = 0; window.renderCommandSurface(); });
22254
+ search.addEventListener('keydown', function(event){
22255
+ if (event.isComposing || event.key === 'Process' || event.keyCode === 229) return;
22256
+ if (event.key === 'ArrowDown') { event.preventDefault(); guiMoveCommandSelection(1); }
22257
+ else if (event.key === 'ArrowUp') { event.preventDefault(); guiMoveCommandSelection(-1); }
22258
+ else if (event.key === 'Home' && !event.ctrlKey && !event.metaKey) { event.preventDefault(); guiMoveCommandSelection(0,'start'); }
22259
+ else if (event.key === 'End' && !event.ctrlKey && !event.metaKey) { event.preventDefault(); guiMoveCommandSelection(0,'end'); }
22260
+ else if (event.key === 'Enter') { event.preventDefault(); window.executeCommandSurfaceIndex(guiCommandSurfaceSelection); }
22261
+ });
22262
+ }
22263
+ document.addEventListener('keydown', guiHandleCommandKeydown, true);
22264
+ if (api.onKeyboardCommand) api.onKeyboardCommand(function(payload){ if (payload && payload.id) window.executeGuiCommand(String(payload.id), { source:'browserGuest' }); });
22265
+ var attributes = [
22266
+ ['#left-collapse-btn','view.toggleLeft'],
22267
+ ['#bottom-toggle-btn','view.toggleTerminal'],
22268
+ ['.sub-win-close','common.close']
22269
+ ];
22270
+ for (var i = 0; i < attributes.length; i++) {
22271
+ var element = document.querySelector(attributes[i][0]);
22272
+ var command = NEWMARK_GUI_COMMANDS.find(function(item){ return item.id === attributes[i][1]; });
22273
+ if (!element || !command || !command.bindings.length) continue;
22274
+ var direct = guiBindingRecord(command,command.bindings[0]).keys;
22275
+ if (direct.indexOf(' ') < 0) element.setAttribute('aria-keyshortcuts', direct.replace(/\bMod\b/g, guiIsMac() ? 'Meta' : 'Control'));
22276
+ }
22277
+ var manifest = window.getGuiCommandManifest();
22278
+ document.documentElement.dataset.keyboardRegistry = manifest.errors.length ? 'invalid' : manifest.revision;
22279
+ }
22280
+
19891
22281
  function schedulePostStartupUiRendering() {
19892
22282
  if (state._postStartupUiRendering && state._postStartupUiRendering.cancel) state._postStartupUiRendering.cancel();
19893
22283
  var tasks = [
@@ -19936,8 +22326,33 @@ function schedulePostStartupUiRendering() {
19936
22326
  (function initResize() {
19937
22327
  var handles = document.querySelectorAll('.resize-handle');
19938
22328
  for (var i = 0; i < handles.length; i++) {
22329
+ var resizeTarget = handles[i].getAttribute('data-target');
22330
+ if (resizeTarget === 'left') { handles[i].setAttribute('aria-valuemin', '220'); handles[i].setAttribute('aria-valuemax', '460'); }
22331
+ if (resizeTarget === 'right') { handles[i].setAttribute('aria-valuemin', '340'); handles[i].setAttribute('aria-valuemax', '680'); }
22332
+ handles[i].addEventListener('keydown', function(e) {
22333
+ if (!e || e.defaultPrevented || e.isComposing || e.key === 'Process' || e.keyCode === 229 || e.ctrlKey || e.metaKey || e.altKey) return;
22334
+ var target = this.getAttribute('data-target');
22335
+ var side = this.getAttribute('data-side');
22336
+ var el = target === 'left' ? (els.left || document.getElementById('left')) : (target === 'right' ? (els.right || document.getElementById('right')) : null);
22337
+ if (!el || (target === 'left' && state.leftCollapsed) || (target === 'right' && state.rightCollapsed)) return;
22338
+ var min = target === 'left' ? 220 : 340;
22339
+ var max = target === 'left' ? 460 : 680;
22340
+ var size = el.offsetWidth;
22341
+ var delta = e.shiftKey ? 32 : 12;
22342
+ if (e.key === 'Home') size = min;
22343
+ else if (e.key === 'End') size = max;
22344
+ else if (e.key === 'ArrowLeft') size += side === 'left' ? delta : -delta;
22345
+ else if (e.key === 'ArrowRight') size += side === 'left' ? -delta : delta;
22346
+ else return;
22347
+ e.preventDefault();
22348
+ size = Math.max(min, Math.min(max, size));
22349
+ if (target === 'left') { setLeftWidthPx(size); state.leftWidth = size; }
22350
+ else window.setRightWidthPx(size);
22351
+ this.setAttribute('aria-valuenow', String(Math.round(size)));
22352
+ });
19939
22353
  handles[i].addEventListener('mousedown', function(e) {
19940
22354
  e.preventDefault();
22355
+ var handle = this;
19941
22356
  var target = this.getAttribute('data-target');
19942
22357
  var side = this.getAttribute('data-side');
19943
22358
  var el = target === 'left' ? els.left : (target === 'right' ? els.right : null);
@@ -19957,10 +22372,12 @@ function schedulePostStartupUiRendering() {
19957
22372
  var leftSize = Math.max(220, Math.min(460, newSize));
19958
22373
  setLeftWidthPx(leftSize);
19959
22374
  state.leftWidth = leftSize;
22375
+ handle.setAttribute('aria-valuenow', String(Math.round(leftSize)));
19960
22376
  } else if (side === 'left') {
19961
22377
  var newSize2 = startSize - dx;
19962
22378
  var rightSize = Math.max(340, Math.min(680, newSize2));
19963
22379
  window.setRightWidthPx(rightSize);
22380
+ handle.setAttribute('aria-valuenow', String(Math.round(rightSize)));
19964
22381
  } else if (side === 'top') {
19965
22382
  var newSize3 = startSize - dy;
19966
22383
  el.style.height = Math.max(0, Math.min(400, newSize3)) + 'px';
@@ -20059,6 +22476,10 @@ function schedulePostStartupUiRendering() {
20059
22476
  var startupHydrationError = null;
20060
22477
  cacheEls();
20061
22478
  setupAgentWorkEvents();
22479
+ if (api.onEditorCompletionDelta && !state._editorCompletionDeltaReady) {
22480
+ state._editorCompletionDeltaReady = true;
22481
+ api.onEditorCompletionDelta(function(payload) { window.applyEditorCompletionDelta(payload); });
22482
+ }
20062
22483
  if (api.onBrowserEnsureGuest) {
20063
22484
  api.onBrowserEnsureGuest(function(target) {
20064
22485
  cancelBrowserGuestIdleDestroy();
@@ -20171,6 +22592,7 @@ function schedulePostStartupUiRendering() {
20171
22592
  startupHydrationError = e;
20172
22593
  }
20173
22594
  applyUiAppearance();
22595
+ setupGuiKeyboard();
20174
22596
  window.renderContextWindow();
20175
22597
  if (api.onAutomationUpdated) {
20176
22598
  api.onAutomationUpdated(function() {
@@ -20194,6 +22616,11 @@ function schedulePostStartupUiRendering() {
20194
22616
  // navigation surfaces render after promotion on cancellable browser tasks.
20195
22617
  updateWorkspaceGate();
20196
22618
 
22619
+ if (document.documentElement.classList.contains('config-reloading')) {
22620
+ document.documentElement.classList.remove('config-reloading');
22621
+ try { sessionStorage.removeItem('newmark-config-reloading'); } catch (_) {}
22622
+ }
22623
+
20197
22624
  // === Event Listeners ===
20198
22625
  function escapeBelongsToFocusedControl(event) {
20199
22626
  var target = event && event.target;
@@ -20216,12 +22643,12 @@ function schedulePostStartupUiRendering() {
20216
22643
 
20217
22644
  function stopRunningFromEscape(event) {
20218
22645
  if (!event || event.key !== 'Escape' || event.defaultPrevented || escapeBelongsToFocusedControl(event)) return false;
20219
- if (currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
22646
+ if (currentFlowRunning() && flowTakeoverMatchesCurrent()) {
20220
22647
  event.preventDefault();
20221
22648
  window.stopFlowRun();
20222
22649
  return true;
20223
22650
  }
20224
- if (isCurrentConversationRunning() && !promptHasText()) {
22651
+ if (isCurrentConversationRunning()) {
20225
22652
  event.preventDefault();
20226
22653
  window.stopCurrentConversation();
20227
22654
  return true;
@@ -20234,22 +22661,30 @@ function schedulePostStartupUiRendering() {
20234
22661
  // after focus moved to the chat, title bar, button, or another non-editor
20235
22662
  // surface. Modal/editor/select Escape behavior remains owned by that UI.
20236
22663
  document.addEventListener('keydown', function(event) {
22664
+ if (!event || event.defaultPrevented || event.repeat || event.isComposing || event.key === 'Process' || event.keyCode === 229) return;
22665
+ if (event.key === 'Escape' && !event.defaultPrevented && els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open')) {
22666
+ event.preventDefault();
22667
+ event.stopPropagation();
22668
+ window.closeSubWin();
22669
+ return;
22670
+ }
20237
22671
  if (stopRunningFromEscape(event)) event.stopPropagation();
20238
22672
  });
20239
22673
 
20240
22674
  if (els.prompt) {
20241
22675
  els.prompt.addEventListener('keydown', function(e) {
20242
- if (e.key === 'Escape' && currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
22676
+ if (e.isComposing || e.key === 'Process' || e.keyCode === 229) return;
22677
+ if (e.key === 'Escape' && currentFlowRunning() && flowTakeoverMatchesCurrent()) {
20243
22678
  e.preventDefault();
20244
22679
  window.stopFlowRun();
20245
22680
  return;
20246
22681
  }
20247
- if (e.key === 'Escape' && isCurrentConversationRunning() && !promptHasText()) {
22682
+ if (e.key === 'Escape' && isCurrentConversationRunning()) {
20248
22683
  e.preventDefault();
20249
22684
  window.stopCurrentConversation();
20250
22685
  return;
20251
22686
  }
20252
- if (e.key === 'Enter' && e.ctrlKey) {
22687
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey) {
20253
22688
  e.preventDefault();
20254
22689
  if (state.mode === 'flow' || (currentFlowRunning() && flowTakeoverMatchesCurrent())) {
20255
22690
  window.submitCurrentAction();
@@ -20259,9 +22694,9 @@ function schedulePostStartupUiRendering() {
20259
22694
  window.sendMessage(opposite);
20260
22695
  return;
20261
22696
  }
20262
- if (e.key === 'Enter' && !e.shiftKey) {
22697
+ if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
20263
22698
  e.preventDefault();
20264
- window.submitCurrentAction();
22699
+ window.submitCurrentAction('enter');
20265
22700
  }
20266
22701
  });
20267
22702
  els.prompt.addEventListener('input', function() {
@@ -20356,10 +22791,10 @@ function schedulePostStartupUiRendering() {
20356
22791
  els['editor-textarea'].addEventListener('select', window.handleEditorCaretChange);
20357
22792
  els['editor-textarea'].addEventListener('keyup', window.handleEditorCaretChange);
20358
22793
  els['editor-textarea'].addEventListener('keydown', function(e) {
22794
+ if (e.isComposing || e.key === 'Process' || e.keyCode === 229) return;
20359
22795
  if (state.editorCompletionText && e.key === 'Tab') { e.preventDefault(); window.acceptEditorCompletion(); return; }
20360
22796
  if (e.key === 'Escape' && (state.editorCompletionText || (els['editor-completion'] && els['editor-completion'].classList.contains('open')))) { e.preventDefault(); window.dismissEditorCompletion(); return; }
20361
22797
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') { e.preventDefault(); window.saveEditor(); return; }
20362
- if ((e.ctrlKey && e.key === ' ') || (e.altKey && e.key === '\\')) { e.preventDefault(); window.requestEditorCompletion(); return; }
20363
22798
  if (state.editorVimEnabled && e.key === 'Escape') { state.editorVimMode = 'normal'; state.editorVimPending = ''; e.preventDefault(); window.renderNativeEditor(); return; }
20364
22799
  if (window.handleEditorVimKey(e)) return;
20365
22800
  });