newmark-agent 0.3.12 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) 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 +1147 -131
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +139 -5
  8. package/dist/core/agent.js +962 -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 +6 -0
  26. package/dist/core/toolPolicy.js +49 -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 +51 -5
  46. package/dist/tools/index.js +11 -2
  47. package/dist/tools/nativeTools.js +5 -1
  48. package/dist/ui/index.html +2530 -205
  49. package/dist/ui/lucide-sprite.svg +26 -0
  50. package/dist/wsl-agent-host.bundle.cjs +1147 -131
  51. package/dist/wsl-agent-host.js +3 -0
  52. package/package.json +4 -2
@@ -136,6 +136,21 @@ try {
136
136
  --duration-normal: 250ms;
137
137
  --duration-slow: 400ms;
138
138
 
139
+ /* Stable Newmark semantic layers. Components depend on meaning, not on an
140
+ upstream developer-preview framework's private tokens or brand palette. */
141
+ --nm-surface-canvas: var(--app-bg);
142
+ --nm-surface-sunken: var(--glass-bg-1);
143
+ --nm-surface-raised: var(--glass-bg-2);
144
+ --nm-surface-overlay: var(--modal-surface);
145
+ --nm-label-primary: var(--text-bright);
146
+ --nm-label-secondary: var(--text);
147
+ --nm-label-tertiary: var(--text-dim);
148
+ --nm-state-info: var(--accent);
149
+ --nm-state-success: var(--accent2);
150
+ --nm-state-warning: #f4c95d;
151
+ --nm-state-danger: #ff7785;
152
+ --nm-focus-ring: 0 0 0 3px var(--accent-glow);
153
+
139
154
  /* Layout sizes */
140
155
  --left-width: 200px;
141
156
  --left-secondary-width: 220px;
@@ -338,6 +353,19 @@ html, body {
338
353
  border: 0;
339
354
  }
340
355
  button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus { outline: none; }
356
+ :where(button, select, input, textarea, [contenteditable], [tabindex]):focus-visible {
357
+ outline: 2px solid var(--accent);
358
+ outline-offset: 2px;
359
+ }
360
+
361
+ @media (prefers-reduced-motion: reduce) {
362
+ *, *::before, *::after {
363
+ scroll-behavior: auto !important;
364
+ animation-duration: 0.01ms !important;
365
+ animation-iteration-count: 1 !important;
366
+ transition-duration: 0.01ms !important;
367
+ }
368
+ }
341
369
  ::-webkit-scrollbar { width: 4px; height: 4px; }
342
370
  ::-webkit-scrollbar-track { background: transparent; }
343
371
  ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.10); border-radius: var(--radius-full); }
@@ -527,6 +555,24 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
527
555
  flex-shrink: 0;
528
556
  }
529
557
 
558
+ button.lt-item,
559
+ button.left-nav-icon {
560
+ border: 0;
561
+ background: transparent;
562
+ font-family: var(--font-ui);
563
+ text-align: left;
564
+ }
565
+
566
+ button.left-nav-icon { width: calc(100% - 12px); }
567
+
568
+ .lt-item:focus-visible,
569
+ .left-nav-icon:focus-visible {
570
+ outline: none;
571
+ color: var(--text-bright);
572
+ background: var(--control-hover-bg);
573
+ box-shadow: inset 0 0 0 2px var(--accent);
574
+ }
575
+
530
576
  .lt-item:hover {
531
577
  background: var(--glass-bg-2);
532
578
  color: var(--text);
@@ -749,6 +795,14 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
749
795
  overflow: hidden;
750
796
  }
751
797
 
798
+ button.left-ws-item {
799
+ width: 100%;
800
+ border: 0;
801
+ background: transparent;
802
+ font-family: var(--font-ui);
803
+ text-align: left;
804
+ }
805
+
752
806
  .left-ws-item:hover { background: var(--glass-bg-2); }
753
807
  .left-ws-item.active { background: var(--accent-glow); color: var(--text-bright); }
754
808
 
@@ -1055,6 +1109,11 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
1055
1109
  }
1056
1110
 
1057
1111
  .resize-handle:hover { background: var(--accent); }
1112
+ .resize-handle:focus-visible {
1113
+ outline: none;
1114
+ background: var(--accent);
1115
+ box-shadow: 0 0 0 2px var(--accent-glow);
1116
+ }
1058
1117
 
1059
1118
  .resize-handle[data-side="left"] {
1060
1119
  cursor: col-resize;
@@ -1883,12 +1942,12 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
1883
1942
 
1884
1943
  .terminal-output {
1885
1944
  flex: 1;
1886
- overflow-y: auto;
1945
+ overflow: auto;
1887
1946
  padding: 8px 12px;
1888
1947
  font-family: var(--font-mono);
1889
1948
  font-size: 12px;
1890
1949
  color: var(--text-dim);
1891
- white-space: pre-wrap;
1950
+ white-space: pre;
1892
1951
  line-height: 1.5;
1893
1952
  }
1894
1953
 
@@ -2419,6 +2478,8 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2419
2478
  .conversation-work-subagent-chip.completed .conversation-work-subagent-status { color: var(--accent2); }
2420
2479
  .conversation-work-command-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2421
2480
  .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; }
2481
+ .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; }
2482
+ .conversation-work-thought-pending { color: var(--text-dim); font-style: italic; }
2422
2483
  .conversation-work-files { margin-top: 1px; }
2423
2484
  .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
2485
  .conversation-work-files > summary::-webkit-details-marker,
@@ -2524,7 +2585,7 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2524
2585
  .shell-block {
2525
2586
  margin: 8px 0;
2526
2587
  border-radius: var(--radius-md);
2527
- background: rgba(0,0,0,0.3);
2588
+ background: var(--nm-surface-sunken);
2528
2589
  border: 1px solid var(--glass-border-1);
2529
2590
  overflow: hidden;
2530
2591
  }
@@ -2550,7 +2611,7 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2550
2611
  font-family: var(--font-mono);
2551
2612
  font-size: 12px;
2552
2613
  color: var(--text-dim);
2553
- white-space: pre-wrap;
2614
+ white-space: pre;
2554
2615
  line-height: 1.5;
2555
2616
  max-height: 400px;
2556
2617
  overflow: auto;
@@ -2583,6 +2644,23 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2583
2644
  .diff-block-header .arrow { transition: transform var(--duration-fast) var(--ease-out-expo); font-size: 9px; }
2584
2645
  .diff-block.collapsed .diff-block-header .arrow { transform: rotate(-90deg); }
2585
2646
 
2647
+ button.shell-block-header,
2648
+ button.diff-block-header,
2649
+ button.flow-item-header,
2650
+ button.work-review-head,
2651
+ button.todo-item {
2652
+ width: 100%;
2653
+ border: 0;
2654
+ font-family: var(--font-ui);
2655
+ text-align: left;
2656
+ }
2657
+
2658
+ button.shell-block-header { background: rgba(255,255,255,0.03); }
2659
+ button.diff-block-header,
2660
+ button.flow-item-header,
2661
+ button.work-review-head,
2662
+ button.todo-item { background: transparent; }
2663
+
2586
2664
  .diff-stat { display: flex; gap: 8px; margin-left: auto; }
2587
2665
  .diff-add { color: var(--accent2); font-size: 11px; }
2588
2666
  .diff-del { color: #ff6666; font-size: 11px; }
@@ -2958,6 +3036,8 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
2958
3036
  .work-review-actions { display:flex; align-items:center; gap:5px; }
2959
3037
  .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
3038
  .work-review-btn:hover { background:var(--control-hover-bg); border-color:var(--border-hover); }
3039
+ .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; }
3040
+ .work-review-toggle:hover { background:var(--control-hover-bg); border-color:var(--border-hover); }
2961
3041
  .work-review-list { border-top:1px solid var(--glass-border-1); }
2962
3042
  .work-review.collapsed .work-review-list { display: none; }
2963
3043
  .work-review-head { cursor: pointer; }
@@ -3057,6 +3137,16 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3057
3137
  font: 9px var(--font-mono);
3058
3138
  }
3059
3139
 
3140
+ .conv-branch-comm-badge {
3141
+ flex: 0 0 auto;
3142
+ padding: 2px 5px;
3143
+ border-radius: 999px;
3144
+ background: color-mix(in srgb, var(--accent) 16%, transparent);
3145
+ color: var(--accent);
3146
+ font: 9px var(--font-mono);
3147
+ white-space: nowrap;
3148
+ }
3149
+
3060
3150
  .conv-runtime-badge.running { color: var(--accent2); }
3061
3151
  .conv-runtime-badge.stopping,
3062
3152
  .conv-runtime-badge.force_restarting { color: #ffd27a; }
@@ -3391,9 +3481,47 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3391
3481
  flex: 0 0 auto;
3392
3482
  }
3393
3483
 
3484
+ #context-inspector {
3485
+ position: absolute;
3486
+ right: -8px;
3487
+ bottom: calc(100% + 10px);
3488
+ z-index: 80;
3489
+ display: none;
3490
+ width: min(320px, calc(100vw - 24px));
3491
+ padding: 12px;
3492
+ border: 1px solid var(--glass-border-2);
3493
+ border-radius: var(--radius-lg);
3494
+ background: var(--modal-surface);
3495
+ box-shadow: var(--shadow-lg);
3496
+ backdrop-filter: blur(var(--glass-blur-3)) saturate(1.08);
3497
+ color: var(--text);
3498
+ font-size: 10px;
3499
+ line-height: 1.45;
3500
+ }
3501
+ #context-inspector.open { display: block; animation: model-menu-in 150ms var(--ease-out-expo); }
3502
+ .context-inspector-head { display:flex; align-items:flex-start; gap:8px; margin-bottom:10px; }
3503
+ .context-inspector-title { flex:1; min-width:0; color:var(--text-bright); font-size:12px; font-weight:700; }
3504
+ .context-inspector-subtitle { margin-top:2px; color:var(--text-dim); font-size:9px; }
3505
+ .context-inspector-close { width:22px; height:22px; padding:0; border:0; border-radius:var(--radius-sm); background:transparent; color:var(--text-dim); cursor:pointer; }
3506
+ .context-inspector-close:hover, .context-inspector-close:focus-visible { color:var(--text-bright); background:var(--control-hover-bg); outline:none; }
3507
+ .context-inspector-meter { height:6px; margin:8px 0 10px; overflow:hidden; border-radius:var(--radius-full); background:rgba(168,168,168,.18); }
3508
+ .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); }
3509
+ .context-inspector-grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
3510
+ .context-inspector-cell { padding:7px 8px; border:1px solid var(--glass-border-1); border-radius:var(--radius-sm); background:var(--glass-bg-1); }
3511
+ .context-inspector-label { display:block; color:var(--text-dim); font-size:9px; }
3512
+ .context-inspector-value { display:block; margin-top:2px; color:var(--text-bright); font:600 10px var(--font-mono); }
3513
+ .context-inspector-section { margin-top:10px; padding-top:9px; border-top:1px solid var(--glass-border-1); }
3514
+ .context-inspector-section-title { color:var(--text-bright); font-size:10px; font-weight:650; }
3515
+ .context-inspector-meta { margin-top:3px; color:var(--text-dim); overflow-wrap:anywhere; }
3516
+ .context-inspector-actions { display:flex; align-items:center; gap:7px; margin-top:10px; }
3517
+ .context-inspector-actions .sec-btn { flex:1; min-width:0; }
3518
+ .context-inspector-actions .sec-btn:disabled { opacity:.55; cursor:wait; }
3519
+
3394
3520
  #context-token-ring {
3395
3521
  width: 16px;
3396
3522
  height: 16px;
3523
+ padding: 0;
3524
+ border: 0;
3397
3525
  border-radius: 50%;
3398
3526
  background: conic-gradient(#a8a8a8 0deg, rgba(168,168,168,0.22) 0deg);
3399
3527
  cursor: default;
@@ -3601,6 +3729,124 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3601
3729
  50% { box-shadow: 0 0 24px rgba(91,120,255,0.5), 0 0 60px rgba(91,120,255,0.15); }
3602
3730
  }
3603
3731
 
3732
+ /* ============================================================
3733
+ KEYBOARD COMMAND SURFACE
3734
+ ============================================================ */
3735
+ .command-surface-overlay {
3736
+ position: fixed;
3737
+ inset: 0;
3738
+ z-index: 320;
3739
+ display: flex;
3740
+ align-items: flex-start;
3741
+ justify-content: center;
3742
+ padding: min(12vh, 96px) 18px 18px;
3743
+ background: color-mix(in srgb, var(--nm-surface-canvas) 62%, transparent);
3744
+ opacity: 0;
3745
+ visibility: hidden;
3746
+ pointer-events: none;
3747
+ transition: opacity var(--duration-fast) var(--ease-out-expo), visibility 0ms linear var(--duration-fast);
3748
+ }
3749
+
3750
+ .command-surface-overlay.open {
3751
+ opacity: 1;
3752
+ visibility: visible;
3753
+ pointer-events: auto;
3754
+ transition-delay: 0ms, 0ms;
3755
+ }
3756
+
3757
+ .command-surface {
3758
+ width: min(680px, calc(100vw - 36px));
3759
+ max-height: min(680px, calc(100vh - 132px));
3760
+ display: flex;
3761
+ flex-direction: column;
3762
+ overflow: hidden;
3763
+ border: 1px solid var(--glass-border-2);
3764
+ border-radius: var(--radius-xl);
3765
+ background: var(--nm-surface-overlay);
3766
+ box-shadow: var(--shadow-lg);
3767
+ backdrop-filter: blur(var(--glass-blur-3)) saturate(150%);
3768
+ transform: translateY(-8px) scale(0.985);
3769
+ transition: transform var(--duration-fast) var(--ease-out-expo);
3770
+ }
3771
+
3772
+ .command-surface-overlay.open .command-surface { transform: translateY(0) scale(1); }
3773
+ .command-surface-header { display:flex; align-items:flex-start; gap:12px; padding:14px 16px 10px; }
3774
+ .command-surface-heading { flex:1; min-width:0; }
3775
+ .command-surface-title { color:var(--text-bright); font-size:14px; font-weight:700; }
3776
+ .command-surface-subtitle { margin-top:3px; color:var(--text-dim); font-size:10px; line-height:1.45; }
3777
+ .command-surface-close { width:28px; height:28px; border:0; border-radius:var(--radius-sm); background:transparent; color:var(--text-dim); cursor:pointer; }
3778
+ .command-surface-close:hover { color:var(--text-bright); background:var(--control-hover-bg); }
3779
+ .command-search-wrap { padding:0 14px 12px; }
3780
+ .command-search {
3781
+ width:100%;
3782
+ height:40px;
3783
+ box-sizing:border-box;
3784
+ padding:0 12px;
3785
+ border:1px solid var(--glass-border-2);
3786
+ border-radius:var(--radius-md);
3787
+ background:var(--control-bg);
3788
+ color:var(--text);
3789
+ font:12px var(--font-ui);
3790
+ }
3791
+ .command-search:focus { border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow); }
3792
+ .command-list { min-height:96px; overflow:auto; padding:0 8px 8px; }
3793
+ .command-category { padding:9px 9px 5px; color:var(--text-dim); font-size:9px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; }
3794
+ .command-option {
3795
+ width:100%;
3796
+ min-height:42px;
3797
+ display:grid;
3798
+ grid-template-columns:minmax(0,1fr) auto;
3799
+ align-items:center;
3800
+ gap:12px;
3801
+ padding:6px 9px;
3802
+ border:1px solid transparent;
3803
+ border-radius:var(--radius-md);
3804
+ background:transparent;
3805
+ color:var(--text);
3806
+ text-align:left;
3807
+ cursor:pointer;
3808
+ font-family:var(--font-ui);
3809
+ }
3810
+ .command-option.active,
3811
+ .command-option:hover { background:var(--control-hover-bg); border-color:var(--glass-border-1); }
3812
+ .command-option[aria-disabled="true"] { opacity:.48; cursor:not-allowed; }
3813
+ .command-option-title { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }
3814
+ .command-option-context { display:block; margin-top:2px; color:var(--text-dim); font-size:9px; }
3815
+ .command-shortcuts { display:flex; justify-content:flex-end; gap:4px; }
3816
+ .command-key {
3817
+ min-width:22px;
3818
+ padding:3px 6px;
3819
+ border:1px solid var(--glass-border-2);
3820
+ border-bottom-color:var(--border-hover);
3821
+ border-radius:5px;
3822
+ background:var(--glass-bg-1);
3823
+ color:var(--text-dim);
3824
+ font:10px var(--font-mono);
3825
+ text-align:center;
3826
+ white-space:nowrap;
3827
+ }
3828
+ .command-empty { padding:24px 12px; color:var(--text-dim); font-size:11px; text-align:center; }
3829
+ .command-surface-footer { padding:8px 14px 10px; border-top:1px solid var(--glass-border-1); color:var(--text-dim); font-size:9px; }
3830
+ .shortcut-chord-hint {
3831
+ position:fixed;
3832
+ z-index:330;
3833
+ left:50%;
3834
+ bottom:22px;
3835
+ max-width:min(520px, calc(100vw - 32px));
3836
+ padding:7px 10px;
3837
+ border:1px solid var(--glass-border-2);
3838
+ border-radius:var(--radius-md);
3839
+ background:var(--modal-surface);
3840
+ color:var(--text);
3841
+ box-shadow:var(--shadow-md);
3842
+ font:10px var(--font-mono);
3843
+ opacity:0;
3844
+ visibility:hidden;
3845
+ transform:translate(-50%, 4px);
3846
+ transition:opacity var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-expo), visibility 0ms linear var(--duration-fast);
3847
+ }
3848
+ .shortcut-chord-hint.open { opacity:1; visibility:visible; transform:translate(-50%, 0); transition-delay:0ms; }
3849
+
3604
3850
  /* ============================================================
3605
3851
  SUB-WINDOW SYSTEM
3606
3852
  ============================================================ */
@@ -3702,6 +3948,7 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3702
3948
  }
3703
3949
 
3704
3950
  .sub-win-close:hover { background: rgba(255,68,68,0.15); color: #ff6666; }
3951
+ .sub-win-close:focus-visible { outline:none; color:#ff7777; box-shadow:inset 0 0 0 2px currentColor; }
3705
3952
 
3706
3953
  .sub-win-body {
3707
3954
  flex: 1;
@@ -3734,6 +3981,81 @@ button:focus, select:focus, input:focus, textarea:focus, [contenteditable]:focus
3734
3981
 
3735
3982
  .stab-btn:hover { background: rgba(255,255,255,0.05); color: var(--text); }
3736
3983
  .stab-btn.active { background: rgba(91,120,255,0.12); color: var(--accent); border-color: rgba(91,120,255,0.25); }
3984
+ .stab-btn:focus-visible { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow); color:var(--text-bright); }
3985
+
3986
+ .plugin-tabs { overflow-x:auto; scrollbar-width:thin; }
3987
+ .plugin-tabs .stab-btn { flex:0 0 auto; }
3988
+ .plugin-panel { min-height:180px; margin-top:10px; }
3989
+ .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; }
3990
+ .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); }
3991
+ .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); }
3992
+ .plugin-panel-state.error { border-color:var(--notice-error-border); color:var(--notice-error-text); }
3993
+ .plugin-panel-state .sec-btn { flex:0 0 auto; margin-left:auto; }
3994
+ .plugin-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:12px; }
3995
+ .plugin-toolbar-copy { flex:1; min-width:0; }
3996
+ .plugin-toolbar-title { color:var(--text-bright); font-size:13px; font-weight:650; }
3997
+ .plugin-toolbar-meta { margin-top:2px; color:var(--text-dim); font-size:10px; }
3998
+ .plugin-toolbar .sec-btn { flex:0 0 auto; }
3999
+ .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); }
4000
+ .plugin-search:focus { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-glow); }
4001
+ .plugin-section { margin-top:14px; }
4002
+ .plugin-section-head { display:flex; align-items:center; gap:8px; margin-bottom:8px; }
4003
+ .plugin-section-title { color:var(--text-bright); font-size:12px; font-weight:650; }
4004
+ .plugin-count-badge,
4005
+ .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; }
4006
+ .plugin-status-badge.enabled { border-color:color-mix(in srgb, var(--accent2) 45%, transparent); color:var(--accent2); }
4007
+ .plugin-status-badge.preview { border-color:color-mix(in srgb, var(--accent) 45%, transparent); color:var(--text-accent); }
4008
+ .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; }
4009
+ .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); }
4010
+ .mcp-form-head { display:flex; align-items:flex-start; gap:10px; margin-bottom:12px; }
4011
+ .mcp-form-copy { flex:1; min-width:0; }
4012
+ .mcp-form-title { color:var(--text-bright); font-size:13px; font-weight:650; }
4013
+ .mcp-form-subtitle { margin-top:2px; color:var(--text-dim); font-size:10px; line-height:1.45; }
4014
+ .mcp-field-grid { display:grid; grid-template-columns:minmax(0,1fr) 130px; gap:10px; }
4015
+ .mcp-field { display:flex; flex-direction:column; gap:5px; margin-bottom:10px; }
4016
+ .mcp-field label { color:var(--text); font-size:10px; font-weight:600; }
4017
+ .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); }
4018
+ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mono); line-height:1.5; }
4019
+ .mcp-input:focus { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-glow); }
4020
+ .mcp-field-help { margin-top:-1px; color:var(--text-dim); font-size:9px; line-height:1.45; word-break:break-word; }
4021
+ .mcp-enabled-check { display:inline-flex; align-items:center; gap:7px; color:var(--text); font-size:10px; cursor:pointer; }
4022
+ .mcp-enabled-check input { accent-color:var(--accent); }
4023
+ .mcp-form-actions { display:flex; align-items:center; justify-content:flex-end; gap:8px; margin-top:4px; }
4024
+ .mcp-form-actions .sec-btn { flex:0 0 auto; }
4025
+ .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); }
4026
+ .mcp-server-copy { flex:1; min-width:0; }
4027
+ .mcp-server-title { display:flex; align-items:center; gap:6px; flex-wrap:wrap; color:var(--text-bright); font-size:12px; font-weight:650; }
4028
+ .mcp-server-meta { margin-top:4px; color:var(--text-dim); font:10px/1.45 var(--font-mono); overflow-wrap:anywhere; }
4029
+ .mcp-row-actions { display:flex; align-items:center; gap:5px; flex-wrap:wrap; justify-content:flex-end; }
4030
+ .mcp-row-actions .sec-btn { flex:0 0 auto; }
4031
+ .dsh-hero { padding:14px; border:1px solid var(--glass-border-2); border-radius:var(--radius-md); background:var(--glass-bg-1); }
4032
+ .dsh-hero-head { display:flex; align-items:flex-start; gap:10px; }
4033
+ .dsh-hero-copy { flex:1; min-width:0; }
4034
+ .dsh-title { color:var(--text-bright); font-size:14px; font-weight:700; }
4035
+ .dsh-description { margin-top:5px; color:var(--text-dim); font-size:11px; line-height:1.55; }
4036
+ .dsh-actions { display:flex; flex-wrap:wrap; gap:7px; margin-top:11px; }
4037
+ .dsh-actions .sec-btn { flex:0 0 auto; }
4038
+ .dsh-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; margin-top:10px; }
4039
+ .dsh-card { min-width:0; padding:11px; border:1px solid var(--glass-border-1); border-radius:var(--radius-md); background:var(--glass-bg-1); }
4040
+ .dsh-card-title { color:var(--text-bright); font-size:11px; font-weight:650; }
4041
+ .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; }
4042
+ .dsh-kv dt { color:var(--text-dim); }
4043
+ .dsh-kv dd { color:var(--text); overflow-wrap:anywhere; }
4044
+ .dsh-list { display:flex; flex-direction:column; gap:6px; margin-top:8px; }
4045
+ .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; }
4046
+ .dsh-list-item.warning { border-color:var(--notice-error-border); color:var(--notice-error-text); }
4047
+ .dsh-layer-profile { margin-top:10px; padding-top:9px; border-top:1px solid var(--glass-border-1); }
4048
+ .dsh-candidate-actions { display:flex; align-items:center; gap:7px; margin-top:7px; }
4049
+ .dsh-candidate-actions .sec-btn { flex:0 0 auto; }
4050
+
4051
+ @media (max-width: 560px) {
4052
+ .sub-win { width:calc(100vw - 20px); min-width:0; }
4053
+ .plugin-toolbar { align-items:stretch; flex-wrap:wrap; }
4054
+ .plugin-search { width:100%; order:3; }
4055
+ .mcp-field-grid, .dsh-grid { grid-template-columns:1fr; }
4056
+ .mcp-server-row { align-items:flex-start; flex-direction:column; }
4057
+ .mcp-row-actions { width:100%; justify-content:flex-start; }
4058
+ }
3737
4059
 
3738
4060
  .stab-panel {
3739
4061
  display: none;
@@ -4806,6 +5128,89 @@ html.newmark-memory-overview-viewer body {
4806
5128
 
4807
5129
  .hidden { display: none !important; }
4808
5130
  .no-select { user-select: none; -webkit-user-select: none; }
5131
+
5132
+ /* ============================================================
5133
+ NEWMARK CONVERSATION HIERARCHY (三级字体体系)
5134
+ 最终回复 > Build 内联历史 > 工具调用 toolcall
5135
+ 保持原有 DOM 结构不变,仅通过字体大小/行高/不透明度体现差异。
5136
+ ============================================================ */
5137
+
5138
+ /* --- Level 1: 最终回复(最大、最醒目) --- */
5139
+ .chat-msg.assistant {
5140
+ font-size: 15px;
5141
+ line-height: 1.72;
5142
+ }
5143
+
5144
+ /* Build 块内的最终回复同样以 Level 1 呈现 */
5145
+ .chat-msg.run-final-response {
5146
+ font-size: 15px;
5147
+ line-height: 1.72;
5148
+ }
5149
+
5150
+ /* 用户消息保持可读的稍大字号 */
5151
+ .chat-msg.user {
5152
+ font-size: 14px;
5153
+ line-height: 1.65;
5154
+ }
5155
+
5156
+ /* --- Level 2: Build 内联历史(中等字号) --- */
5157
+ .conversation-work-event.narrative {
5158
+ font-size: 13px;
5159
+ line-height: 1.6;
5160
+ opacity: 0.9;
5161
+ }
5162
+
5163
+ /* Build 块标题保持中等偏小,作为历史导航 */
5164
+ .conversation-work-run-head {
5165
+ font-size: 12px;
5166
+ opacity: 0.78;
5167
+ }
5168
+
5169
+ /* --- Level 3: 工具调用 toolcall(最小、最淡、等宽) --- */
5170
+ .tool-event-summary {
5171
+ font-size: 11px;
5172
+ font-family: var(--font-mono);
5173
+ opacity: 0.78;
5174
+ }
5175
+
5176
+ .tool-event-content {
5177
+ font-size: 11px;
5178
+ font-family: var(--font-mono);
5179
+ line-height: 1.45;
5180
+ opacity: 0.72;
5181
+ color: var(--text-dim);
5182
+ }
5183
+
5184
+ /* 工具调用外的活动/状态项也归入 Level 3 */
5185
+ .conversation-work-event:not(.narrative):not(.guide):not(.error) {
5186
+ font-size: 11px;
5187
+ line-height: 1.45;
5188
+ opacity: 0.72;
5189
+ font-family: var(--font-mono);
5190
+ }
5191
+
5192
+ .conversation-work-event.activity-summary {
5193
+ font-size: 11px;
5194
+ }
5195
+
5196
+ /* 工具调用活动列表项保持最小字号 */
5197
+ .conversation-work-activity-item {
5198
+ font-size: 10.5px;
5199
+ opacity: 0.68;
5200
+ }
5201
+
5202
+ /* --- 层级间距调整,强化视觉层次 --- */
5203
+ .chat-msg.assistant { margin-top: 2px; }
5204
+ .chat-msg.run-final-response { margin-bottom: 6px; }
5205
+ .conversation-work-run { margin: 4px 0 7px; }
5206
+
5207
+ /* 浅色主题下放宽不透明度,保证可读性 */
5208
+ .light .tool-event-content,
5209
+ .light .tool-event-summary,
5210
+ .light .conversation-work-event:not(.narrative):not(.guide):not(.error) {
5211
+ opacity: 0.8;
5212
+ }
5213
+
4809
5214
  </style>
4810
5215
  </head>
4811
5216
  <body>
@@ -4941,6 +5346,14 @@ html.newmark-memory-overview-viewer body {
4941
5346
  <circle cx="15" cy="5" r="1" />
4942
5347
  <circle cx="15" cy="19" r="1" />
4943
5348
  </symbol>
5349
+ <symbol id="group" viewBox="0 0 24 24">
5350
+ <path d="M3 7V5c0-1.1.9-2 2-2h2" />
5351
+ <path d="M17 3h2c1.1 0 2 .9 2 2v2" />
5352
+ <path d="M21 17v2c0 1.1-.9 2-2 2h-2" />
5353
+ <path d="M7 21H5c-1.1 0-2-.9-2-2v-2" />
5354
+ <rect width="7" height="5" x="7" y="7" rx="1" />
5355
+ <rect width="7" height="5" x="10" y="12" rx="1" />
5356
+ </symbol>
4944
5357
  <symbol id="image" viewBox="0 0 24 24">
4945
5358
  <rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
4946
5359
  <circle cx="9" cy="9" r="2" />
@@ -4968,6 +5381,14 @@ html.newmark-memory-overview-viewer body {
4968
5381
  <path d="m3 17 2 2 4-4" />
4969
5382
  <path d="m3 7 2 2 4-4" />
4970
5383
  </symbol>
5384
+ <symbol id="list" viewBox="0 0 24 24">
5385
+ <path d="M3 5h.01" />
5386
+ <path d="M3 12h.01" />
5387
+ <path d="M3 19h.01" />
5388
+ <path d="M8 5h13" />
5389
+ <path d="M8 12h13" />
5390
+ <path d="M8 19h13" />
5391
+ </symbol>
4971
5392
  <symbol id="loader-circle" viewBox="0 0 24 24">
4972
5393
  <path d="M21 12a9 9 0 1 1-6.219-8.56" />
4973
5394
  </symbol>
@@ -5008,6 +5429,9 @@ html.newmark-memory-overview-viewer body {
5008
5429
  <path d="m5 9-3 3 3 3" />
5009
5430
  <path d="m9 5 3-3 3 3" />
5010
5431
  </symbol>
5432
+ <symbol id="navigation" viewBox="0 0 24 24">
5433
+ <polygon points="3 11 22 2 13 21 11 13 3 11" />
5434
+ </symbol>
5011
5435
  <symbol id="octagon-x" viewBox="0 0 24 24">
5012
5436
  <path d="m15 9-6 6" />
5013
5437
  <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 +5444,13 @@ html.newmark-memory-overview-viewer body {
5020
5444
  <path d="M3 3h6l6 18h6" />
5021
5445
  <path d="M14 3h7" />
5022
5446
  </symbol>
5447
+ <symbol id="palette" viewBox="0 0 24 24">
5448
+ <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" />
5449
+ <circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
5450
+ <circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
5451
+ <circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
5452
+ <circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
5453
+ </symbol>
5023
5454
  <symbol id="panel-left-open" viewBox="0 0 24 24">
5024
5455
  <rect width="18" height="18" x="3" y="3" rx="2" />
5025
5456
  <path d="M9 3v18" />
@@ -5231,37 +5662,37 @@ html.newmark-memory-overview-viewer body {
5231
5662
  <!-- Left Sidebar -->
5232
5663
  <div id="left">
5233
5664
  <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>
5665
+ <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>
5666
+ <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>
5667
+ <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>
5668
+ <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>
5669
+ <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>
5670
+ <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>
5671
+ <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
5672
  </div>
5242
5673
  <div id="left-content">
5243
5674
  <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">
5675
+ <button type="button" class="left-nav-icon" onclick="window.showNewConversationPage()" title="New conversation">
5245
5676
  <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()">
5677
+ </button>
5678
+ <button type="button" class="left-nav-icon" onclick="window.showPluginList()">
5248
5679
  <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()">
5680
+ </button>
5681
+ <button type="button" class="left-nav-icon" onclick="window.showMemoryLab()">
5251
5682
  <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()">
5683
+ </button>
5684
+ <button type="button" class="left-nav-icon" onclick="window.showAutomationWindow()">
5254
5685
  <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()">
5686
+ </button>
5687
+ <button type="button" class="left-nav-icon" onclick="window.showFlowEditor()">
5257
5688
  <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()">
5689
+ </button>
5690
+ <button type="button" class="left-nav-icon" onclick="window.openSettings()">
5260
5691
  <span class="icon"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#settings"></use></svg></span><span>Settings</span>
5261
- </div>
5692
+ </button>
5262
5693
  <div id="left-ws-section">
5263
5694
  <div id="left-ws-header">Workspaces</div>
5264
- <div id="left-ws-list"></div>
5695
+ <div id="left-ws-list" role="list" aria-label="Workspaces"></div>
5265
5696
  <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
5697
  </div>
5267
5698
  </div>
@@ -5275,11 +5706,11 @@ html.newmark-memory-overview-viewer body {
5275
5706
  <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
5707
  <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
5708
  </div>
5278
- <div id="conversation-list"></div>
5709
+ <div id="conversation-list" role="list" aria-label="Conversations"></div>
5279
5710
  </div>
5280
5711
 
5281
5712
  <!-- 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>
5713
+ <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
5714
 
5284
5715
  <!-- Center Stack (chat + terminal) -->
5285
5716
  <div id="center-stack">
@@ -5305,7 +5736,7 @@ html.newmark-memory-overview-viewer body {
5305
5736
  <div id="todo-header" class="stack-row" onclick="window.toggleTodoCollapse()">
5306
5737
  <span id="todo-header-label" class="stack-title">Task</span>
5307
5738
  <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>
5739
+ <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
5740
  </span>
5310
5741
  </div>
5311
5742
  <div id="todo-list"></div>
@@ -5317,7 +5748,7 @@ html.newmark-memory-overview-viewer body {
5317
5748
  <span id="queue-header-label" class="stack-title">Next</span>
5318
5749
  <span class="stack-actions">
5319
5750
  <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>
5751
+ <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
5752
  </span>
5322
5753
  </div>
5323
5754
  <div id="queue-list"></div>
@@ -5361,8 +5792,9 @@ html.newmark-memory-overview-viewer body {
5361
5792
  <div class="model-select-menu" id="model-select-menu" role="listbox" aria-label="Model" popover="manual"></div>
5362
5793
  </div>
5363
5794
  <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>
5795
+ <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
5796
  <div id="context-token-tooltip"></div>
5797
+ <div id="context-inspector" role="dialog" aria-label="Context management" aria-live="polite"></div>
5366
5798
  </div>
5367
5799
  <select class="tool-select" id="intel-select">
5368
5800
  <option value="low">low</option>
@@ -5376,7 +5808,7 @@ html.newmark-memory-overview-viewer body {
5376
5808
  <button class="mode-toggle-btn active" data-mode="guide" onclick="window.setInputMode('guide')">Guide</button>
5377
5809
  <button class="mode-toggle-btn" data-mode="next" onclick="window.setInputMode('next')">Next</button>
5378
5810
  </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>
5811
+ <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
5812
  </div>
5381
5813
  </div>
5382
5814
  </div>
@@ -5384,7 +5816,7 @@ html.newmark-memory-overview-viewer body {
5384
5816
  <!-- Bottom Terminal -->
5385
5817
  <div id="bottom" class="open">
5386
5818
  <div id="bottom-header">
5387
- <div id="terminal-tabs"></div>
5819
+ <div id="terminal-tabs" role="tablist" aria-label="Terminals"></div>
5388
5820
  <select id="terminal-shell-select" onchange="window.spawnTerminal(this.value)">
5389
5821
  <option value="powershell" data-platform-shell="win32">PowerShell</option>
5390
5822
  <option value="cmd" data-platform-shell="win32">CMD</option>
@@ -5402,7 +5834,7 @@ html.newmark-memory-overview-viewer body {
5402
5834
  <div class="terminal-output"><span style="color:var(--text-dim);opacity:0.5;">Terminal ready</span></div>
5403
5835
  <div class="terminal-input-row">
5404
5836
  <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()">
5837
+ <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
5838
  </div>
5407
5839
  </div>
5408
5840
  </div>
@@ -5410,25 +5842,25 @@ html.newmark-memory-overview-viewer body {
5410
5842
  </div>
5411
5843
 
5412
5844
  <!-- Right Resize Handle -->
5413
- <div class="resize-handle" data-side="left" data-target="right" title="Resize right sidebar"></div>
5845
+ <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
5846
 
5415
5847
  <!-- Right Sidebar -->
5416
5848
  <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>
5849
+ <div id="right-tabs" role="tablist" aria-label="Right sidebar">
5850
+ <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>
5851
+ <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>
5852
+ <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>
5853
+ <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>
5854
+ <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
5855
  <div class="tab-divider"></div>
5424
5856
  <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
5857
  </div>
5426
5858
  <div id="right-content">
5427
- <div class="tab-panel active" id="panel-file-tree">
5859
+ <div class="tab-panel active" id="panel-file-tree" role="tabpanel" aria-labelledby="right-tab-file-tree">
5428
5860
  <div style="font-size:11px;color:var(--text-dim);margin-bottom:8px;">Workspace file tree</div>
5429
- <div id="file-tree-container"></div>
5861
+ <div id="file-tree-container" role="tree"></div>
5430
5862
  </div>
5431
- <div class="tab-panel" id="panel-editor">
5863
+ <div class="tab-panel" id="panel-editor" role="tabpanel" aria-labelledby="right-tab-editor" hidden>
5432
5864
  <div class="editor-toolbar">
5433
5865
  <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
5866
  <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 +5876,22 @@ html.newmark-memory-overview-viewer body {
5444
5876
  <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
5877
  </div>
5446
5878
  </div>
5447
- <div class="tab-panel" id="panel-plan">
5879
+ <div class="tab-panel" id="panel-plan" role="tabpanel" aria-labelledby="right-tab-plan" hidden>
5448
5880
  <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
5881
  <div id="conversation-plan-content"></div>
5450
5882
  <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
5883
  <div id="linked-plan-content" class="linked-plan-reader md-rendered" aria-live="polite"></div>
5452
5884
  </div>
5453
- <div class="tab-panel" id="panel-subagent">
5885
+ <div class="tab-panel" id="panel-subagent" role="tabpanel" aria-labelledby="right-tab-subagent" hidden>
5454
5886
  <div style="font-size:11px;color:var(--text-dim);margin-bottom:8px;">Subagents</div>
5455
5887
  <div id="subagent-list"></div>
5456
5888
  </div>
5457
- <div class="tab-panel" id="panel-browser">
5889
+ <div class="tab-panel" id="panel-browser" role="tabpanel" aria-labelledby="right-tab-browser" hidden>
5458
5890
  <div class="browser-url-bar">
5459
5891
  <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
5892
  <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
5893
  <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)">
5894
+ <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
5895
  <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
5896
  <button class="shell-btn" id="computer-use-toggle" onclick="window.toggleComputerUse()" title="ComputerUse: enabled for this conversation" aria-pressed="true">CU</button>
5465
5897
  </div>
@@ -5476,7 +5908,7 @@ html.newmark-memory-overview-viewer body {
5476
5908
 
5477
5909
  <!-- ========== SUB-WINDOW OVERLAY ========== -->
5478
5910
  <div class="sub-win-overlay" id="sub-win-overlay" onclick="if(event.target===this)window.closeSubWin()">
5479
- <div class="sub-win" id="sub-win">
5911
+ <div class="sub-win" id="sub-win" role="dialog" aria-modal="true" aria-labelledby="sub-win-title" tabindex="-1">
5480
5912
  <div class="sub-win-header" id="sub-win-header">
5481
5913
  <span class="sub-win-title" id="sub-win-title">Window</span>
5482
5914
  <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 +5916,21 @@ html.newmark-memory-overview-viewer body {
5484
5916
  <div class="sub-win-body" id="sub-win-body"></div>
5485
5917
  </div>
5486
5918
  </div>
5919
+ <div class="command-surface-overlay" id="command-surface-overlay" onclick="if(event.target===this)window.closeCommandSurface()">
5920
+ <section class="command-surface" id="command-surface" role="dialog" aria-modal="true" aria-labelledby="command-surface-title" tabindex="-1">
5921
+ <header class="command-surface-header">
5922
+ <div class="command-surface-heading">
5923
+ <div class="command-surface-title" id="command-surface-title"></div>
5924
+ <div class="command-surface-subtitle" id="command-surface-subtitle"></div>
5925
+ </div>
5926
+ <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>
5927
+ </header>
5928
+ <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>
5929
+ <div class="command-list" id="command-list" role="listbox"></div>
5930
+ <footer class="command-surface-footer" id="command-surface-footer"></footer>
5931
+ </section>
5932
+ </div>
5933
+ <div class="shortcut-chord-hint" id="shortcut-chord-hint" role="status" aria-live="polite"></div>
5487
5934
  <script>
5488
5935
  /* ============================================================
5489
5936
  NEWMARK AGENT - Complete UI Controller v2.0
@@ -5579,6 +6026,8 @@ var state = {
5579
6026
  automationItems: [],
5580
6027
  contextCompression: null,
5581
6028
  contextWindow: null,
6029
+ contextInspectorOpen: false,
6030
+ contextMutationPending: false,
5582
6031
  flowWorks: [],
5583
6032
  activeAgentRunning: false,
5584
6033
  editorPath: '',
@@ -5596,6 +6045,9 @@ var state = {
5596
6045
  editorCompletionText: '',
5597
6046
  editorCompletionRequest: 0,
5598
6047
  editorCompletionAnchor: null,
6048
+ editorCompletionCache: [],
6049
+ editorCompletionInFlight: false,
6050
+ editorCompletionStreamText: '',
5599
6051
  editorCaretSignature: '',
5600
6052
  editorPredictionEnabled: true,
5601
6053
  editorCompletionTimer: null,
@@ -5651,7 +6103,14 @@ var state = {
5651
6103
  githubOverviewPendingRepo: '',
5652
6104
  mcpServers: [],
5653
6105
  mcpDiscovered: [],
6106
+ dshCompatibility: null,
5654
6107
  mcpEditingId: '',
6108
+ mcpDraft: null,
6109
+ mcpSearchQuery: '',
6110
+ mcpMutationPending: false,
6111
+ mcpMutationGeneration: 0,
6112
+ mcpFormFocusRequested: false,
6113
+ pluginPanelGeneration: 0,
5655
6114
  skillMarketQuery: '',
5656
6115
  memoryLab: null,
5657
6116
  memoryLabComponentContents: {},
@@ -5720,6 +6179,48 @@ var NEWMARK_I18N = {
5720
6179
  en: {
5721
6180
  'app.ready': 'Ready',
5722
6181
  'app.subtitle': 'Enter an instruction to begin. Enter sends. Shift+Enter inserts a newline.',
6182
+ 'shortcuts.paletteTitle': 'Command palette',
6183
+ 'shortcuts.helpTitle': 'Keyboard shortcuts',
6184
+ 'shortcuts.paletteSubtitle': 'Search every registered Newmark GUI command. Shortcuts never replace standard editing keys.',
6185
+ 'shortcuts.helpSubtitle': 'Common desktop keys plus conflict-safe Newmark command chords.',
6186
+ 'shortcuts.searchCommands': 'Search commands...',
6187
+ 'shortcuts.searchShortcuts': 'Search commands or shortcuts...',
6188
+ 'shortcuts.noResults': 'No matching commands.',
6189
+ 'shortcuts.footer': 'Up/Down navigate · Enter runs · Esc closes · Tab stays inside this dialog',
6190
+ 'shortcuts.category.general': 'General',
6191
+ 'shortcuts.category.navigation': 'Navigation',
6192
+ 'shortcuts.category.workspace': 'Workspace and chat',
6193
+ 'shortcuts.category.layout': 'Layout and focus',
6194
+ 'shortcuts.category.input': 'Input and modes',
6195
+ 'shortcuts.category.editor': 'Editor',
6196
+ 'shortcuts.category.browser': 'Browser',
6197
+ 'shortcuts.category.terminal': 'Terminal',
6198
+ 'shortcuts.category.context': 'Context controls',
6199
+ 'shortcuts.context.global': 'Application',
6200
+ 'shortcuts.context.prompt': 'Prompt',
6201
+ 'shortcuts.context.editor': 'Editor only',
6202
+ 'shortcuts.context.browser': 'Browser only',
6203
+ 'shortcuts.context.terminal': 'Terminal only',
6204
+ 'shortcuts.context.dialog': 'Dialog',
6205
+ 'shortcuts.openPalette': 'Open command palette',
6206
+ 'shortcuts.openHelp': 'Show keyboard shortcuts',
6207
+ 'shortcuts.focusPrimary': 'Focus primary input',
6208
+ 'shortcuts.focusNext': 'Focus next application region',
6209
+ 'shortcuts.focusPrevious': 'Focus previous application region',
6210
+ 'shortcuts.toggleLeft': 'Toggle left sidebar',
6211
+ 'shortcuts.toggleWorkspacePanel': 'Toggle workspace and conversation panel',
6212
+ 'shortcuts.toggleRight': 'Toggle right sidebar',
6213
+ 'shortcuts.toggleTerminal': 'Toggle terminal',
6214
+ 'shortcuts.nextConversation': 'Next conversation',
6215
+ 'shortcuts.previousConversation': 'Previous conversation',
6216
+ 'shortcuts.nextBranch': 'Next branch',
6217
+ 'shortcuts.previousBranch': 'Previous branch',
6218
+ 'shortcuts.scrollBottom': 'Scroll chat to bottom',
6219
+ 'shortcuts.newTerminal': 'New terminal',
6220
+ 'shortcuts.focusTerminal': 'Focus terminal input',
6221
+ 'shortcuts.focusBrowserAddress': 'Focus browser address',
6222
+ 'shortcuts.toggleTheme': 'Toggle light and dark theme',
6223
+ 'shortcuts.chordHint': '{prefix} waiting for: {keys}',
5723
6224
  'top.minimize': 'Minimize',
5724
6225
  'top.maximize': 'Maximize',
5725
6226
  'top.close': 'Close',
@@ -5731,12 +6232,14 @@ var NEWMARK_I18N = {
5731
6232
  'left.flow': 'Flow',
5732
6233
  'left.settings': 'Settings',
5733
6234
  'left.workspaces': 'Workspaces',
6235
+ 'left.conversations': 'Conversations',
5734
6236
  'left.new': 'New',
5735
6237
  'left.collapse': 'Collapse left sidebar',
5736
6238
  'left.collapseSecondary': 'Collapse workspace panel',
5737
6239
  'input.placeholder': 'Input instruction...',
5738
6240
  'input.send': 'Send',
5739
6241
  'input.stop': 'Stop',
6242
+ 'input.continue': 'Continue',
5740
6243
  'input.guide': 'Guide',
5741
6244
  'input.next': 'Next',
5742
6245
  'mode.build': 'Build',
@@ -6066,7 +6569,13 @@ var NEWMARK_I18N = {
6066
6569
  'flow.noAvailable': 'No workflows are available to run.',
6067
6570
  'flow.started': 'Flow started',
6068
6571
  'plugins.title': 'Plugins',
6572
+ 'plugins.tabMcp': 'MCP',
6573
+ 'plugins.tabDsh': 'DSH Plugin',
6574
+ 'plugins.tabSkills': 'Skills',
6575
+ 'plugins.tabMarket': 'Market',
6576
+ 'plugins.tabGithub': 'GitHub',
6069
6577
  'plugins.mcp': 'MCP Management',
6578
+ 'plugins.dsh': 'DSH Plugin',
6070
6579
  'plugins.management': 'Skills Management',
6071
6580
  'plugins.market': 'Skills Market',
6072
6581
  'plugins.github': 'GitHub CLI',
@@ -6124,6 +6633,74 @@ var NEWMARK_I18N = {
6124
6633
  'plugins.mcpNoServers': 'No user MCP servers.',
6125
6634
  'plugins.mcpDiscovered': 'Discovered from plugins',
6126
6635
  'plugins.mcpStored': 'User MCP servers',
6636
+ 'plugins.mcpConfiguredCount': '{count} configured',
6637
+ 'plugins.mcpSearch': 'Search MCP servers...',
6638
+ 'plugins.mcpNoMatch': 'No MCP servers match this search.',
6639
+ 'plugins.mcpRefresh': 'Refresh',
6640
+ 'plugins.mcpRefreshing': 'Refreshing MCP servers...',
6641
+ 'plugins.mcpLoadError': 'MCP servers could not be loaded.',
6642
+ 'plugins.mcpUnavailable': 'MCP management is unavailable in this interface.',
6643
+ 'plugins.mcpAddTitle': 'Add MCP server',
6644
+ 'plugins.mcpEditTitle': 'Edit MCP server',
6645
+ 'plugins.mcpReviewTitle': 'Review MCP candidate',
6646
+ 'plugins.mcpFormHelp': 'Review every field before saving. Imported candidates always start disabled.',
6647
+ 'plugins.mcpEnabled': 'Enable this server after saving',
6648
+ 'plugins.mcpEndpointStdio': 'Command',
6649
+ 'plugins.mcpEndpointHttp': 'Server URL',
6650
+ 'plugins.mcpCwd': 'Working directory (optional)',
6651
+ 'plugins.mcpSecretsHelp': 'When editing, leave blank to preserve saved values; enter {} to clear them. Secret values are never displayed.',
6652
+ 'plugins.mcpSavedEnvKeys': 'Saved environment keys: {keys}',
6653
+ 'plugins.mcpSavedHeaderKeys': 'Saved header keys: {keys}',
6654
+ 'plugins.mcpNoSavedKeys': 'No saved keys.',
6655
+ 'plugins.mcpInvalidArgs': 'Arguments must be a JSON array.',
6656
+ 'plugins.mcpInvalidObject': '{field} must be a JSON object.',
6657
+ 'plugins.mcpRequiredName': 'Enter a server name.',
6658
+ 'plugins.mcpRequiredCommand': 'Enter a command for this stdio server.',
6659
+ 'plugins.mcpRequiredUrl': 'Enter an http(s) URL for this server.',
6660
+ 'plugins.mcpSaving': 'Saving...',
6661
+ 'plugins.mcpSaved': 'MCP server saved.',
6662
+ 'plugins.mcpEnabledStatus': 'Enabled',
6663
+ 'plugins.mcpDisabledStatus': 'Disabled',
6664
+ 'plugins.mcpMutationFailed': 'MCP operation failed.',
6665
+ 'plugins.mcpRemoveConfirm': 'Remove MCP server "{name}"?',
6666
+ 'plugins.mcpReadonly': 'Read-only metadata',
6667
+ 'plugins.mcpDiscoveredEmpty': 'No MCP metadata was discovered from installed plugins.',
6668
+ 'plugins.mcpStoredEmpty': 'No user MCP servers yet. Add one or review a compatible DSH candidate.',
6669
+ 'plugins.mcpReviewImport': 'Review and import',
6670
+ 'plugins.mcpCandidateHelp': 'This only pre-fills the MCP form. Nothing is installed, executed, or enabled automatically.',
6671
+ 'plugins.retry': 'Retry',
6672
+ 'plugins.dshTitle': 'DSH Plugin compatibility',
6673
+ '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.',
6674
+ 'plugins.dshPreview': 'Developer preview',
6675
+ 'plugins.dshRescan': 'Rescan',
6676
+ 'plugins.dshScanning': 'Scanning DSH compatibility...',
6677
+ 'plugins.dshLoadError': 'DSH compatibility information could not be loaded.',
6678
+ 'plugins.dshUnavailable': 'DSH discovery is unavailable in this interface.',
6679
+ 'plugins.dshCli': 'CLI and package',
6680
+ 'plugins.dshCliPath': 'CLI path',
6681
+ 'plugins.dshCliVersion': 'CLI version',
6682
+ 'plugins.dshPackageVersion': 'Package version',
6683
+ 'plugins.dshHome': 'DSH_HOME',
6684
+ 'plugins.dshHomeSource': 'Home source',
6685
+ 'plugins.dshConfigFiles': 'Configuration files',
6686
+ 'plugins.dshLayers': 'Configuration layer order',
6687
+ 'plugins.dshLayerOrder': 'Later layers override earlier rows; unknown keys remain visible and untouched.',
6688
+ 'plugins.dshHomePatches': 'Home-level patches',
6689
+ 'plugins.dshUpdateability': 'Updateability: use official DSH tooling or edit the shown patch files; Newmark never rewrites them.',
6690
+ 'plugins.dshUpdateChannel': 'Update channel',
6691
+ 'plugins.dshLatestChannel': 'latest (not locked)',
6692
+ 'plugins.dshProfiles': 'Profiles',
6693
+ 'plugins.dshBundles': 'Bundles',
6694
+ 'plugins.dshWarnings': 'Warnings',
6695
+ 'plugins.dshUnknownKeys': 'Unknown keys',
6696
+ 'plugins.dshMcpCandidates': 'MCP candidates',
6697
+ 'plugins.dshNone': 'None discovered',
6698
+ 'plugins.dshNotAvailable': 'Not available',
6699
+ 'plugins.dshOfficialRepo': 'Official repository',
6700
+ 'plugins.dshOfficialDocs': 'Documentation',
6701
+ 'plugins.dshOfficialNpm': 'npm package',
6702
+ 'plugins.dshReadonly': 'Read-only compatibility policy',
6703
+ 'plugins.dshReadonlyDetail': 'Newmark only reads local compatibility metadata. Use official DSH tooling to install, execute, update, or change DSH configuration.',
6127
6704
  'workspace.new': 'New workspace',
6128
6705
  'workspace.type': 'Type',
6129
6706
  'workspace.internal': 'Internal workspace',
@@ -6223,6 +6800,21 @@ var NEWMARK_I18N = {
6223
6800
  'status.contextModeModel': 'model',
6224
6801
  'status.messages': 'messages',
6225
6802
  'status.noCompression': 'No compression event in this conversation',
6803
+ 'status.contextInspector': 'Context management',
6804
+ 'status.contextInspectorHint': 'DSH-inspired live budget; visible chat history stays unchanged.',
6805
+ 'status.activeBuild': 'Active Build',
6806
+ 'status.longHistory': 'Long history',
6807
+ 'status.trigger': 'Trigger',
6808
+ 'status.retention': 'Retention',
6809
+ 'status.hotCache': 'Hot cache',
6810
+ 'status.coldArchive': 'Cold archive',
6811
+ 'status.lastCompression': 'Last compression',
6812
+ 'status.noCompressionShort': 'No compression yet',
6813
+ 'status.compressNow': 'Compress now',
6814
+ 'status.compressing': 'Compressing...',
6815
+ 'status.compressedNow': 'Context compressed.',
6816
+ 'status.compressionFailed': 'Context compression failed.',
6817
+ 'status.compressionBusy': 'Compression is unavailable while this conversation is running.',
6226
6818
  'status.recentFiles': 'Recent file changes',
6227
6819
  'status.noFileChanges': 'No file changes recorded for the latest run.',
6228
6820
  'status.pendingOptions': 'Pending options',
@@ -6243,6 +6835,8 @@ var NEWMARK_I18N = {
6243
6835
  'conversation.reorderFailed': 'Could not save the conversation order.',
6244
6836
  'conversation.pin': 'Pin conversation',
6245
6837
  'conversation.unpin': 'Unpin conversation',
6838
+ 'conversation.branchCommunication': 'Allow branch communication',
6839
+ 'conversation.branchCommunicationBadge': 'Branch communication',
6246
6840
  'conversation.loadingIsolated': 'Loading isolated conversation...',
6247
6841
  'conversation.locked': 'Current conversation is locked while the agent is working.',
6248
6842
  'queue.next': 'Next',
@@ -6286,6 +6880,48 @@ var NEWMARK_I18N = {
6286
6880
  zh: {
6287
6881
  'app.ready': '就绪',
6288
6882
  'app.subtitle': '输入指令开始。Enter 发送,Shift+Enter 换行。',
6883
+ 'shortcuts.paletteTitle': '命令面板',
6884
+ 'shortcuts.helpTitle': '键盘快捷键',
6885
+ 'shortcuts.paletteSubtitle': '搜索并执行所有已注册的 Newmark GUI 命令;不会覆盖标准文本编辑按键。',
6886
+ 'shortcuts.helpSubtitle': '通用桌面快捷键与避免冲突的 Newmark 命令序列。',
6887
+ 'shortcuts.searchCommands': '搜索命令...',
6888
+ 'shortcuts.searchShortcuts': '搜索命令或快捷键...',
6889
+ 'shortcuts.noResults': '没有匹配的命令。',
6890
+ 'shortcuts.footer': '上/下选择 · Enter 执行 · Esc 关闭 · Tab 保持在对话框内',
6891
+ 'shortcuts.category.general': '通用',
6892
+ 'shortcuts.category.navigation': '导航',
6893
+ 'shortcuts.category.workspace': '工作区与对话',
6894
+ 'shortcuts.category.layout': '布局与焦点',
6895
+ 'shortcuts.category.input': '输入与模式',
6896
+ 'shortcuts.category.editor': '编辑器',
6897
+ 'shortcuts.category.browser': '浏览器',
6898
+ 'shortcuts.category.terminal': '终端',
6899
+ 'shortcuts.category.context': '上下文操作',
6900
+ 'shortcuts.context.global': '应用',
6901
+ 'shortcuts.context.prompt': '输入框',
6902
+ 'shortcuts.context.editor': '仅编辑器',
6903
+ 'shortcuts.context.browser': '仅浏览器',
6904
+ 'shortcuts.context.terminal': '仅终端',
6905
+ 'shortcuts.context.dialog': '对话框',
6906
+ 'shortcuts.openPalette': '打开命令面板',
6907
+ 'shortcuts.openHelp': '显示键盘快捷键',
6908
+ 'shortcuts.focusPrimary': '聚焦主要输入框',
6909
+ 'shortcuts.focusNext': '聚焦下一个应用区域',
6910
+ 'shortcuts.focusPrevious': '聚焦上一个应用区域',
6911
+ 'shortcuts.toggleLeft': '切换左侧边栏',
6912
+ 'shortcuts.toggleWorkspacePanel': '切换工作区与对话面板',
6913
+ 'shortcuts.toggleRight': '切换右侧边栏',
6914
+ 'shortcuts.toggleTerminal': '切换终端',
6915
+ 'shortcuts.nextConversation': '下一个对话',
6916
+ 'shortcuts.previousConversation': '上一个对话',
6917
+ 'shortcuts.nextBranch': '下一个分支',
6918
+ 'shortcuts.previousBranch': '上一个分支',
6919
+ 'shortcuts.scrollBottom': '滚动到对话底部',
6920
+ 'shortcuts.newTerminal': '新建终端',
6921
+ 'shortcuts.focusTerminal': '聚焦终端输入框',
6922
+ 'shortcuts.focusBrowserAddress': '聚焦浏览器地址栏',
6923
+ 'shortcuts.toggleTheme': '切换明暗主题',
6924
+ 'shortcuts.chordHint': '{prefix} 等待:{keys}',
6289
6925
  'top.minimize': '最小化',
6290
6926
  'top.maximize': '最大化',
6291
6927
  'top.close': '关闭',
@@ -6297,12 +6933,14 @@ var NEWMARK_I18N = {
6297
6933
  'left.flow': '工作流',
6298
6934
  'left.settings': '设置',
6299
6935
  'left.workspaces': '工作区',
6936
+ 'left.conversations': '对话',
6300
6937
  'left.new': '新建',
6301
6938
  'left.collapse': '折叠左侧栏',
6302
6939
  'left.collapseSecondary': '折叠工作区面板',
6303
6940
  'input.placeholder': '输入指令...',
6304
6941
  'input.send': '发送',
6305
6942
  'input.stop': '停止',
6943
+ 'input.continue': '继续',
6306
6944
  'input.guide': 'Guide',
6307
6945
  'input.next': 'Next',
6308
6946
  'mode.build': 'Build',
@@ -6632,7 +7270,13 @@ var NEWMARK_I18N = {
6632
7270
  'flow.noAvailable': '当前没有可运行的工作流。',
6633
7271
  'flow.started': 'Flow 已启动',
6634
7272
  'plugins.title': '插件',
7273
+ 'plugins.tabMcp': 'MCP',
7274
+ 'plugins.tabDsh': 'DSH Plugin',
7275
+ 'plugins.tabSkills': 'Skills',
7276
+ 'plugins.tabMarket': 'Market',
7277
+ 'plugins.tabGithub': 'GitHub',
6635
7278
  'plugins.mcp': 'MCP 管理',
7279
+ 'plugins.dsh': 'DSH Plugin',
6636
7280
  'plugins.management': 'Skills 管理',
6637
7281
  'plugins.market': 'Skills Market',
6638
7282
  'plugins.github': 'GitHub CLI',
@@ -6690,6 +7334,74 @@ var NEWMARK_I18N = {
6690
7334
  'plugins.mcpNoServers': '暂无用户 MCP 服务器。',
6691
7335
  'plugins.mcpDiscovered': '从插件发现',
6692
7336
  'plugins.mcpStored': '用户 MCP 服务器',
7337
+ 'plugins.mcpConfiguredCount': '已配置 {count} 项',
7338
+ 'plugins.mcpSearch': '搜索 MCP 服务器...',
7339
+ 'plugins.mcpNoMatch': '没有符合搜索条件的 MCP 服务器。',
7340
+ 'plugins.mcpRefresh': '刷新',
7341
+ 'plugins.mcpRefreshing': '正在刷新 MCP 服务器...',
7342
+ 'plugins.mcpLoadError': '无法加载 MCP 服务器。',
7343
+ 'plugins.mcpUnavailable': '当前界面不支持 MCP 管理。',
7344
+ 'plugins.mcpAddTitle': '添加 MCP 服务器',
7345
+ 'plugins.mcpEditTitle': '编辑 MCP 服务器',
7346
+ 'plugins.mcpReviewTitle': '审核 MCP 候选项',
7347
+ 'plugins.mcpFormHelp': '保存前请审核所有字段。从候选项导入时始终默认禁用。',
7348
+ 'plugins.mcpEnabled': '保存后启用此服务器',
7349
+ 'plugins.mcpEndpointStdio': '命令',
7350
+ 'plugins.mcpEndpointHttp': '服务器 URL',
7351
+ 'plugins.mcpCwd': '工作目录(可选)',
7352
+ 'plugins.mcpSecretsHelp': '编辑时留空可保留已保存的值;输入 {} 可清空。界面绝不显示密钥值。',
7353
+ 'plugins.mcpSavedEnvKeys': '已保存环境变量键:{keys}',
7354
+ 'plugins.mcpSavedHeaderKeys': '已保存请求头键:{keys}',
7355
+ 'plugins.mcpNoSavedKeys': '没有已保存的键。',
7356
+ 'plugins.mcpInvalidArgs': '参数必须是 JSON 数组。',
7357
+ 'plugins.mcpInvalidObject': '{field} 必须是 JSON 对象。',
7358
+ 'plugins.mcpRequiredName': '请输入服务器名称。',
7359
+ 'plugins.mcpRequiredCommand': '请输入 stdio 服务器命令。',
7360
+ 'plugins.mcpRequiredUrl': '请输入 http(s) 服务器 URL。',
7361
+ 'plugins.mcpSaving': '正在保存...',
7362
+ 'plugins.mcpSaved': 'MCP 服务器已保存。',
7363
+ 'plugins.mcpEnabledStatus': '已启用',
7364
+ 'plugins.mcpDisabledStatus': '已禁用',
7365
+ 'plugins.mcpMutationFailed': 'MCP 操作失败。',
7366
+ 'plugins.mcpRemoveConfirm': '移除 MCP 服务器“{name}”?',
7367
+ 'plugins.mcpReadonly': '只读元数据',
7368
+ 'plugins.mcpDiscoveredEmpty': '未从已安装插件中发现 MCP 元数据。',
7369
+ 'plugins.mcpStoredEmpty': '暂无用户 MCP 服务器。可以添加服务器,或审核兼容的 DSH 候选项。',
7370
+ 'plugins.mcpReviewImport': '审核并导入',
7371
+ 'plugins.mcpCandidateHelp': '此操作只会预填 MCP 表单,不会安装、执行或自动启用任何内容。',
7372
+ 'plugins.retry': '重试',
7373
+ 'plugins.dshTitle': 'DSH Plugin 兼容',
7374
+ 'plugins.dshHelp': '开发者预览:检查本地官方 DSH CLI 与包配置。发现过程严格只读,绝不会安装、执行、更新或改写 DSH。',
7375
+ 'plugins.dshPreview': '开发者预览',
7376
+ 'plugins.dshRescan': '重新扫描',
7377
+ 'plugins.dshScanning': '正在扫描 DSH 兼容信息...',
7378
+ 'plugins.dshLoadError': '无法加载 DSH 兼容信息。',
7379
+ 'plugins.dshUnavailable': '当前界面不支持 DSH 发现。',
7380
+ 'plugins.dshCli': 'CLI 与软件包',
7381
+ 'plugins.dshCliPath': 'CLI 路径',
7382
+ 'plugins.dshCliVersion': 'CLI 版本',
7383
+ 'plugins.dshPackageVersion': '软件包版本',
7384
+ 'plugins.dshHome': 'DSH_HOME',
7385
+ 'plugins.dshHomeSource': '目录来源',
7386
+ 'plugins.dshConfigFiles': '配置文件',
7387
+ 'plugins.dshLayers': '配置层顺序',
7388
+ 'plugins.dshLayerOrder': '后面的层覆盖前面的条目;未知键会被展示但不会改写。',
7389
+ 'plugins.dshHomePatches': 'Home 级 patch',
7390
+ 'plugins.dshUpdateability': '可更新性:请使用官方 DSH 工具或编辑下方 patch 文件;Newmark 不会重写这些文件。',
7391
+ 'plugins.dshUpdateChannel': '更新通道',
7392
+ 'plugins.dshLatestChannel': 'latest(未锁定)',
7393
+ 'plugins.dshProfiles': 'Profiles',
7394
+ 'plugins.dshBundles': 'Bundles',
7395
+ 'plugins.dshWarnings': '警告',
7396
+ 'plugins.dshUnknownKeys': '未知配置键',
7397
+ 'plugins.dshMcpCandidates': 'MCP 候选项',
7398
+ 'plugins.dshNone': '未发现',
7399
+ 'plugins.dshNotAvailable': '不可用',
7400
+ 'plugins.dshOfficialRepo': '官方仓库',
7401
+ 'plugins.dshOfficialDocs': '官方文档',
7402
+ 'plugins.dshOfficialNpm': 'npm 软件包',
7403
+ 'plugins.dshReadonly': '只读兼容策略',
7404
+ 'plugins.dshReadonlyDetail': 'Newmark 只读取本地兼容元数据。安装、执行、更新或更改 DSH 配置请使用官方 DSH 工具。',
6693
7405
  'workspace.new': '新建工作区',
6694
7406
  'workspace.type': '类型',
6695
7407
  'workspace.internal': '内部工作区',
@@ -6789,6 +7501,21 @@ var NEWMARK_I18N = {
6789
7501
  'status.contextModeModel': '模型',
6790
7502
  'status.messages': '条消息',
6791
7503
  'status.noCompression': '当前对话暂无压缩事件',
7504
+ 'status.contextInspector': '上下文管理',
7505
+ 'status.contextInspectorHint': '借鉴 DSH 的动态预算;可见对话历史不会被改写。',
7506
+ 'status.activeBuild': '当前 Build',
7507
+ 'status.longHistory': '长期历史',
7508
+ 'status.trigger': '触发线',
7509
+ 'status.retention': '保留量',
7510
+ 'status.hotCache': '热缓存',
7511
+ 'status.coldArchive': '冷归档',
7512
+ 'status.lastCompression': '最近一次压缩',
7513
+ 'status.noCompressionShort': '尚未压缩',
7514
+ 'status.compressNow': '立即压缩',
7515
+ 'status.compressing': '正在压缩...',
7516
+ 'status.compressedNow': '上下文已压缩。',
7517
+ 'status.compressionFailed': '上下文压缩失败。',
7518
+ 'status.compressionBusy': '对话运行中,暂时无法压缩上下文。',
6792
7519
  'status.recentFiles': '最近文件变更',
6793
7520
  'status.noFileChanges': '最近一轮没有记录文件变更。',
6794
7521
  'status.pendingOptions': '待处理选项',
@@ -6809,6 +7536,8 @@ var NEWMARK_I18N = {
6809
7536
  'conversation.reorderFailed': '无法保存对话顺序。',
6810
7537
  'conversation.pin': '置顶对话',
6811
7538
  'conversation.unpin': '取消置顶对话',
7539
+ 'conversation.branchCommunication': '允许分支交流',
7540
+ 'conversation.branchCommunicationBadge': '分支交流',
6812
7541
  'conversation.loadingIsolated': '正在加载隔离对话...',
6813
7542
  'conversation.locked': 'Agent 工作中,当前对话已锁定。',
6814
7543
  'queue.next': '下一轮',
@@ -6933,6 +7662,10 @@ function applyLanguageToUi() {
6933
7662
  }
6934
7663
  var wsHead = document.getElementById('left-ws-header');
6935
7664
  if (wsHead) wsHead.textContent = t('left.workspaces');
7665
+ var wsList = document.getElementById('left-ws-list');
7666
+ if (wsList) wsList.setAttribute('aria-label', t('left.workspaces'));
7667
+ var conversationList = document.getElementById('conversation-list');
7668
+ if (conversationList) conversationList.setAttribute('aria-label', t('left.conversations'));
6936
7669
  var wsAdd = document.querySelector('#left-ws-add span:last-child');
6937
7670
  if (wsAdd) wsAdd.textContent = t('left.new');
6938
7671
  setTitleAndLabel('.secondary-top button[onclick="window.openWsSettings()"]', t('workspace.settingsTitle'));
@@ -6958,6 +7691,10 @@ function applyLanguageToUi() {
6958
7691
  if (browserUrl) browserUrl.setAttribute('placeholder', t('browser.url'));
6959
7692
  var terminalInputs = document.querySelectorAll('.terminal-input');
6960
7693
  for (var ti = 0; ti < terminalInputs.length; ti++) terminalInputs[ti].setAttribute('placeholder', t('terminal.enterCommand'));
7694
+ var terminalTabs = document.getElementById('terminal-tabs');
7695
+ if (terminalTabs) terminalTabs.setAttribute('aria-label', t('shortcuts.category.terminal'));
7696
+ var fileTree = document.getElementById('file-tree-container');
7697
+ if (fileTree) fileTree.setAttribute('aria-label', t('right.fileTree'));
6961
7698
  var terminalReady = document.querySelector('#terminal-pane-0 .terminal-output span');
6962
7699
  if (terminalReady && terminalReady.textContent === 'Terminal ready') terminalReady.textContent = t('terminal.ready');
6963
7700
  var terminalStatusSpans = document.querySelectorAll('.terminal-output span');
@@ -8220,6 +8957,8 @@ window.toggleWorkReview = function(head) {
8220
8957
  var review = head && head.closest ? head.closest('.work-review') : null;
8221
8958
  if (!review) return;
8222
8959
  review.classList.toggle('collapsed');
8960
+ var toggle = review.querySelector('.work-review-toggle');
8961
+ if (toggle) toggle.setAttribute('aria-expanded', review.classList.contains('collapsed') ? 'false' : 'true');
8223
8962
  };
8224
8963
 
8225
8964
  window.toggleWorkReviewFiles = function(button) {
@@ -8324,7 +9063,7 @@ function addWorkReview(diffs) {
8324
9063
  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
9064
  '<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
9065
  '<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>' +
9066
+ '<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
9067
  '<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
9068
  els['chat-area'].appendChild(review);
8330
9069
  autoScrollIfAtBottom();
@@ -8859,7 +9598,7 @@ function publicWorkEvent(event) {
8859
9598
  var content = String(event && event.content || '');
8860
9599
  var toolArgs = String(event && event.toolArgs || '');
8861
9600
  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;
9601
+ 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
9602
  }
8864
9603
 
8865
9604
  function publicToolNameForUi(value) {
@@ -9118,7 +9857,12 @@ function syncWorkRunsSnapshot(runs, target, branchId) {
9118
9857
  return item;
9119
9858
  });
9120
9859
  state.workRunsByBranch[workRunBranchKey(target, selectedBranchId)] = normalized;
9121
- state.workRunsByTarget[key] = normalized;
9860
+ // 串分支修复:全局显示列表(workRunsByTarget)只在 sync 的分支就是「浏览分支」时
9861
+ // 才更新。当运行分支(runtime)的 streaming 结果到达、而用户正在浏览另一个分支时,
9862
+ // 只按分支存储(workRunsByBranch),不污染当前浏览分支的显示。
9863
+ if (selectedBranchId === branchIds.viewed) {
9864
+ state.workRunsByTarget[key] = normalized;
9865
+ }
9122
9866
  return normalized;
9123
9867
  }
9124
9868
 
@@ -9410,6 +10154,23 @@ function renderWorkToolGroup(event, eventIndex) {
9410
10154
  activityRows + '</div></details>';
9411
10155
  }
9412
10156
 
10157
+ function renderWorkThought(event, eventIndex) {
10158
+ var zh = currentLang() === 'zh';
10159
+ var content = String(event.content || '').trim();
10160
+ var completed = !!event.completed;
10161
+ var activityDomKey = String(event.id || ('thought-' + eventIndex));
10162
+ var label = completed ? (zh ? '进行了思考' : 'Thought') : (zh ? '思考中' : 'Thinking');
10163
+ var detailRows = '';
10164
+ if (content) {
10165
+ detailRows = '<div class="conversation-work-thought-text">' + esc(content) + '</div>';
10166
+ } else if (!completed) {
10167
+ detailRows = '<div class="conversation-work-thought-text conversation-work-thought-pending">' + esc(zh ? '正在思考…' : 'Thinking…') + '</div>';
10168
+ }
10169
+ return '<details class="conversation-work-activity conversation-work-thought" data-activity-key="' + escAttr(activityDomKey) + '"><summary>' +
10170
+ iconSvg('brain', 'thought', 'tiny') + '<span>' + esc(label) + '</span>' +
10171
+ '<span class="conversation-work-activity-chevron" aria-hidden="true"></span></summary>' + detailRows + '</details>';
10172
+ }
10173
+
9413
10174
  function workEventLabel(event) {
9414
10175
  var type = String(event && event.type || 'status').toLowerCase();
9415
10176
  var content = String(event && event.content || '').trim();
@@ -9514,6 +10275,16 @@ function renderWorkRunEvents(run, includeGuides) {
9514
10275
  }
9515
10276
  if (!rawEvent) continue;
9516
10277
  }
10278
+ if (rawType === 'thought_result') {
10279
+ for (var thoughtPriorIndex = events.length - 1; thoughtPriorIndex >= 0; thoughtPriorIndex--) {
10280
+ var thoughtPrior = events[thoughtPriorIndex];
10281
+ if (String(thoughtPrior && thoughtPrior.type || '').toLowerCase() !== 'thought' || thoughtPrior.completed) continue;
10282
+ events[thoughtPriorIndex] = Object.assign({}, thoughtPrior, { completed: true, content: rawEvent.content || thoughtPrior.content });
10283
+ rawEvent = null;
10284
+ break;
10285
+ }
10286
+ if (!rawEvent) continue;
10287
+ }
9517
10288
  events.push(rawEvent);
9518
10289
  }
9519
10290
  flushPublicText();
@@ -9554,6 +10325,7 @@ function renderWorkRunEvents(run, includeGuides) {
9554
10325
  }
9555
10326
  if (type.indexOf('guide') === 0 || event.guide) return renderWorkRunGuideMessage(event);
9556
10327
  if (type === 'tool_group') return renderWorkToolGroup(event, eventIndex);
10328
+ if (type === 'thought') return renderWorkThought(event, eventIndex);
9557
10329
  var activitySummary = type.indexOf('tool_') === 0 && type !== 'tool_call' && type !== 'tool_result';
9558
10330
  var cls = type.indexOf('guide') === 0 ? ' guide' : (type === 'error' ? ' error' : (activitySummary ? ' activity-summary' : ''));
9559
10331
  var label = activitySummary ? workToolActivityLabel(event.activity, Number(event.count || 1), !!event.completed) : workEventLabel(event) + (event.completed ? (currentLang() === 'zh' ? ' · 已完成' : ' · completed') : '');
@@ -10576,7 +11348,12 @@ function applyConversationSnapshot(s, requestedConversationId) {
10576
11348
  if (s && s.runtimeKey) registerRuntimeKey(snapshotTarget, s.runtimeKey);
10577
11349
  hydrateConversationBranchState(s);
10578
11350
  rebindQueueToRuntimeBranch(snapshotTarget);
10579
- if (s && Array.isArray(s.workRuns)) syncWorkRunsSnapshot(s.workRuns, snapshotTarget, String(s.activeBranchId || ''));
11351
+ if (s && Array.isArray(s.workRuns)) {
11352
+ var viewedBranchIdForSync = Array.isArray(s.viewedBranchNodePath) && s.viewedBranchNodePath.length
11353
+ ? String(s.viewedBranchNodePath[s.viewedBranchNodePath.length - 1])
11354
+ : '';
11355
+ syncWorkRunsSnapshot(s.workRuns, snapshotTarget, viewedBranchIdForSync || undefined);
11356
+ }
10580
11357
  applyAutoRouteRatingState(s);
10581
11358
  if (s && s.chatMessages) {
10582
11359
  renderChatMessages(s.chatMessages);
@@ -10639,9 +11416,9 @@ function addShellBlock(title, content) {
10639
11416
  var div = document.createElement('div');
10640
11417
  div.className = 'chat-msg assistant';
10641
11418
  div.innerHTML = '<div class="shell-block collapsed">' +
10642
- '<div class="shell-block-header" onclick="this.parentElement.classList.toggle(\'collapsed\')">' +
11419
+ '<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
11420
  '<span class="arrow">&gt;</span> ' + esc(title) +
10644
- '</div><div class="shell-block-body">' + esc(content) + '</div></div>';
11421
+ '</button><div class="shell-block-body">' + esc(content) + '</div></div>';
10645
11422
  els['chat-area'].appendChild(div);
10646
11423
  autoScrollIfAtBottom();
10647
11424
  }
@@ -10650,10 +11427,10 @@ function addDiffBlock(title, adds, dels, linesHTML) {
10650
11427
  var div = document.createElement('div');
10651
11428
  div.className = 'chat-msg assistant';
10652
11429
  div.innerHTML = '<div class="diff-block collapsed">' +
10653
- '<div class="diff-block-header" onclick="this.parentElement.classList.toggle(\'collapsed\')">' +
11430
+ '<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
11431
  '<span class="arrow">&gt;</span> ' + esc(title) +
10655
11432
  '<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>';
11433
+ '</button><div class="diff-block-body">' + linesHTML + '</div></div>';
10657
11434
  els['chat-area'].appendChild(div);
10658
11435
  autoScrollIfAtBottom();
10659
11436
  }
@@ -11135,7 +11912,7 @@ window.stopCurrentConversation = async function() {
11135
11912
  return true;
11136
11913
  };
11137
11914
 
11138
- window.submitCurrentAction = function() {
11915
+ window.submitCurrentAction = function(source) {
11139
11916
  if (currentFlowRunning() && flowTakeoverMatchesCurrent()) {
11140
11917
  if (!promptHasText()) {
11141
11918
  window.stopFlowRun();
@@ -11170,6 +11947,8 @@ window.submitCurrentAction = function() {
11170
11947
  return;
11171
11948
  }
11172
11949
  if (isCurrentConversationRunning() && !promptHasText()) {
11950
+ // 回车:运行中空输入不操作(打断仅通过 Esc 或点击打断按钮)。
11951
+ if (source === 'enter') return;
11173
11952
  window.stopCurrentConversation();
11174
11953
  return;
11175
11954
  }
@@ -11211,6 +11990,98 @@ window.renderContextWindow = function() {
11211
11990
  ring.style.mask = 'radial-gradient(farthest-side, transparent 58%, #000 60%)';
11212
11991
  var status = c.warning === 'over_limit' ? t('status.contextOverLimit') : (c.warning === 'near_limit' ? t('status.contextNearLimit') : t('status.contextTokens'));
11213
11992
  ring.title = status + ': ' + used + ' / ' + max;
11993
+ if (window.renderContextInspector) window.renderContextInspector();
11994
+ };
11995
+
11996
+ function contextInspectorValue(value) {
11997
+ var number = Number(value);
11998
+ return Number.isFinite(number) ? Math.max(0, Math.round(number)) : 0;
11999
+ }
12000
+
12001
+ function contextInspectorCell(label, value) {
12002
+ return '<div class="context-inspector-cell"><span class="context-inspector-label">' + esc(label) + '</span><span class="context-inspector-value">' + esc(value) + '</span></div>';
12003
+ }
12004
+
12005
+ window.renderContextInspector = function() {
12006
+ var panel = document.getElementById('context-inspector');
12007
+ var ring = els['context-token-ring'] || document.getElementById('context-token-ring');
12008
+ if (!panel || !ring) return;
12009
+ ring.setAttribute('aria-expanded', state.contextInspectorOpen ? 'true' : 'false');
12010
+ panel.classList.toggle('open', state.contextInspectorOpen);
12011
+ if (!state.contextInspectorOpen) {
12012
+ panel.innerHTML = '';
12013
+ return;
12014
+ }
12015
+ var c = state.contextWindow || {};
12016
+ var max = Math.max(1, contextInspectorValue(c.maxTokens) || 1);
12017
+ var used = contextInspectorValue(c.estimatedTokens);
12018
+ var active = contextInspectorValue(c.buildBlockTokens);
12019
+ var history = contextInspectorValue(c.longHistoryTokens);
12020
+ var activeTrigger = contextInspectorValue(c.buildBlockTriggerTokens);
12021
+ var historyTrigger = contextInspectorValue(c.longHistoryTriggerTokens);
12022
+ var activeRetention = contextInspectorValue(c.buildBlockRetentionTokens);
12023
+ var historyRetention = contextInspectorValue(c.longHistoryRetentionTokens);
12024
+ var percent = Math.min(100, Math.round((used / max) * 100));
12025
+ var fillColor = c.warning === 'over_limit' ? 'var(--nm-state-danger)' : (c.warning === 'near_limit' ? 'var(--nm-state-warning)' : 'var(--accent)');
12026
+ var compression = state.contextCompression || null;
12027
+ var last = compression
12028
+ ? ((compression.fallback ? t('status.contextModeFallback') : t('status.contextModeModel')) + ' · ' + String(compression.originalMessages || 0) + ' → ' + String(compression.compressedMessages || 0) + ' ' + t('status.messages'))
12029
+ : t('status.noCompressionShort');
12030
+ var busy = !!state.contextMutationPending;
12031
+ var running = typeof isCurrentConversationRunning === 'function' && isCurrentConversationRunning();
12032
+ 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>' +
12033
+ '<button type="button" class="context-inspector-close" onclick="window.closeContextInspector()" aria-label="' + esc(t('common.close') || 'Close') + '">×</button></div>' +
12034
+ '<div class="context-inspector-meter"><div class="context-inspector-meter-fill" style="width:' + percent + '%;background:' + fillColor + '"></div></div>' +
12035
+ '<div class="context-inspector-grid">' +
12036
+ contextInspectorCell(t('status.contextTokens'), used + ' / ' + max) +
12037
+ contextInspectorCell(t('status.activeBuild'), active + ' / ' + activeTrigger) +
12038
+ contextInspectorCell(t('status.longHistory'), history + ' / ' + historyTrigger) +
12039
+ contextInspectorCell(t('status.retention'), activeRetention + ' + ' + historyRetention) +
12040
+ contextInspectorCell(t('status.hotCache'), contextInspectorValue(c.cacheEntries) + ' entries') +
12041
+ contextInspectorCell(t('status.coldArchive'), contextInspectorValue(c.archiveEntries) + ' entries') +
12042
+ '</div>' +
12043
+ '<div class="context-inspector-section"><div class="context-inspector-section-title">' + esc(t('status.lastCompression')) + '</div><div class="context-inspector-meta">' + esc(last) + '</div>' +
12044
+ (compression && compression.model ? '<div class="context-inspector-meta">' + esc(compression.model) + '</div>' : '') + '</div>' +
12045
+ '<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>';
12046
+ };
12047
+
12048
+ window.closeContextInspector = function() {
12049
+ state.contextInspectorOpen = false;
12050
+ window.renderContextInspector();
12051
+ };
12052
+
12053
+ window.toggleContextInspector = function() {
12054
+ state.contextInspectorOpen = !state.contextInspectorOpen;
12055
+ window.hideContextWindowTooltip();
12056
+ window.renderContextInspector();
12057
+ };
12058
+
12059
+ window.compressContextNow = function() {
12060
+ if (state.contextMutationPending) return;
12061
+ if (typeof isCurrentConversationRunning === 'function' && isCurrentConversationRunning()) {
12062
+ showUiNotice(t('status.compressionBusy'), 'error', 'context-compress-busy');
12063
+ return;
12064
+ }
12065
+ if (!api.compressContext) {
12066
+ showUiNotice(t('status.compressionFailed'), 'error', 'context-compress-unavailable');
12067
+ return;
12068
+ }
12069
+ state.contextMutationPending = true;
12070
+ window.renderContextInspector();
12071
+ Promise.resolve(api.compressContext({ target: currentConversationTarget(), force: true })).then(function(result) {
12072
+ if (!result || result.ok === false) throw new Error((result && result.error) || t('status.compressionFailed'));
12073
+ if (result.contextWindow) state.contextWindow = result.contextWindow;
12074
+ if (result.contextCompression !== undefined) state.contextCompression = result.contextCompression;
12075
+ window.renderContextWindow();
12076
+ window.renderRightStatusPanel();
12077
+ showUiNotice(t('status.compressedNow'), 'success', 'context-compress-success');
12078
+ return result;
12079
+ }).catch(function(error) {
12080
+ showUiNotice(error && error.message ? error.message : t('status.compressionFailed'), 'error', 'context-compress-error');
12081
+ }).finally(function() {
12082
+ state.contextMutationPending = false;
12083
+ window.renderContextInspector();
12084
+ });
11214
12085
  };
11215
12086
 
11216
12087
  window.contextWindowTooltipHtml = function() {
@@ -11831,6 +12702,7 @@ window.renderQueuePanel = function() {
11831
12702
  }
11832
12703
  var toggle = panel.querySelector('#queue-expand-btn');
11833
12704
  if (toggle) {
12705
+ toggle.setAttribute('aria-expanded', state.queueCollapsed ? 'false' : 'true');
11834
12706
  toggle.title = state.queueCollapsed ? t('queue.expand') : t('queue.collapse');
11835
12707
  toggle.innerHTML = iconSvg(state.queueCollapsed ? 'chevron-up' : 'chevron-down', toggle.title, 'tiny');
11836
12708
  }
@@ -12120,7 +12992,14 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12120
12992
  var displayText = optimisticAttachments.length
12121
12993
  ? rawText + (rawText ? '\n\n' : '') + '[' + optimisticAttachments.length + ' image attachment' + (optimisticAttachments.length === 1 ? '' : 's') + ']'
12122
12994
  : text;
12123
- if (!text && !optimisticAttachments.length) return;
12995
+ if (!text && !optimisticAttachments.length) {
12996
+ // 空输入时发送默认句段「继续 / Continue」,按界面语言切换。
12997
+ var continuePhrase = t('input.continue');
12998
+ rawText = continuePhrase;
12999
+ text = continuePhrase;
13000
+ requestMessage = continuePhrase;
13001
+ displayText = continuePhrase;
13002
+ }
12124
13003
 
12125
13004
  var lockedConversationId = activeConversationId();
12126
13005
  var lockedTarget = currentConversationTarget(lockedConversationId);
@@ -12868,19 +13747,25 @@ window.toggleBottom = function() {
12868
13747
  };
12869
13748
 
12870
13749
  // === Right Sidebar Tabs ===
13750
+ function rightTabButtonHtml(tab, icon, label, active) {
13751
+ 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>';
13752
+ }
13753
+
12871
13754
  window.upgradeRightSidebar = function() {
12872
13755
  var rightTabs = els['right-tabs'] || document.getElementById('right-tabs');
12873
13756
  var rightContent = els['right-content'] || document.getElementById('right-content');
12874
13757
  if (!rightTabs || !rightContent || rightTabs.getAttribute('data-upgraded') === 'true') return;
12875
13758
  rightTabs.setAttribute('data-upgraded', 'true');
13759
+ rightTabs.setAttribute('role', 'tablist');
13760
+ rightTabs.setAttribute('aria-label', t('right.files'));
12876
13761
  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>' +
13762
+ rightTabButtonHtml('file-tree', 'folder', t('right.files'), state.rightTab === 'file-tree') +
13763
+ rightTabButtonHtml('editor', 'square-pen', t('right.editor'), state.rightTab === 'editor') +
13764
+ rightTabButtonHtml('plan', 'list-checks', t('right.plan'), state.rightTab === 'plan') +
13765
+ rightTabButtonHtml('subagent', 'bot', t('right.subagents'), state.rightTab === 'subagent') +
13766
+ rightTabButtonHtml('browser', 'globe', t('right.browser'), state.rightTab === 'browser') +
13767
+ rightTabButtonHtml('status', 'activity', t('right.status'), state.rightTab === 'status') +
13768
+ rightTabButtonHtml('archives', 'archive', t('right.archives'), state.rightTab === 'archives') +
12884
13769
  '<div class="tab-divider"></div>' +
12885
13770
  '<button class="tab-btn" onclick="window.toggleRight()" title="' + escAttr(t('right.close')) + '">' + iconOnly('x', t('right.close')) + '</button>';
12886
13771
 
@@ -12888,7 +13773,7 @@ window.upgradeRightSidebar = function() {
12888
13773
  if (filePanel && !filePanel.getAttribute('data-upgraded')) {
12889
13774
  filePanel.setAttribute('data-upgraded', 'true');
12890
13775
  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>';
13776
+ 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
13777
  if (tree) document.getElementById('file-tree-container').innerHTML = tree.innerHTML;
12893
13778
  }
12894
13779
  var subPanel = document.getElementById('panel-subagent');
@@ -12919,6 +13804,14 @@ window.upgradeRightSidebar = function() {
12919
13804
  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
13805
  rightContent.appendChild(archivesPanel);
12921
13806
  }
13807
+ var upgradedPanels = rightContent.querySelectorAll('.tab-panel');
13808
+ for (var upgradedIndex = 0; upgradedIndex < upgradedPanels.length; upgradedIndex++) {
13809
+ var upgradedPanel = upgradedPanels[upgradedIndex];
13810
+ var upgradedTab = upgradedPanel.id.replace(/^panel-/, '');
13811
+ upgradedPanel.setAttribute('role', 'tabpanel');
13812
+ upgradedPanel.setAttribute('aria-labelledby', 'right-tab-' + upgradedTab);
13813
+ upgradedPanel.hidden = upgradedTab !== state.rightTab;
13814
+ }
12922
13815
  };
12923
13816
 
12924
13817
  window.switchRightTab = function(tab) {
@@ -12932,11 +13825,16 @@ window.switchRightTab = function(tab) {
12932
13825
  if (!rightTabs || !rightContent) return;
12933
13826
  var btns = rightTabs.querySelectorAll('.tab-btn[data-tab]');
12934
13827
  for (var i = 0; i < btns.length; i++) {
12935
- btns[i].classList.toggle('active', btns[i].getAttribute('data-tab') === tab);
13828
+ var selected = btns[i].getAttribute('data-tab') === tab;
13829
+ btns[i].classList.toggle('active', selected);
13830
+ btns[i].setAttribute('aria-selected', selected ? 'true' : 'false');
13831
+ btns[i].tabIndex = selected ? 0 : -1;
12936
13832
  }
12937
13833
  var panels = rightContent.querySelectorAll('.tab-panel');
12938
13834
  for (var j = 0; j < panels.length; j++) {
12939
- panels[j].classList.toggle('active', panels[j].id === 'panel-' + tab);
13835
+ var panelActive = panels[j].id === 'panel-' + tab;
13836
+ panels[j].classList.toggle('active', panelActive);
13837
+ panels[j].hidden = !panelActive;
12940
13838
  }
12941
13839
  // Load content on first access
12942
13840
  if (tab === 'file-tree') window.loadFileTree();
@@ -13028,7 +13926,7 @@ window.renderConversationPlan = function() {
13028
13926
  var plan = normalizeConversationPlan(state.conversationPlan);
13029
13927
  var items = plan.items || [];
13030
13928
  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()">' +
13929
+ '<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
13930
  '<button class="sec-btn primary" onclick="window.addConversationPlanItem()">' + esc(t('plan.add')) + '</button>' +
13033
13931
  '</div>';
13034
13932
  if (!items.length) {
@@ -13096,7 +13994,7 @@ window.editConversationPlanItem = function(idx) {
13096
13994
  var rows = target.querySelectorAll('.plan-row');
13097
13995
  var row = rows[idx];
13098
13996
  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 + ')">';
13997
+ 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
13998
  var actions = row.querySelector('.plan-actions');
13101
13999
  if (actions) {
13102
14000
  actions.innerHTML = '<button class="archive-action-btn" onclick="window.saveConversationPlanEdit(' + idx + ')">' + esc(t('common.save')) + '</button>' +
@@ -13247,6 +14145,8 @@ window.renderRightStatusPanel = function() {
13247
14145
  if (contextWindow) {
13248
14146
  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
14147
  esc(String(contextWindow.estimatedTokens || 0) + ' / ' + String(contextWindow.maxTokens || 0) + ' | ' + String(contextWindow.warning || 'ok')) +
14148
+ (contextWindow.buildBlockTokens !== undefined ? '<br>' + esc(t('status.activeBuild')) + ': ' + esc(String(contextWindow.buildBlockTokens || 0) + ' / ' + String(contextWindow.buildBlockTriggerTokens || 0)) +
14149
+ ' · ' + esc(t('status.longHistory')) + ': ' + esc(String(contextWindow.longHistoryTokens || 0) + ' / ' + String(contextWindow.longHistoryTriggerTokens || 0)) : '') +
13250
14150
  '</span></div></div>';
13251
14151
  }
13252
14152
  if (compression) {
@@ -13470,6 +14370,35 @@ window.spawnTerminal = function(shellId, options) {
13470
14370
  return window.addTerminalTab(shellId, options);
13471
14371
  };
13472
14372
 
14373
+ window.handleTerminalTabKey = function(event) {
14374
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
14375
+ var tabs = Array.prototype.slice.call(document.querySelectorAll('#terminal-tabs .terminal-tab'));
14376
+ var index = tabs.indexOf(event.currentTarget);
14377
+ if (index < 0 || !tabs.length) return false;
14378
+ var next = index;
14379
+ if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
14380
+ else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
14381
+ else if (event.key === 'Home') next = 0;
14382
+ else if (event.key === 'End') next = tabs.length - 1;
14383
+ else if (event.key === 'Enter' || event.key === ' ') next = index;
14384
+ else if (event.key === 'Delete') {
14385
+ event.preventDefault();
14386
+ var closingId = parseInt(tabs[index].getAttribute('data-tab-id'), 10);
14387
+ window.closeTerminalTab(closingId);
14388
+ requestAnimationFrame(function() {
14389
+ var activeTab = document.querySelector('#terminal-tabs .terminal-tab.active') || document.querySelector('#terminal-tabs .terminal-tab');
14390
+ if (activeTab) activeTab.focus({ preventScroll:true });
14391
+ });
14392
+ return true;
14393
+ }
14394
+ else return false;
14395
+ event.preventDefault();
14396
+ var tabId = parseInt(tabs[next].getAttribute('data-tab-id'), 10);
14397
+ window.switchTerminalTab(tabId);
14398
+ tabs[next].focus({ preventScroll:true });
14399
+ return true;
14400
+ };
14401
+
13473
14402
  window.addTerminalTab = function(shellId, options) {
13474
14403
  options = options || {};
13475
14404
  shellId = normalizeTerminalShell(shellId || state._terminalShell);
@@ -13487,13 +14416,23 @@ window.addTerminalTab = function(shellId, options) {
13487
14416
  // Create tab button
13488
14417
  var tab = document.createElement('div');
13489
14418
  tab.className = 'terminal-tab active';
14419
+ tab.id = 'terminal-tab-' + tabId;
14420
+ tab.setAttribute('role', 'tab');
14421
+ tab.setAttribute('aria-selected', 'true');
14422
+ tab.setAttribute('aria-controls', 'terminal-pane-' + tabId);
14423
+ tab.tabIndex = 0;
13490
14424
  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>';
14425
+ 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
14426
  tab.onclick = function() { window.switchTerminalTab(tabId); };
14427
+ tab.addEventListener('keydown', window.handleTerminalTabKey);
13493
14428
 
13494
14429
  // Deactivate all other tabs
13495
14430
  var allTabs = tabsContainer.querySelectorAll('.terminal-tab');
13496
- for (var i = 0; i < allTabs.length; i++) allTabs[i].classList.remove('active');
14431
+ for (var i = 0; i < allTabs.length; i++) {
14432
+ allTabs[i].classList.remove('active');
14433
+ allTabs[i].setAttribute('aria-selected', 'false');
14434
+ allTabs[i].tabIndex = -1;
14435
+ }
13497
14436
 
13498
14437
  tabsContainer.appendChild(tab);
13499
14438
 
@@ -13501,18 +14440,20 @@ window.addTerminalTab = function(shellId, options) {
13501
14440
  var pane = document.createElement('div');
13502
14441
  pane.className = 'terminal-pane active';
13503
14442
  pane.id = 'terminal-pane-' + tabId;
14443
+ pane.setAttribute('role', 'tabpanel');
14444
+ pane.setAttribute('aria-labelledby', tab.id);
13504
14445
  pane.setAttribute('data-tab-id', tabId);
13505
14446
  pane.setAttribute('data-session', '');
13506
14447
  pane.innerHTML =
13507
14448
  '<div class="terminal-output"><span style="color:var(--accent2);">Connecting ' + shellId + '...</span>\r\n</div>' +
13508
14449
  '<div class="terminal-input-row">' +
13509
14450
  '<span class="terminal-prompt">' + shellLabel + '></span>' +
13510
- '<input class="terminal-input" type="text" placeholder="Enter command..." onkeydown="if(event.key===\'Enter\')window.terminalSend()">' +
14451
+ '<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
14452
  '</div>';
13512
14453
 
13513
14454
  // Deactivate all other panes
13514
14455
  var allPanes = body.querySelectorAll('.terminal-pane');
13515
- for (var j = 0; j < allPanes.length; j++) allPanes[j].classList.remove('active');
14456
+ for (var j = 0; j < allPanes.length; j++) { allPanes[j].classList.remove('active'); allPanes[j].hidden = true; }
13516
14457
 
13517
14458
  body.appendChild(pane);
13518
14459
 
@@ -13563,21 +14504,33 @@ window.ensureTerminalTakeoverPane = function(session) {
13563
14504
 
13564
14505
  var tab = document.createElement('div');
13565
14506
  tab.className = 'terminal-tab agent-takeover marquee-border';
14507
+ tab.id = 'terminal-tab-' + tabId;
14508
+ tab.setAttribute('role', 'tab');
14509
+ tab.setAttribute('aria-selected', 'true');
14510
+ tab.setAttribute('aria-controls', 'terminal-pane-' + tabId);
14511
+ tab.tabIndex = 0;
13566
14512
  tab.setAttribute('data-tab-id', tabId);
13567
14513
  tab.setAttribute('data-takeover-session', key);
13568
14514
  tab.setAttribute('data-takeover-workspace-id', session.workspaceId || '');
13569
14515
  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>';
14516
+ 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
14517
  tab.onclick = function() { window.switchTerminalTab(tabId); };
14518
+ tab.addEventListener('keydown', window.handleTerminalTabKey);
13572
14519
 
13573
14520
  var allTabs = tabsContainer.querySelectorAll('.terminal-tab');
13574
- for (var i = 0; i < allTabs.length; i++) allTabs[i].classList.remove('active');
14521
+ for (var i = 0; i < allTabs.length; i++) {
14522
+ allTabs[i].classList.remove('active');
14523
+ allTabs[i].setAttribute('aria-selected', 'false');
14524
+ allTabs[i].tabIndex = -1;
14525
+ }
13575
14526
  tab.classList.add('active');
13576
14527
  tabsContainer.appendChild(tab);
13577
14528
 
13578
14529
  var pane = document.createElement('div');
13579
14530
  pane.className = 'terminal-pane active agent-takeover marquee-border';
13580
14531
  pane.id = 'terminal-pane-' + tabId;
14532
+ pane.setAttribute('role', 'tabpanel');
14533
+ pane.setAttribute('aria-labelledby', tab.id);
13581
14534
  pane.setAttribute('data-tab-id', tabId);
13582
14535
  pane.setAttribute('data-session', '');
13583
14536
  pane.setAttribute('data-takeover-session', key);
@@ -13589,11 +14542,11 @@ window.ensureTerminalTakeoverPane = function(session) {
13589
14542
  '<div class="terminal-output"></div>' +
13590
14543
  '<div class="terminal-input-row">' +
13591
14544
  '<span class="terminal-prompt">Agent&gt;</span>' +
13592
- '<input class="terminal-input" type="text" placeholder="Enter command..." onkeydown="if(event.key===\'Enter\')window.terminalSend()">' +
14545
+ '<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
14546
  '</div>';
13594
14547
 
13595
14548
  var allPanes = body.querySelectorAll('.terminal-pane');
13596
- for (var j = 0; j < allPanes.length; j++) allPanes[j].classList.remove('active');
14549
+ for (var j = 0; j < allPanes.length; j++) { allPanes[j].classList.remove('active'); allPanes[j].hidden = true; }
13597
14550
  body.appendChild(pane);
13598
14551
  tabsContainer.scrollLeft = tabsContainer.scrollWidth;
13599
14552
  window.switchTerminalTab(tabId);
@@ -13688,12 +14641,17 @@ window.switchTerminalTab = function(tabId) {
13688
14641
  // Update tabs
13689
14642
  var allTabs = document.querySelectorAll('.terminal-tab');
13690
14643
  for (var i = 0; i < allTabs.length; i++) {
13691
- allTabs[i].classList.toggle('active', parseInt(allTabs[i].getAttribute('data-tab-id')) === tabId);
14644
+ var selected = parseInt(allTabs[i].getAttribute('data-tab-id')) === tabId;
14645
+ allTabs[i].classList.toggle('active', selected);
14646
+ allTabs[i].setAttribute('aria-selected', selected ? 'true' : 'false');
14647
+ allTabs[i].tabIndex = selected ? 0 : -1;
13692
14648
  }
13693
14649
  // Update panes
13694
14650
  var allPanes = document.querySelectorAll('.terminal-pane');
13695
14651
  for (var j = 0; j < allPanes.length; j++) {
13696
- allPanes[j].classList.toggle('active', parseInt(allPanes[j].getAttribute('data-tab-id')) === tabId);
14652
+ var active = parseInt(allPanes[j].getAttribute('data-tab-id')) === tabId;
14653
+ allPanes[j].classList.toggle('active', active);
14654
+ allPanes[j].hidden = !active;
13697
14655
  }
13698
14656
  // Update active session
13699
14657
  var activePane = document.querySelector('.terminal-pane.active');
@@ -13879,6 +14837,7 @@ function renderTodo() {
13879
14837
  label.textContent = t('goal.list') + (items.length ? ' ' + items.length : '');
13880
14838
  var toggle = wrap.querySelector('#todo-header .stack-icon-btn');
13881
14839
  if (toggle) {
14840
+ toggle.setAttribute('aria-expanded', state.todoCollapsed ? 'false' : 'true');
13882
14841
  var title = state.todoCollapsed ? t('queue.expand') : t('queue.collapse');
13883
14842
  toggle.title = title;
13884
14843
  toggle.innerHTML = iconSvg(state.todoCollapsed ? 'chevron-up' : 'chevron-down', title, 'tiny');
@@ -13891,10 +14850,10 @@ function renderTodo() {
13891
14850
  for (var i = 0; i < items.length; i++) {
13892
14851
  var item = items[i];
13893
14852
  var done = item.status === 'done';
13894
- html += '<div class="todo-item ' + (done ? 'done' : '') + '" onclick="window.checkTodo(' + i + ')" title="' + escAttr(item.text) + '">' +
14853
+ html += '<button type="button" class="todo-item ' + (done ? 'done' : '') + '" onclick="window.checkTodo(' + i + ')" title="' + escAttr(item.text) + '" aria-pressed="' + (done ? 'true' : 'false') + '">' +
13895
14854
  '<span class="todo-check">' + (done ? iconSvg('check', 'done', 'tiny') : '') + '</span>' +
13896
14855
  '<span class="todo-text">' + esc(item.text) + '</span>' +
13897
- '</div>';
14856
+ '</button>';
13898
14857
  }
13899
14858
  list.innerHTML = html;
13900
14859
  }
@@ -13995,8 +14954,61 @@ window.setInputMode = function(mode, persist) {
13995
14954
  };
13996
14955
 
13997
14956
  // === Sub-Window System ===
14957
+ var subWindowOriginFocus = null;
14958
+ function newmarkFocusableElements(container) {
14959
+ if (!container || !container.querySelectorAll) return [];
14960
+ 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) {
14961
+ return !element.hidden && element.getAttribute('aria-hidden') !== 'true' && element.getClientRects().length > 0;
14962
+ });
14963
+ }
14964
+
14965
+ function focusSubWindowInitial() {
14966
+ var dialog = els['sub-win'];
14967
+ if (!dialog || !els['sub-win-overlay'].classList.contains('open')) return;
14968
+ var body = els['sub-win-body'];
14969
+ var target = body && (body.querySelector('[autofocus]') || newmarkFocusableElements(body)[0]);
14970
+ if (!target) target = dialog.querySelector('.sub-win-close') || dialog;
14971
+ if (target && typeof target.focus === 'function') target.focus({ preventScroll: true });
14972
+ }
14973
+
14974
+ function updateApplicationInertState() {
14975
+ var commandOpen = !!(document.getElementById('command-surface-overlay') && document.getElementById('command-surface-overlay').classList.contains('open'));
14976
+ var subWindowOpen = !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'));
14977
+ var topbar = document.getElementById('topbar');
14978
+ var main = document.getElementById('main');
14979
+ if (topbar) topbar.inert = commandOpen || subWindowOpen;
14980
+ if (main) main.inert = commandOpen || subWindowOpen;
14981
+ if (els['sub-win-overlay']) els['sub-win-overlay'].inert = commandOpen;
14982
+ }
14983
+ window.updateApplicationInertState = updateApplicationInertState;
14984
+
14985
+ function trapNewmarkDialogFocus(event, container) {
14986
+ if (!event || event.key !== 'Tab' || !container) return false;
14987
+ var items = newmarkFocusableElements(container);
14988
+ if (!items.length) {
14989
+ event.preventDefault();
14990
+ if (typeof container.focus === 'function') container.focus();
14991
+ return true;
14992
+ }
14993
+ var first = items[0];
14994
+ var last = items[items.length - 1];
14995
+ var active = document.activeElement;
14996
+ if (event.shiftKey && (active === first || !container.contains(active))) {
14997
+ event.preventDefault();
14998
+ last.focus();
14999
+ return true;
15000
+ }
15001
+ if (!event.shiftKey && (active === last || !container.contains(active))) {
15002
+ event.preventDefault();
15003
+ first.focus();
15004
+ return true;
15005
+ }
15006
+ return false;
15007
+ }
15008
+
13998
15009
  window.openSubWin = function(title, html) {
13999
15010
  var overlayOpen = els['sub-win-overlay'].classList.contains('open');
15011
+ if (!overlayOpen && document.activeElement && typeof document.activeElement.focus === 'function') subWindowOriginFocus = document.activeElement;
14000
15012
  if (overlayOpen && !state.restoringSubWindow && state.activeSubWindowView) {
14001
15013
  if (!state.subWindowStack) state.subWindowStack = [];
14002
15014
  state.subWindowStack.push({
@@ -14010,6 +15022,25 @@ window.openSubWin = function(title, html) {
14010
15022
  els['sub-win-body'].innerHTML = html;
14011
15023
  els['sub-win'].classList.toggle('memory-lab-window', !!(state.activeSubWindowView && state.activeSubWindowView.name === 'memoryLab'));
14012
15024
  els['sub-win-overlay'].classList.add('open');
15025
+ updateApplicationInertState();
15026
+ requestAnimationFrame(focusSubWindowInitial);
15027
+ };
15028
+
15029
+ window.handleRightTabKey = function(event) {
15030
+ if (!event) return;
15031
+ var tabs = Array.prototype.slice.call((els['right-tabs'] || document).querySelectorAll('.tab-btn[data-tab]'));
15032
+ var index = tabs.indexOf(event.currentTarget);
15033
+ if (index < 0) return;
15034
+ var next = index;
15035
+ if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
15036
+ else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
15037
+ else if (event.key === 'Home') next = 0;
15038
+ else if (event.key === 'End') next = tabs.length - 1;
15039
+ else return;
15040
+ event.preventDefault();
15041
+ var tab = tabs[next].getAttribute('data-tab');
15042
+ window.switchRightTab(tab);
15043
+ tabs[next].focus();
14013
15044
  };
14014
15045
 
14015
15046
  window.closeSubWin = function() {
@@ -14023,6 +15054,7 @@ window.closeSubWin = function() {
14023
15054
  requestAnimationFrame(function() {
14024
15055
  els['sub-win-body'].scrollTop = previous.scrollTop || 0;
14025
15056
  state.restoringSubWindow = false;
15057
+ focusSubWindowInitial();
14026
15058
  });
14027
15059
  return;
14028
15060
  }
@@ -14030,6 +15062,12 @@ window.closeSubWin = function() {
14030
15062
  state.activeSubWindowView = null;
14031
15063
  var win = els['sub-win'];
14032
15064
  if (win) { win.classList.remove('memory-lab-window'); win.style.left = ''; win.style.top = ''; win.style.margin = ''; }
15065
+ var restoreFocus = subWindowOriginFocus;
15066
+ subWindowOriginFocus = null;
15067
+ updateApplicationInertState();
15068
+ requestAnimationFrame(function() {
15069
+ if (restoreFocus && restoreFocus.isConnected && typeof restoreFocus.focus === 'function') restoreFocus.focus({ preventScroll: true });
15070
+ });
14033
15071
  };
14034
15072
 
14035
15073
  function rerenderActiveSubWindowForLanguage() {
@@ -14078,19 +15116,19 @@ window.openSettings = function(tab) {
14078
15116
  var activeTab = tab || state.settingsActiveTab || 'general';
14079
15117
  var refreshingSettings = els['sub-win-overlay'].classList.contains('open') && state.activeSubWindowView && state.activeSubWindowView.name === 'settings';
14080
15118
  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>' +
15119
+ var html = '<div class="settings-tabs" role="tablist" aria-label="' + escAttr(t('settings.title')) + '">' +
15120
+ '<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>' +
15121
+ '<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>' +
15122
+ '<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>' +
15123
+ '<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>' +
15124
+ '<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
15125
  '</div>' +
14088
- '<div class="stab-panel" id="stab-general" data-lazy="1"></div>' +
15126
+ '<div class="stab-panel" id="stab-general" role="tabpanel" aria-labelledby="settings-tab-general" data-lazy="1"></div>' +
14089
15127
  // 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>');
15128
+ (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>') +
15129
+ (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>') +
15130
+ (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>') +
15131
+ (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
15132
  state.activeSubWindowView = { name: 'settings', tab: activeTab };
14095
15133
  if (refreshingSettings) state.restoringSubWindow = true;
14096
15134
  window.openSubWin(t('settings.title'), html);
@@ -14457,7 +15495,12 @@ window.settingsTab = function(name) {
14457
15495
  state.settingsActiveTab = name || 'general';
14458
15496
  var btns = document.querySelectorAll('.stab-btn');
14459
15497
  for (var i = 0; i < btns.length; i++) {
14460
- btns[i].classList.toggle('active', btns[i].getAttribute('data-stab') === name);
15498
+ var selected = btns[i].getAttribute('data-stab') === name;
15499
+ btns[i].classList.toggle('active', selected);
15500
+ if (btns[i].closest('.settings-tabs:not(.plugin-tabs)')) {
15501
+ btns[i].setAttribute('aria-selected', selected ? 'true' : 'false');
15502
+ btns[i].tabIndex = selected ? 0 : -1;
15503
+ }
14461
15504
  }
14462
15505
  // Render lazy panels on first access for faster initial open
14463
15506
  var panelId = 'stab-' + name;
@@ -14483,6 +15526,7 @@ window.settingsTab = function(name) {
14483
15526
  var panels = document.querySelectorAll('.stab-panel');
14484
15527
  for (var j = 0; j < panels.length; j++) {
14485
15528
  panels[j].classList.toggle('active', panels[j].id === panelId);
15529
+ panels[j].hidden = panels[j].id !== panelId;
14486
15530
  }
14487
15531
  if (name === 'models' && panel && panel.getAttribute('data-lazy') !== '1') window.loadGlobalAgentPrompt();
14488
15532
  };
@@ -15290,9 +16334,26 @@ window.archiveCurrent = function() {
15290
16334
  return window.archiveConv(currentId);
15291
16335
  };
15292
16336
 
15293
- window.loadArchive = function(idx, scope) {
15294
- var item = (scope === 'all' ? state.allArchives : state.workspaceArchives)[idx];
15295
- if (!item || !api.readArchive) return;
16337
+ window.handleSettingsTabKey = function(event) {
16338
+ if (!event) return;
16339
+ var tabs = Array.prototype.slice.call(document.querySelectorAll('.settings-tabs:not(.plugin-tabs) [role="tab"]'));
16340
+ var index = tabs.indexOf(event.currentTarget);
16341
+ if (index < 0) return;
16342
+ var next = index;
16343
+ if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
16344
+ else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
16345
+ else if (event.key === 'Home') next = 0;
16346
+ else if (event.key === 'End') next = tabs.length - 1;
16347
+ else return;
16348
+ event.preventDefault();
16349
+ var name = tabs[next].getAttribute('data-stab');
16350
+ window.settingsTab(name);
16351
+ tabs[next].focus();
16352
+ };
16353
+
16354
+ window.loadArchive = function(idx, scope) {
16355
+ var item = (scope === 'all' ? state.allArchives : state.workspaceArchives)[idx];
16356
+ if (!item || !api.readArchive) return;
15296
16357
  api.readArchive(item.id || item.name).then(function(content) {
15297
16358
  window.openSubWin(t('archive.titlePrefix') + ': ' + item.name, '<pre style="white-space:pre-wrap;word-break:break-word;background:var(--glass-bg-1);border:1px solid var(--glass-border-1);border-radius:8px;padding:12px;font-size:11px;max-height:65vh;overflow:auto;">' + esc(content || '') + '</pre>');
15298
16359
  }).catch(function(err) {
@@ -15624,10 +16685,10 @@ function renderFlowItem(work, idx, expanded) {
15624
16685
  componentsHtml += '</div>';
15625
16686
  }
15626
16687
  return '<div class="flow-item">' +
15627
- '<div class="flow-item-header' + (expanded ? '' : ' collapsed') + '" onclick="this.classList.toggle(\'collapsed\')">' +
16688
+ '<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
16689
  '<span class="arrow">></span><span class="flow-item-name">' + esc(work.name || t('flow.untitled')) + '</span>' +
15629
16690
  '<span class="flow-item-type">' + (work.components ? work.components.length : 0) + ' ' + esc(t('flow.components')) + '</span>' +
15630
- '</div>' +
16691
+ '</button>' +
15631
16692
  '<div class="flow-item-children" style="margin-bottom:4px;">' +
15632
16693
  '<div style="display:flex;gap:4px;padding:4px 0;">' +
15633
16694
  '<button class="sec-btn" style="flex:1;font-size:10px;" onclick="window.addFlowComp(' + idx + ',\'dialog\')">+ ' + esc(t('flow.dialog')) + '</button>' +
@@ -15642,7 +16703,7 @@ function renderFlowItem(work, idx, expanded) {
15642
16703
  window.newFlowWork = function() {
15643
16704
  var html = '<div style="padding:10px;">' +
15644
16705
  '<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()">' +
16706
+ '<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
16707
  '<div style="margin-top:12px;display:flex;gap:8px;">' +
15647
16708
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewFlowWork()">' + esc(t('common.create')) + '</button>' +
15648
16709
  '<button class="sec-btn" onclick="window.showFlowEditor()">' + esc(t('common.cancel')) + '</button>' +
@@ -16351,14 +17412,25 @@ function renderTreeNodes(nodes, parent, depth) {
16351
17412
  var n = nodes[i];
16352
17413
  var div = document.createElement('div');
16353
17414
  div.className = 'ft-item';
17415
+ div.setAttribute('role', 'treeitem');
17416
+ var treeRoot = parent.closest ? (parent.closest('[role="tree"]') || parent) : parent;
17417
+ div.tabIndex = treeRoot.querySelector && treeRoot.querySelector('[role="treeitem"][tabindex="0"]') ? -1 : 0;
17418
+ div.addEventListener('keydown', window.handleFileTreeKey);
17419
+ div.addEventListener('focus', function() {
17420
+ var root = this.closest('[role="tree"]');
17421
+ if (!root) return;
17422
+ Array.prototype.forEach.call(root.querySelectorAll('[role="treeitem"]'), function(item) { item.tabIndex = item === document.activeElement ? 0 : -1; });
17423
+ });
16354
17424
  div.style.paddingLeft = (8 + depth * 14) + 'px';
16355
17425
  var icon = n.type === 'directory' ? iconSvg('folder', 'Folder', 'small') : iconSvg('file', 'File', 'small');
16356
17426
  if (n.type === 'directory') {
17427
+ div.setAttribute('aria-expanded', 'false');
16357
17428
  div.innerHTML = '<span class="ft-toggle collapsed">' + iconSvg('chevron-right', t('fileTree.toggleFolder'), 'tiny') + '</span>' +
16358
17429
  '<span class="ft-icon">' + icon + '</span><span class="ft-name">' + esc(n.name) + '</span>';
16359
17430
  parent.appendChild(div);
16360
17431
  var childContainer = document.createElement('div');
16361
17432
  childContainer.className = 'ft-children';
17433
+ childContainer.setAttribute('role', 'group');
16362
17434
  childContainer.style.display = 'none';
16363
17435
  parent.appendChild(childContainer);
16364
17436
  div.onclick = function(node, toggle, children, childDepth) {
@@ -16367,6 +17439,7 @@ function renderTreeNodes(nodes, parent, depth) {
16367
17439
  var opening = children.style.display === 'none';
16368
17440
  children.style.display = opening ? 'block' : 'none';
16369
17441
  toggle.classList.toggle('collapsed', !opening);
17442
+ this.setAttribute('aria-expanded', opening ? 'true' : 'false');
16370
17443
  if (!opening || children.getAttribute('data-loaded') === 'true') return;
16371
17444
  children.setAttribute('data-loading', 'true');
16372
17445
  try {
@@ -16474,8 +17547,7 @@ window.renderNativeEditor = function() {
16474
17547
  }
16475
17548
  window.renderEditorGutter(code);
16476
17549
  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);
17550
+ window.renderEditorCaretStatus();
16479
17551
  if (els['editor-dirty']) els['editor-dirty'].textContent = code !== state.editorOriginal ? 'modified' : '';
16480
17552
  if (els['editor-vim-mode']) els['editor-vim-mode'].textContent = state.editorVimEnabled ? state.editorVimMode.toUpperCase() : 'INSERT';
16481
17553
  if (state.editorPreview && els['editor-md-preview']) els['editor-md-preview'].innerHTML = renderMessageContent(code);
@@ -16517,6 +17589,7 @@ window.editorSetValue = function(value, preserveUndo) {
16517
17589
  if (!ta) return;
16518
17590
  if (!preserveUndo && ta.value !== value) { state.editorUndo.push(ta.value); if (state.editorUndo.length > 100) state.editorUndo.shift(); state.editorRedo = []; }
16519
17591
  ta.value = value;
17592
+ state.editorCompletionCache = [];
16520
17593
  window.renderNativeEditor();
16521
17594
  };
16522
17595
 
@@ -16545,23 +17618,100 @@ window.editorReplaceSelection = function(text) {
16545
17618
  window.renderNativeEditor();
16546
17619
  };
16547
17620
 
16548
- window.requestEditorCompletion = async function() {
17621
+ window.applyEditorCompletionDelta = function(payload) {
17622
+ payload = payload || {};
17623
+ if (String(payload.requestId || '') !== String(state.editorCompletionRequest || '')) return;
17624
+ var delta = String(payload.text || '');
17625
+ if (!delta || !state.editorCompletionAnchor) return;
17626
+ state.editorCompletionStreamText = (state.editorCompletionStreamText || '') + delta;
17627
+ state.editorCompletionText = state.editorCompletionStreamText.slice(0, 1200);
17628
+ window.renderEditorGhostText();
17629
+ if (els['editor-completion']) { els['editor-completion'].textContent = ''; els['editor-completion'].classList.remove('open'); }
17630
+ };
17631
+
17632
+ function editorCompletionValueKey(value) {
17633
+ var text = String(value || '');
17634
+ var hash = 2166136261;
17635
+ for (var i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);
17636
+ return text.length + ':' + (hash >>> 0);
17637
+ }
17638
+
17639
+ function editorCompletionAnchorKey(anchor) {
17640
+ return [anchor.path, anchor.start, anchor.end, editorCompletionValueKey(anchor.value)].join('\u0000');
17641
+ }
17642
+
17643
+ function editorCompletionCacheFind(anchor) {
17644
+ var cache = Array.isArray(state.editorCompletionCache) ? state.editorCompletionCache : [];
17645
+ state.editorCompletionCache = cache;
17646
+ var now = Date.now();
17647
+ var key = editorCompletionAnchorKey(anchor);
17648
+ for (var i = cache.length - 1; i >= 0; i--) {
17649
+ var item = cache[i];
17650
+ if (!item || item.expiresAt <= now) { cache.splice(i, 1); continue; }
17651
+ if (item.key === key) {
17652
+ cache.splice(i, 1);
17653
+ cache.unshift(item);
17654
+ return item;
17655
+ }
17656
+ }
17657
+ return null;
17658
+ }
17659
+
17660
+ function editorCompletionCachePut(anchor, text, ttl) {
17661
+ var cache = Array.isArray(state.editorCompletionCache) ? state.editorCompletionCache : [];
17662
+ var key = editorCompletionAnchorKey(anchor);
17663
+ state.editorCompletionCache = cache.filter(function(item) { return !item || item.key !== key; });
17664
+ state.editorCompletionCache.unshift({ key: key, text: text, expiresAt: Date.now() + ttl });
17665
+ if (state.editorCompletionCache.length > 16) state.editorCompletionCache.length = 16;
17666
+ }
17667
+
17668
+ window.requestEditorCompletion = async function(options) {
17669
+ options = options || {};
17670
+ var force = options.force === true;
16549
17671
  var ta = els['editor-textarea']; if (!ta || !state.editorPath || !api.editorComplete) return;
16550
17672
  var requestId = ++state.editorCompletionRequest;
16551
17673
  var pos = ta.selectionStart;
16552
17674
  var anchor = { path: state.editorPath, value: ta.value, start: pos, end: ta.selectionEnd };
16553
17675
  state.editorCompletionAnchor = anchor;
17676
+ var cached = force ? null : editorCompletionCacheFind(anchor);
17677
+ if (cached) {
17678
+ state.editorCompletionInFlight = false;
17679
+ state.editorCompletionStreamText = '';
17680
+ state.editorCompletionText = cached.text || '';
17681
+ state.editorCompletionAnchor = state.editorCompletionText ? anchor : null;
17682
+ window.renderEditorGhostText();
17683
+ if (els['editor-completion']) { els['editor-completion'].textContent = ''; els['editor-completion'].classList.remove('open'); }
17684
+ return;
17685
+ }
16554
17686
  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) });
17687
+ state.editorCompletionInFlight = true;
17688
+ state.editorCompletionStreamText = '';
17689
+ var beforeStart = Math.max(0, pos - 3200);
17690
+ var afterEnd = Math.min(ta.value.length, pos + 800);
17691
+ var result;
17692
+ try {
17693
+ result = await api.editorComplete({ requestId: String(requestId), path: state.editorPath, before: ta.value.slice(beforeStart, pos), after: ta.value.slice(pos, afterEnd) });
17694
+ } catch (error) {
17695
+ result = { ok: false, text: '', error: error && error.message ? error.message : String(error) };
17696
+ } finally {
17697
+ if (requestId === state.editorCompletionRequest) state.editorCompletionInFlight = false;
17698
+ }
16558
17699
  if (requestId !== state.editorCompletionRequest) return;
16559
- if (!window.editorAnchorMatches(anchor)) { window.dismissEditorCompletion(); window.scheduleEditorCompletion(); return; }
16560
- state.editorCompletionText = result && result.ok ? String(result.text || '') : '';
17700
+ // Input/caret handlers already schedule the replacement request. Do not
17701
+ // recursively schedule here: that doubled provider traffic on fast edits.
17702
+ if (!window.editorAnchorMatches(anchor)) return;
17703
+ var suggestion = result && result.ok ? String(result.text || '').slice(0, 1200) : '';
17704
+ if (!suggestion.trim()) suggestion = '';
17705
+ state.editorCompletionText = suggestion;
17706
+ state.editorCompletionStreamText = '';
17707
+ state.editorCompletionAnchor = suggestion ? anchor : null;
17708
+ editorCompletionCachePut(anchor, suggestion, suggestion ? 15000 : 2000);
16561
17709
  window.renderEditorGhostText();
16562
17710
  if (els['editor-completion']) {
16563
- els['editor-completion'].textContent = state.editorCompletionText ? '' : (result.error || 'No completion');
16564
- els['editor-completion'].classList.toggle('open', !state.editorCompletionText);
17711
+ // Empty/aborted/timeout responses are normal for an inline predictor. The
17712
+ // editor should stay quiet instead of flashing an error on every keystroke.
17713
+ els['editor-completion'].textContent = '';
17714
+ els['editor-completion'].classList.remove('open');
16565
17715
  }
16566
17716
  };
16567
17717
 
@@ -16590,7 +17740,7 @@ window.scheduleEditorCompletion = function() {
16590
17740
  state.editorCompletionTimer = setTimeout(function() {
16591
17741
  state.editorCompletionTimer = null;
16592
17742
  window.requestEditorCompletion();
16593
- }, 180);
17743
+ }, 300);
16594
17744
  };
16595
17745
 
16596
17746
  window.editorCaretSignature = function() {
@@ -16609,7 +17759,8 @@ window.handleEditorCaretChange = function() {
16609
17759
  var signature = window.editorCaretSignature();
16610
17760
  if (signature === state.editorCaretSignature) return;
16611
17761
  state.editorCaretSignature = signature;
16612
- window.renderNativeEditor();
17762
+ window.renderEditorCaretStatus();
17763
+ window.renderEditorGhostText();
16613
17764
  window.scheduleEditorCompletion();
16614
17765
  };
16615
17766
 
@@ -16617,7 +17768,24 @@ window.acceptEditorCompletion = function() {
16617
17768
  if (state.editorCompletionText && window.editorCompletionAnchorIsCurrent()) window.editorReplaceSelection(state.editorCompletionText);
16618
17769
  window.dismissEditorCompletion();
16619
17770
  };
16620
- window.dismissEditorCompletion = function() { state.editorCompletionText = ''; state.editorCompletionAnchor = null; state.editorCompletionRequest++; if (els['editor-completion']) els['editor-completion'].classList.remove('open'); window.renderEditorGhostText(); };
17771
+ window.dismissEditorCompletion = function() {
17772
+ state.editorCompletionText = '';
17773
+ state.editorCompletionStreamText = '';
17774
+ state.editorCompletionAnchor = null;
17775
+ state.editorCompletionRequest++;
17776
+ if (state.editorCompletionInFlight && api.editorCompleteCancel) {
17777
+ try { Promise.resolve(api.editorCompleteCancel()).catch(function() {}); } catch (_) {}
17778
+ }
17779
+ if (els['editor-completion']) { els['editor-completion'].textContent = ''; els['editor-completion'].classList.remove('open'); }
17780
+ window.renderEditorGhostText();
17781
+ };
17782
+
17783
+ window.renderEditorCaretStatus = function() {
17784
+ var ta = els['editor-textarea'];
17785
+ if (!ta) return;
17786
+ var before = String(ta.value || '').slice(0, ta.selectionStart || 0).split('\n');
17787
+ if (els['editor-position']) els['editor-position'].textContent = before.length + ':' + (before[before.length - 1].length + 1);
17788
+ };
16621
17789
 
16622
17790
  window.requestEditorAssist = async function() {
16623
17791
  var ta = els['editor-textarea']; if (!ta || !state.editorPath || !api.editorAssist) return;
@@ -17223,6 +18391,42 @@ function rememberBrowserUrl(url, force, target) {
17223
18391
  }
17224
18392
  }
17225
18393
 
18394
+ window.handleFileTreeKey = function(event) {
18395
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
18396
+ var item = event.currentTarget;
18397
+ var root = item.closest('[role="tree"]');
18398
+ if (!root) return false;
18399
+ if (event.key === 'Enter' || event.key === ' ') return window.activateButtonLike(event);
18400
+ if (event.key === 'ArrowRight') {
18401
+ if (item.hasAttribute('aria-expanded') && item.getAttribute('aria-expanded') !== 'true') {
18402
+ event.preventDefault(); item.click(); return true;
18403
+ }
18404
+ var group = item.nextElementSibling;
18405
+ var child = group && group.matches('[role="group"]') ? group.querySelector('[role="treeitem"]') : null;
18406
+ if (child) { event.preventDefault(); child.focus({ preventScroll:true }); return true; }
18407
+ return false;
18408
+ }
18409
+ if (event.key === 'ArrowLeft') {
18410
+ if (item.getAttribute('aria-expanded') === 'true') { event.preventDefault(); item.click(); return true; }
18411
+ var ownerGroup = item.parentElement && item.parentElement.matches('[role="group"]') ? item.parentElement : null;
18412
+ var parentItem = ownerGroup && ownerGroup.previousElementSibling && ownerGroup.previousElementSibling.matches('[role="treeitem"]') ? ownerGroup.previousElementSibling : null;
18413
+ if (parentItem) { event.preventDefault(); parentItem.focus({ preventScroll:true }); return true; }
18414
+ return false;
18415
+ }
18416
+ var visible = Array.prototype.slice.call(root.querySelectorAll('[role="treeitem"]')).filter(function(node) { return node.getClientRects().length > 0; });
18417
+ var index = visible.indexOf(item);
18418
+ var next = index;
18419
+ if (event.key === 'ArrowDown') next = Math.min(visible.length - 1, index + 1);
18420
+ else if (event.key === 'ArrowUp') next = Math.max(0, index - 1);
18421
+ else if (event.key === 'Home') next = 0;
18422
+ else if (event.key === 'End') next = visible.length - 1;
18423
+ else return false;
18424
+ if (next < 0 || !visible[next]) return false;
18425
+ event.preventDefault();
18426
+ visible[next].focus({ preventScroll:true });
18427
+ return true;
18428
+ };
18429
+
17226
18430
  function waitForBrowserCreationFloor() {
17227
18431
  var remaining = NEWMARK_BROWSER_MIN_CREATE_DELAY_MS - (Date.now() - newmarkRendererStartedAt);
17228
18432
  if (remaining <= 0) return Promise.resolve();
@@ -17555,7 +18759,8 @@ function applyBackendConversations(items, activeId, workspaceId) {
17555
18759
  updatedAt: item.updatedAt || '',
17556
18760
  pinned: !!item.pinned,
17557
18761
  pinnedAt: item.pinnedAt || '',
17558
- order: Number(item.order || 0)
18762
+ order: Number(item.order || 0),
18763
+ branchCommunication: !!item.branchCommunication
17559
18764
  });
17560
18765
  }
17561
18766
  // A stale list response can arrive between creating a conversation locally
@@ -17682,16 +18887,22 @@ function renderConversations() {
17682
18887
  }
17683
18888
  var div = document.createElement('div');
17684
18889
  div.className = 'conv-item' + (conv.active ? ' active' : '');
18890
+ div.setAttribute('role', 'listitem');
18891
+ div.setAttribute('tabindex', conv.active ? '0' : '-1');
18892
+ div.setAttribute('aria-current', conv.active ? 'true' : 'false');
17685
18893
  div.setAttribute('draggable', 'true');
17686
18894
  div.setAttribute('data-conversation-id', String(conv.id || ''));
17687
18895
  if (runtimeState && ['running', 'stopping', 'force_restarting'].indexOf(String(runtimeState.status || '')) >= 0) div.classList.add('marquee-border');
17688
18896
  var runtimeBadge = runtimeState && runtimeState.status && runtimeState.status !== 'idle'
17689
18897
  ? '<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 +
18898
+ var branchCommBadge = conv.branchCommunication
18899
+ ? '<span class="conv-branch-comm-badge" title="' + escAttr(t('conversation.branchCommunicationBadge')) + '">' + esc(t('conversation.branchCommunicationBadge')) + '</span>' : '';
18900
+ div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + branchCommBadge + runtimeBadge +
17691
18901
  '<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
17692
18902
  '<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>' +
17693
18903
  '<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
18904
  div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
18905
+ div.addEventListener('keydown', window.handleConversationKey);
17695
18906
  div.addEventListener('dragstart', function(event) {
17696
18907
  this.classList.add('dragging');
17697
18908
  if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', this.getAttribute('data-conversation-id') || ''); }
@@ -17721,6 +18932,30 @@ function renderConversations() {
17721
18932
  updateWorkspaceGate();
17722
18933
  }
17723
18934
 
18935
+ window.handleConversationKey = function(event) {
18936
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
18937
+ if (event.key === 'Enter' || event.key === ' ') return window.activateButtonLike(event);
18938
+ var items = Array.prototype.slice.call(document.querySelectorAll('#conversation-list .conv-item'));
18939
+ var index = items.indexOf(event.currentTarget);
18940
+ if (index < 0 || !items.length) return false;
18941
+ var next = index;
18942
+ if (event.key === 'ArrowDown') next = (index + 1) % items.length;
18943
+ else if (event.key === 'ArrowUp') next = (index - 1 + items.length) % items.length;
18944
+ else if (event.key === 'Home') next = 0;
18945
+ else if (event.key === 'End') next = items.length - 1;
18946
+ else return false;
18947
+ event.preventDefault();
18948
+ var nextId = items[next].getAttribute('data-conversation-id');
18949
+ items[next].click();
18950
+ requestAnimationFrame(function() {
18951
+ var replacement = Array.prototype.slice.call(document.querySelectorAll('#conversation-list .conv-item')).find(function(item) {
18952
+ return item.getAttribute('data-conversation-id') === nextId;
18953
+ });
18954
+ if (replacement) replacement.focus({ preventScroll:true });
18955
+ });
18956
+ return true;
18957
+ };
18958
+
17724
18959
  window.newConversationOld = function() {
17725
18960
  var id = 'conv-' + Date.now();
17726
18961
  var summary = t('left.newChat') + ' ' + (state.conversations.length + 1);
@@ -17754,7 +18989,7 @@ window.archiveConvOld = function(idx) {
17754
18989
  }
17755
18990
  };
17756
18991
 
17757
- window.newConversation = function(workspaceReference) {
18992
+ window.newConversation = function(workspaceReference, branchCommunication) {
17758
18993
  if (workspaceReference && workspaceReference !== state.currentWorkspaceId) {
17759
18994
  window.selectWorkspace(workspaceReference);
17760
18995
  }
@@ -17784,6 +19019,13 @@ window.newConversation = function(workspaceReference) {
17784
19019
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
17785
19020
  state.activeBackendConversationId = String((s && s.conversationId) || id);
17786
19021
  if (s) applyConversationSnapshot(s, id);
19022
+ if (branchCommunication && api.setConversationBranchCommunication) {
19023
+ api.setConversationBranchCommunication(target, true).then(function() {
19024
+ var convsNow = currentWorkspaceConversations();
19025
+ var created = convsNow.find(function(item){ return String(item && item.id) === id; });
19026
+ if (created) { created.branchCommunication = true; renderConversations(); }
19027
+ }).catch(function(){});
19028
+ }
17787
19029
  }).then(function() {
17788
19030
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
17789
19031
  state.foregroundConversationHoldId = '';
@@ -17810,6 +19052,7 @@ window.showNewConversationPage = function() {
17810
19052
  var html = '<div style="display:flex;flex-direction:column;gap:12px;padding:4px 2px;">' +
17811
19053
  '<div style="font-size:12px;color:var(--text-dim);line-height:1.5;">' + esc(t('workspace.newConversationDesc')) + '</div>' +
17812
19054
  '<div class="auto-input-group"><label>' + esc(t('status.workspace')) + '</label><select id="new-conv-ws">' + options + '</select></div>' +
19055
+ '<div class="auto-input-group" style="flex-direction:row;align-items:center;gap:8px;"><label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;"><input type="checkbox" id="new-conv-branch-comm" style="accent-color:var(--accent);">' + esc(t('conversation.branchCommunication')) + '</label></div>' +
17813
19056
  '<div style="display:flex;gap:8px;">' +
17814
19057
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewConversationFromPage()">' + esc(t('workspace.createConversation')) + '</button>' +
17815
19058
  '<button class="sec-btn" style="flex:1;" onclick="window.showNewWorkspaceDialog()">' + esc(t('workspace.createAction')) + '</button>' +
@@ -17826,11 +19069,13 @@ window.doNewConversationFromPage = function() {
17826
19069
  window.showNewWorkspaceDialog();
17827
19070
  return;
17828
19071
  }
19072
+ var branchComm = document.getElementById('new-conv-branch-comm');
19073
+ var branchCommunication = !!(branchComm && branchComm.checked);
17829
19074
  window.switchToWorkspace(identity).then(function(ws) {
17830
19075
  state.currentWorkspace = (ws && ws.name) || state.currentWorkspace;
17831
19076
  state.currentWorkspaceId = workspaceIdentity(ws) || state.currentWorkspaceId || identity;
17832
19077
  window.closeSubWin();
17833
- window.newConversation(state.currentWorkspaceId);
19078
+ window.newConversation(state.currentWorkspaceId, branchCommunication);
17834
19079
  }).catch(function(err) {
17835
19080
  showUiNotice(t('workspace.selectAction') + ': ' + err.message, 'error', 'workspace-new-conversation-' + identity);
17836
19081
  });
@@ -17963,6 +19208,25 @@ window.archiveConv = function(conversationId) {
17963
19208
  state.activeBackendConversationId = nextActiveId;
17964
19209
  if (els['chat-area']) els['chat-area'].innerHTML = '';
17965
19210
  }
19211
+ // Archiving is a destructive handoff: remove the target's Flow takeover
19212
+ // locally at click time as well as removing its conversation row. The
19213
+ // backend cancellation/manifest write is asynchronous; a late Flow
19214
+ // promise must not leave a visible running bubble over the replacement
19215
+ // conversation while the archive is already in flight.
19216
+ if (state.flowTakeovers) {
19217
+ var archivedFlowKey = runtimeKeyFor(targetRuntime.workspaceId, targetRuntime.conversationId);
19218
+ var archivedFlowRecord = state.flowTakeovers[archivedFlowKey];
19219
+ if (archivedFlowRecord) {
19220
+ archivedFlowRecord.running = false;
19221
+ archivedFlowRecord.paused = false;
19222
+ archivedFlowRecord.runtimeLease = null;
19223
+ archivedFlowRecord.queueLease = null;
19224
+ delete state.flowTakeovers[archivedFlowKey];
19225
+ }
19226
+ if (priorActiveId === targetId && window.renderFlowTakeover) {
19227
+ window.renderFlowTakeover(false, '', { target: targetRuntime });
19228
+ }
19229
+ }
17966
19230
  setConversationRuntimeState(targetRuntime, 'idle', '');
17967
19231
  setWorking(!!runningConversationRecord(activeConversationId()));
17968
19232
  renderConversations();
@@ -18809,37 +20073,117 @@ window.reindexMemoryLab = function() {
18809
20073
  });
18810
20074
  };
18811
20075
 
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>');
20076
+ // === Plugin / Skills Market ===
20077
+ var PLUGIN_TABS = ['mcp', 'dsh', 'installed', 'market', 'github'];
20078
+
20079
+ function pluginText(key, values) {
20080
+ var output = t(key);
20081
+ Object.keys(values || {}).forEach(function(name) {
20082
+ output = output.replace(new RegExp('\\{' + name + '\\}', 'g'), function() { return String(values[name]); });
20083
+ });
20084
+ return output;
20085
+ }
20086
+
20087
+ function pluginRequestActive(tab, generation) {
20088
+ return generation === state.pluginPanelGeneration
20089
+ && state.pluginActiveTab === tab
20090
+ && !!(state.activeSubWindowView && state.activeSubWindowView.name === 'plugins')
20091
+ && !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'))
20092
+ && !!document.getElementById('plugin-panel');
20093
+ }
20094
+
20095
+ function pluginPanelState(message, kind, retryCall) {
20096
+ return '<div class="plugin-panel-state' + (kind === 'error' ? ' error' : '') + '" role="' + (kind === 'error' ? 'alert' : 'status') + '"' + (kind === 'loading' ? ' aria-live="polite" aria-busy="true"' : '') + '>' +
20097
+ '<span>' + esc(message) + '</span>' +
20098
+ (retryCall ? '<button type="button" class="sec-btn" onclick="' + retryCall + '">' + esc(t('plugins.retry')) + '</button>' : '') +
20099
+ '</div>';
20100
+ }
20101
+
20102
+ function pluginTabMarkup(tab, label) {
20103
+ var selected = state.pluginActiveTab === tab;
20104
+ 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>';
20105
+ }
20106
+
20107
+ window.syncPluginTabs = function(focusTab) {
20108
+ var labels = { mcp: t('plugins.tabMcp'), dsh: t('plugins.tabDsh'), installed: t('plugins.tabSkills'), market: t('plugins.tabMarket'), github: t('plugins.tabGithub') };
20109
+ PLUGIN_TABS.forEach(function(tab) {
20110
+ var button = document.getElementById('plugin-tab-' + tab);
20111
+ if (!button) return;
20112
+ var selected = tab === state.pluginActiveTab;
20113
+ button.classList.toggle('active', selected);
20114
+ button.setAttribute('aria-selected', selected ? 'true' : 'false');
20115
+ button.tabIndex = selected ? 0 : -1;
20116
+ button.textContent = labels[tab];
20117
+ });
20118
+ var tablist = document.querySelector('.plugin-tabs[role="tablist"]');
20119
+ if (tablist) tablist.setAttribute('aria-label', t('plugins.title'));
20120
+ if (els['sub-win-title']) els['sub-win-title'].textContent = t('plugins.title');
20121
+ var panel = document.getElementById('plugin-panel');
20122
+ if (panel) panel.setAttribute('aria-labelledby', 'plugin-tab-' + state.pluginActiveTab);
20123
+ if (focusTab) {
20124
+ var activeButton = document.getElementById('plugin-tab-' + state.pluginActiveTab);
20125
+ if (activeButton) activeButton.focus({ preventScroll: true });
20126
+ }
18815
20127
  };
18816
20128
 
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();
20129
+ window.handlePluginTabKey = function(event) {
20130
+ if (!event || PLUGIN_TABS.indexOf(state.pluginActiveTab) < 0) return;
20131
+ var index = PLUGIN_TABS.indexOf(state.pluginActiveTab);
20132
+ if (event.key === 'ArrowRight') index = (index + 1) % PLUGIN_TABS.length;
20133
+ else if (event.key === 'ArrowLeft') index = (index - 1 + PLUGIN_TABS.length) % PLUGIN_TABS.length;
20134
+ else if (event.key === 'Home') index = 0;
20135
+ else if (event.key === 'End') index = PLUGIN_TABS.length - 1;
20136
+ else return;
20137
+ event.preventDefault();
20138
+ window.showPluginList(PLUGIN_TABS[index], { focusTab: true });
20139
+ };
20140
+
20141
+ window.selectPluginTab = function(tab) {
20142
+ window.showPluginList(tab, { focusTab: false });
20143
+ };
20144
+
20145
+ window.showPluginList = function(tab, options) {
20146
+ var requested = PLUGIN_TABS.indexOf(tab) >= 0 ? tab : (PLUGIN_TABS.indexOf(state.pluginActiveTab) >= 0 ? state.pluginActiveTab : 'mcp');
20147
+ var overlayOpen = !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'));
20148
+ var pluginOpen = overlayOpen && state.activeSubWindowView && state.activeSubWindowView.name === 'plugins' && !!document.getElementById('plugin-panel');
20149
+ state.pluginActiveTab = requested;
20150
+ state.pluginPanelGeneration++;
20151
+ if (!pluginOpen) {
20152
+ var html = '<div class="settings-tabs plugin-tabs" role="tablist" aria-label="' + escAttr(t('plugins.title')) + '">' +
20153
+ pluginTabMarkup('mcp', t('plugins.tabMcp')) +
20154
+ pluginTabMarkup('dsh', t('plugins.tabDsh')) +
20155
+ pluginTabMarkup('installed', t('plugins.tabSkills')) +
20156
+ pluginTabMarkup('market', t('plugins.tabMarket')) +
20157
+ pluginTabMarkup('github', t('plugins.tabGithub')) +
20158
+ '</div><div id="plugin-panel" class="plugin-panel" role="tabpanel" tabindex="0" aria-labelledby="plugin-tab-' + requested + '"></div>';
20159
+ if (overlayOpen && state.activeSubWindowView) {
20160
+ window.openSubWin(t('plugins.title'), html);
20161
+ state.activeSubWindowView = { name: 'plugins', tab: requested };
20162
+ } else {
20163
+ state.activeSubWindowView = { name: 'plugins', tab: requested };
20164
+ window.openSubWin(t('plugins.title'), html);
20165
+ }
20166
+ if (els['sub-win']) els['sub-win'].classList.remove('memory-lab-window');
20167
+ } else {
20168
+ state.activeSubWindowView = { name: 'plugins', tab: requested };
20169
+ }
20170
+ window.syncPluginTabs(!pluginOpen || !!(options && options.focusTab));
20171
+ var panel = document.getElementById('plugin-panel');
20172
+ if (panel) panel.replaceChildren();
20173
+ if (requested === 'mcp') window.renderMcpManager();
20174
+ else if (requested === 'dsh') window.renderDshPlugin();
20175
+ else if (requested === 'market') window.renderSkillsMarket();
20176
+ else if (requested === 'github') window.renderGithubCliPanel();
18835
20177
  else window.renderInstalledSkills();
18836
20178
  };
18837
20179
 
18838
20180
  window.renderInstalledSkills = function() {
18839
20181
  var panel = document.getElementById('plugin-panel');
18840
- if (!panel) return;
20182
+ if (!panel || state.pluginActiveTab !== 'installed') return;
20183
+ var generation = ++state.pluginPanelGeneration;
18841
20184
  var render = function(items) {
18842
20185
  state.skills = items || [];
20186
+ if (!pluginRequestActive('installed', generation)) return;
18843
20187
  if (!items || !items.length) {
18844
20188
  panel.innerHTML = '<div style="font-size:12px;color:var(--text-dim);padding:18px;text-align:center;">' + esc(t('plugins.noInstalled')) + '</div>';
18845
20189
  return;
@@ -18880,14 +20224,15 @@ window.refreshSkillsRuntime = function(next) {
18880
20224
 
18881
20225
  window.renderSkillsMarket = function() {
18882
20226
  var panel = document.getElementById('plugin-panel');
18883
- if (!panel) return;
20227
+ if (!panel || state.pluginActiveTab !== 'market') return;
20228
+ var generation = ++state.pluginPanelGeneration;
18884
20229
  panel.innerHTML = '<div class="provider-card marquee-border">' + esc(t('plugins.discovering')) + '</div>';
18885
20230
  state._skillMarketAll = [];
18886
20231
  state._skillMarketSources = [];
18887
20232
  var done = 0;
18888
20233
  var finish = function() {
18889
20234
  done++;
18890
- if (done >= 2) window.renderSkillsMarketList();
20235
+ if (done >= 2 && pluginRequestActive('market', generation)) window.renderSkillsMarketList();
18891
20236
  };
18892
20237
  if (api.marketSkillSources) api.marketSkillSources().then(function(sources) {
18893
20238
  state._skillMarketSources = sources || [];
@@ -18901,88 +20246,466 @@ window.renderSkillsMarket = function() {
18901
20246
  else finish();
18902
20247
  };
18903
20248
 
20249
+ var DSH_OFFICIAL_URLS = {
20250
+ repo: 'https://github.com/deepseek-ai/deepseek-harness',
20251
+ docs: 'https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/develop/basic/publish.md',
20252
+ npm: 'https://www.npmjs.com/package/@deepseek-ai/dsh'
20253
+ };
20254
+
20255
+ function dshCollection(value) {
20256
+ if (Array.isArray(value)) return value;
20257
+ if (value && typeof value === 'object') return Object.keys(value).map(function(key) {
20258
+ var item = value[key];
20259
+ if (item && typeof item === 'object' && !Array.isArray(item)) return Object.assign({ name: key }, item);
20260
+ return { name: key, value: item };
20261
+ });
20262
+ return [];
20263
+ }
20264
+
20265
+ function dshDisplayValue(value) {
20266
+ if (value === undefined || value === null || value === '') return t('plugins.dshNotAvailable');
20267
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return String(value);
20268
+ try { return JSON.stringify(value); } catch (_) { return String(value); }
20269
+ }
20270
+
20271
+ function dshUniqueStrings(values) {
20272
+ var seen = Object.create(null);
20273
+ return dshCollection(values).map(function(value) {
20274
+ return typeof value === 'string' ? value : dshDisplayValue(value);
20275
+ }).filter(function(value) {
20276
+ var key = String(value || '');
20277
+ if (!key || seen[key]) return false;
20278
+ seen[key] = true;
20279
+ return true;
20280
+ });
20281
+ }
20282
+
20283
+ function dshListMarkup(items, emptyText, warning) {
20284
+ var values = dshCollection(items);
20285
+ if (!values.length) return '<div class="plugin-empty">' + esc(emptyText || t('plugins.dshNone')) + '</div>';
20286
+ return '<div class="dsh-list">' + values.map(function(item) {
20287
+ var label = item && typeof item === 'object'
20288
+ ? (item.name || item.id || item.path || item.file || item.key || dshDisplayValue(item))
20289
+ : item;
20290
+ var detail = item && typeof item === 'object' ? (item.version || item.source || item.value || item.reason || '') : '';
20291
+ 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>';
20292
+ }).join('') + '</div>';
20293
+ }
20294
+
20295
+ function dshLayerMarkup(profile) {
20296
+ var layers = dshCollection(profile && profile.layers);
20297
+ if (!layers.length) return '<div class="plugin-empty">' + esc(t('plugins.dshNone')) + '</div>';
20298
+ return '<div class="dsh-list">' + layers.map(function(layer, index) {
20299
+ var kind = layer && layer.kind ? String(layer.kind) : 'layer';
20300
+ var order = Number.isFinite(Number(layer && layer.order)) ? Number(layer.order) + 1 : index + 1;
20301
+ var file = layer && (layer.path || layer.file) || '';
20302
+ return '<div class="dsh-list-item"><strong>' + esc(order + '. ' + kind + (layer && layer.name ? ' · ' + layer.name : '')) + '</strong>' +
20303
+ (file ? '<div class="provider-meta">' + esc(redactSensitiveText(file)) + '</div>' : '') + '</div>';
20304
+ }).join('') + '</div>';
20305
+ }
20306
+
20307
+ window.openDshOfficial = function(kind) {
20308
+ var url = DSH_OFFICIAL_URLS[kind];
20309
+ if (!url || !api.openWebUrl) {
20310
+ showUiNotice(t('plugins.dshUnavailable'), 'error', 'dsh-web-url');
20311
+ return;
20312
+ }
20313
+ var generation = state.pluginPanelGeneration;
20314
+ Promise.resolve(api.openWebUrl(url)).then(function(result) {
20315
+ if (result && (result.ok === false || result.success === false || result.error)) throw new Error(result.error || t('plugins.dshUnavailable'));
20316
+ }).catch(function(error) {
20317
+ if (pluginRequestActive('dsh', generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'dsh-web-url');
20318
+ });
20319
+ };
20320
+
20321
+ window.renderDshPlugin = function() {
20322
+ var panel = document.getElementById('plugin-panel');
20323
+ if (!panel || state.pluginActiveTab !== 'dsh') return;
20324
+ var generation = ++state.pluginPanelGeneration;
20325
+ panel.innerHTML = pluginPanelState(t('plugins.dshScanning'), 'loading');
20326
+ if (!api.discoverDshCompatibility) {
20327
+ panel.innerHTML = pluginPanelState(t('plugins.dshUnavailable'), 'error');
20328
+ return;
20329
+ }
20330
+ Promise.resolve(api.discoverDshCompatibility()).then(function(snapshot) {
20331
+ if (!pluginRequestActive('dsh', generation)) return;
20332
+ state.dshCompatibility = snapshot && typeof snapshot === 'object' ? snapshot : {};
20333
+ window.renderDshPluginContent();
20334
+ }).catch(function(error) {
20335
+ if (!pluginRequestActive('dsh', generation)) return;
20336
+ var message = t('plugins.dshLoadError') + (error && error.message ? ' ' + error.message : '');
20337
+ panel.innerHTML = pluginPanelState(message, 'error', 'window.renderDshPlugin()');
20338
+ });
20339
+ };
20340
+
20341
+ window.renderDshPluginContent = function() {
20342
+ var panel = document.getElementById('plugin-panel');
20343
+ if (!panel || state.pluginActiveTab !== 'dsh') return;
20344
+ var dsh = state.dshCompatibility || {};
20345
+ var cli = dsh.cli && typeof dsh.cli === 'object' ? dsh.cli : {};
20346
+ var pkg = dsh.package && typeof dsh.package === 'object' ? dsh.package : {};
20347
+ var update = dsh.update && typeof dsh.update === 'object' ? dsh.update : {};
20348
+ var cliPath = cli.path || cli.executable || cli.commandPath || dsh.cliPath || '';
20349
+ var cliVersion = cli.version || dsh.cliVersion || '';
20350
+ var packageVersion = pkg.version || (cli.package && cli.package.version) || cli.packageVersion || dsh.packageVersion || '';
20351
+ var updateChannel = update.channel || update.tag || dsh.updateChannel || '';
20352
+ var updateLocked = update.locked === true || update.versionLocked === true || update.isLocked === true;
20353
+ if (String(updateChannel).toLowerCase() === 'latest' && !updateLocked) updateChannel = t('plugins.dshLatestChannel');
20354
+ var candidates = dshCollection(dsh.mcpCandidates);
20355
+ var configFiles = dshCollection(dsh.configFiles);
20356
+ dshCollection(dsh.profiles).forEach(function(profile) {
20357
+ if (profile && typeof profile === 'object') configFiles = configFiles.concat(dshCollection(profile.configFiles));
20358
+ });
20359
+ configFiles = dshUniqueStrings(configFiles);
20360
+ var html = '<section class="dsh-hero" id="dsh-hero" aria-labelledby="dsh-title">' +
20361
+ '<div class="dsh-hero-head"><div class="dsh-hero-copy"><div class="dsh-title" id="dsh-title">' + esc(t('plugins.dshTitle')) + '</div>' +
20362
+ '<div class="dsh-description">' + esc(t('plugins.dshHelp')) + '</div></div><span class="plugin-status-badge preview">' + esc(t('plugins.dshPreview')) + '</span></div>' +
20363
+ '<div class="dsh-actions"><button type="button" class="sec-btn primary" id="dsh-rescan" onclick="window.renderDshPlugin()">' + esc(t('plugins.dshRescan')) + '</button>' +
20364
+ '<button type="button" class="sec-btn" id="dsh-official-repo" onclick="window.openDshOfficial(\'repo\')">' + esc(t('plugins.dshOfficialRepo')) + '</button>' +
20365
+ '<button type="button" class="sec-btn" id="dsh-official-docs" onclick="window.openDshOfficial(\'docs\')">' + esc(t('plugins.dshOfficialDocs')) + '</button>' +
20366
+ '<button type="button" class="sec-btn" id="dsh-official-npm" onclick="window.openDshOfficial(\'npm\')">' + esc(t('plugins.dshOfficialNpm')) + '</button></div></section>';
20367
+ html += '<div class="dsh-grid">' +
20368
+ '<section class="dsh-card" id="dsh-cli-card"><div class="dsh-card-title">' + esc(t('plugins.dshCli')) + '</div><dl class="dsh-kv">' +
20369
+ '<dt>' + esc(t('plugins.dshCliPath')) + '</dt><dd id="dsh-cli-path">' + esc(redactSensitiveText(dshDisplayValue(cliPath))) + '</dd>' +
20370
+ '<dt>' + esc(t('plugins.dshCliVersion')) + '</dt><dd id="dsh-cli-version">' + esc(dshDisplayValue(cliVersion)) + '</dd>' +
20371
+ '<dt>' + esc(t('plugins.dshPackageVersion')) + '</dt><dd id="dsh-package-version">' + esc(dshDisplayValue(packageVersion)) + '</dd></dl></section>' +
20372
+ '<section class="dsh-card" id="dsh-runtime-card"><div class="dsh-card-title">' + esc(t('plugins.dshReadonly')) + '</div><dl class="dsh-kv">' +
20373
+ '<dt>' + esc(t('plugins.dshHome')) + '</dt><dd id="dsh-home">' + esc(redactSensitiveText(dshDisplayValue(dsh.dshHome || dsh.home))) + '</dd>' +
20374
+ '<dt>' + esc(t('plugins.dshHomeSource')) + '</dt><dd id="dsh-home-source">' + esc(redactSensitiveText(dshDisplayValue(dsh.dshHomeSource || dsh.homeSource || (dsh.home && dsh.home.source)))) + '</dd>' +
20375
+ '<dt>' + esc(t('plugins.dshUpdateChannel')) + '</dt><dd id="dsh-update-channel">' + esc(dshDisplayValue(updateChannel)) + '</dd></dl>' +
20376
+ '<div class="dsh-description">' + esc(t('plugins.dshReadonlyDetail')) + '</div></section>' +
20377
+ '<section class="dsh-card" id="dsh-profiles-card"><div class="dsh-card-title">' + esc(t('plugins.dshProfiles')) + '</div>' + dshListMarkup(dsh.profiles) + '</section>' +
20378
+ '<section class="dsh-card" id="dsh-bundles-card"><div class="dsh-card-title">' + esc(t('plugins.dshBundles')) + '</div>' + dshListMarkup(dsh.bundles) + '</section>' +
20379
+ '<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>' +
20380
+ '<section class="dsh-card" id="dsh-unknown-keys-card"><div class="dsh-card-title">' + esc(t('plugins.dshUnknownKeys')) + '</div>' + dshListMarkup(dsh.unknownKeys) + '</section></div>';
20381
+ 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>';
20382
+ 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>';
20383
+ dshCollection(dsh.profiles).forEach(function(profile) {
20384
+ html += '<div class="dsh-layer-profile"><div class="plugin-section-title">' + esc(profile && (profile.name || profile.source) || t('plugins.dshNotAvailable')) + '</div>' + dshLayerMarkup(profile) + '</div>';
20385
+ });
20386
+ var homeFiles = dshCollection(dsh.homeConfigFiles);
20387
+ if (homeFiles.length) html += '<div class="dsh-layer-profile"><div class="plugin-section-title">' + esc(t('plugins.dshHomePatches')) + '</div>' + dshListMarkup(homeFiles) + '</div>';
20388
+ html += '<div class="mcp-field-help">' + esc(t('plugins.dshUpdateability')) + '</div></section>';
20389
+ 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>' +
20390
+ '<div class="mcp-field-help">' + esc(t('plugins.mcpCandidateHelp')) + '</div>';
20391
+ if (!candidates.length) html += '<div class="plugin-empty">' + esc(t('plugins.dshNone')) + '</div>';
20392
+ candidates.forEach(function(candidate, index) {
20393
+ var name = candidate && (candidate.name || candidate.id) || ('MCP ' + (index + 1));
20394
+ var source = candidate && (candidate.source || candidate.path) || '';
20395
+ var reason = candidate && candidate.reason || '';
20396
+ var importable = !!(candidate && candidate.importable && candidate.template);
20397
+ 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) +
20398
+ '<span class="plugin-status-badge">' + esc(importable ? t('plugins.mcpReviewImport') : t('plugins.mcpReadonly')) + '</span></div>' +
20399
+ '<div class="mcp-server-meta">' + esc(redactSensitiveText(dshDisplayValue(source))) + '</div>' +
20400
+ (reason ? '<div class="mcp-server-meta">' + esc(redactSensitiveText(dshDisplayValue(reason))) + '</div>' : '') + '</div>' +
20401
+ (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>';
20402
+ });
20403
+ html += '</section>';
20404
+ panel.innerHTML = html;
20405
+ };
20406
+
20407
+ function newMcpDraft(input, source) {
20408
+ input = input && typeof input === 'object' ? input : {};
20409
+ var transport = input.transport === 'http' ? 'http' : 'stdio';
20410
+ return {
20411
+ id: input.id || '',
20412
+ source: source || 'add',
20413
+ name: String(input.name || ''),
20414
+ transport: transport,
20415
+ command: String(input.command || ''),
20416
+ url: String(input.url || ''),
20417
+ argsText: JSON.stringify(Array.isArray(input.args) ? input.args : [], null, 2),
20418
+ cwd: String(input.cwd || ''),
20419
+ envText: input.env && typeof input.env === 'object' ? JSON.stringify(input.env, null, 2) : '',
20420
+ headersText: input.headers && typeof input.headers === 'object' ? JSON.stringify(input.headers, null, 2) : '',
20421
+ enabled: source === 'candidate' ? false : input.enabled !== false,
20422
+ envKeys: Array.isArray(input.envKeys) ? input.envKeys.slice() : [],
20423
+ headerKeys: Array.isArray(input.headerKeys) ? input.headerKeys.slice() : []
20424
+ };
20425
+ }
20426
+
20427
+ window.reviewDshMcpCandidate = function(index) {
20428
+ var candidates = dshCollection(state.dshCompatibility && state.dshCompatibility.mcpCandidates);
20429
+ var candidate = candidates[index];
20430
+ if (!candidate || !candidate.importable || !candidate.template) return;
20431
+ state.mcpEditingId = '';
20432
+ state.mcpDraft = newMcpDraft(Object.assign({}, candidate.template, {
20433
+ enabled: false,
20434
+ envKeys: Array.isArray(candidate.envKeys) ? candidate.envKeys : [],
20435
+ headerKeys: Array.isArray(candidate.headerKeys) ? candidate.headerKeys : []
20436
+ }), 'candidate');
20437
+ state.mcpFormFocusRequested = true;
20438
+ window.showPluginList('mcp');
20439
+ };
20440
+
18904
20441
  window.renderMcpManager = function() {
18905
20442
  var panel = document.getElementById('plugin-panel');
18906
- if (!panel) return;
18907
- panel.innerHTML = '<div class="provider-card marquee-border">' + esc(t('common.loading')) + '</div>';
20443
+ if (!panel || state.pluginActiveTab !== 'mcp') return;
20444
+ var generation = ++state.pluginPanelGeneration;
20445
+ panel.innerHTML = pluginPanelState(t('plugins.mcpRefreshing'), 'loading');
18908
20446
  if (!api.listMcpServers) {
18909
- panel.innerHTML = '<div class="settings-empty">' + esc(t('plugins.ghUnavailable')) + '</div>';
20447
+ panel.innerHTML = pluginPanelState(t('plugins.mcpUnavailable'), 'error');
18910
20448
  return;
18911
20449
  }
18912
- api.listMcpServers().then(function(result) {
20450
+ Promise.resolve(api.listMcpServers()).then(function(result) {
20451
+ if (!pluginRequestActive('mcp', generation)) return;
18913
20452
  state.mcpServers = Array.isArray(result && result.servers) ? result.servers : [];
18914
20453
  state.mcpDiscovered = Array.isArray(result && result.discovered) ? result.discovered : [];
18915
20454
  window.renderMcpManagerContent();
18916
20455
  }).catch(function(error) {
18917
- panel.innerHTML = '<div class="settings-empty">' + esc(String(error && error.message ? error.message : error)) + '</div>';
20456
+ if (!pluginRequestActive('mcp', generation)) return;
20457
+ var message = t('plugins.mcpLoadError') + (error && error.message ? ' ' + error.message : '');
20458
+ panel.innerHTML = pluginPanelState(message, 'error', 'window.renderMcpManager()');
18918
20459
  });
18919
20460
  };
18920
20461
 
20462
+ function mcpSearchMatch(server, query) {
20463
+ server = server || {};
20464
+ if (!query) return true;
20465
+ var text = [server.name, server.transport, server.command, server.url, server.plugin, server.ecosystem, server.root]
20466
+ .concat(server.args || []).join(' ').toLowerCase();
20467
+ return text.indexOf(query) >= 0;
20468
+ }
20469
+
20470
+ function mcpSecretHelp(keys, keyName) {
20471
+ return keys && keys.length
20472
+ ? pluginText(keyName, { keys: keys.join(', ') })
20473
+ : t('plugins.mcpNoSavedKeys');
20474
+ }
20475
+
20476
+ function renderMcpForm() {
20477
+ var draft = state.mcpDraft;
20478
+ if (!draft) return '';
20479
+ var pending = !!state.mcpMutationPending;
20480
+ var titleKey = draft.source === 'candidate' ? 'plugins.mcpReviewTitle' : (draft.id ? 'plugins.mcpEditTitle' : 'plugins.mcpAddTitle');
20481
+ var stdio = draft.transport !== 'http';
20482
+ return '<section class="mcp-form-card" id="mcp-form" aria-labelledby="mcp-form-title" aria-busy="' + (pending ? 'true' : 'false') + '">' +
20483
+ '<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>' +
20484
+ '<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>' +
20485
+ '<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>' +
20486
+ '<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>' +
20487
+ '<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>' +
20488
+ '<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>' +
20489
+ '<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>' +
20490
+ '<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>' +
20491
+ '<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>' +
20492
+ '<label class="mcp-enabled-check" for="mcp-enabled"><input id="mcp-enabled" type="checkbox"' + (draft.enabled ? ' checked' : '') + (pending ? ' disabled' : '') + '> ' + esc(t('plugins.mcpEnabled')) + '</label>' +
20493
+ '<div class="mcp-form-actions"><button type="button" class="sec-btn" id="mcp-cancel" onclick="window.cancelMcpForm()"' + (pending ? ' disabled' : '') + '>' + esc(t('common.cancel')) + '</button>' +
20494
+ '<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>';
20495
+ }
20496
+
18921
20497
  window.renderMcpManagerContent = function() {
18922
20498
  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>';
20499
+ if (!panel || state.pluginActiveTab !== 'mcp') return;
20500
+ var allServers = state.mcpServers || [];
20501
+ var allDiscovered = state.mcpDiscovered || [];
20502
+ var query = String(state.mcpSearchQuery || '').trim().toLowerCase();
20503
+ var servers = allServers.filter(function(server) { return mcpSearchMatch(server, query); });
20504
+ var discovered = allDiscovered.filter(function(server) { return mcpSearchMatch(server, query); });
20505
+ var pending = !!state.mcpMutationPending;
20506
+ 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>' +
20507
+ '<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)">' +
20508
+ '<button type="button" class="sec-btn" id="mcp-refresh" onclick="window.renderMcpManager()"' + (pending ? ' disabled' : '') + '>' + esc(t('plugins.mcpRefresh')) + '</button>' +
20509
+ '<button type="button" class="sec-btn primary" id="mcp-add" onclick="window.openMcpAddForm()"' + (pending ? ' disabled' : '') + '>' + esc(t('plugins.mcpAdd')) + '</button></div>';
20510
+ html += renderMcpForm();
20511
+ 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>';
20512
+ if (!allServers.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpStoredEmpty')) + '</div>';
20513
+ else if (!servers.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpNoMatch')) + '</div>';
18939
20514
  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>';
20515
+ var index = allServers.indexOf(server);
20516
+ var endpoint = server.transport === 'http' ? server.url : [server.command].concat(server.args || []).filter(Boolean).join(' ');
20517
+ html += '<div class="mcp-server-row" data-mcp-index="' + index + '"><div class="mcp-server-copy"><div class="mcp-server-title">' + esc(server.name || '') +
20518
+ '<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>' +
20519
+ '<div class="mcp-server-meta">' + esc(endpoint || '') + '</div></div><div class="mcp-row-actions">' +
20520
+ '<button type="button" class="sec-btn" onclick="window.editMcpServerAt(' + index + ')"' + (pending ? ' disabled' : '') + '>' + esc(t('common.edit')) + '</button>' +
20521
+ '<button type="button" class="sec-btn" onclick="window.toggleMcpServerAt(' + index + ',' + (!server.enabled) + ')"' + (pending ? ' disabled' : '') + '>' + esc(server.enabled ? t('common.disable') : t('common.enable')) + '</button>' +
20522
+ '<button type="button" class="sec-btn" onclick="window.removeMcpServerAt(' + index + ')"' + (pending ? ' disabled' : '') + '>' + esc(t('common.remove')) + '</button></div></div>';
18945
20523
  });
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>';
20524
+ 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>';
20525
+ if (!allDiscovered.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpDiscoveredEmpty')) + '</div>';
20526
+ else if (!discovered.length) html += '<div class="plugin-empty">' + esc(t('plugins.mcpNoMatch')) + '</div>';
18948
20527
  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>';
20528
+ html += '<div class="mcp-server-row"><div class="mcp-server-copy"><div class="mcp-server-title">' + esc(server.name || '') +
20529
+ '<span class="plugin-status-badge">' + esc(server.ecosystem || t('plugins.mcpReadonly')) + '</span>' +
20530
+ '<span class="plugin-status-badge' + (server.enabled ? ' enabled' : '') + '">' + esc(server.enabled ? t('plugins.mcpEnabledStatus') : t('plugins.mcpDisabledStatus')) + '</span></div><div class="mcp-server-meta">' +
20531
+ esc([server.plugin, server.root].filter(Boolean).join(' · ')) + '</div></div><span class="plugin-status-badge">' + esc(t('plugins.mcpReadonly')) + '</span></div>';
18950
20532
  });
20533
+ html += '</section>';
18951
20534
  panel.innerHTML = html;
20535
+ window.updateMcpTransportForm(false);
20536
+ if (state.mcpFormFocusRequested) {
20537
+ state.mcpFormFocusRequested = false;
20538
+ requestAnimationFrame(function() {
20539
+ var form = document.getElementById('mcp-form');
20540
+ var input = document.getElementById('mcp-name');
20541
+ if (form) form.scrollIntoView({ block: 'nearest' });
20542
+ if (input) input.focus({ preventScroll: true });
20543
+ });
20544
+ }
20545
+ };
20546
+
20547
+ window.captureMcpDraftFromForm = function() {
20548
+ if (!state.mcpDraft || !document.getElementById('mcp-form')) return state.mcpDraft;
20549
+ var value = function(id) { var element = document.getElementById(id); return element ? element.value : ''; };
20550
+ state.mcpDraft.name = value('mcp-name');
20551
+ state.mcpDraft.transport = value('mcp-transport') === 'http' ? 'http' : 'stdio';
20552
+ state.mcpDraft.command = value('mcp-command');
20553
+ state.mcpDraft.url = value('mcp-url');
20554
+ state.mcpDraft.argsText = value('mcp-args');
20555
+ state.mcpDraft.cwd = value('mcp-cwd');
20556
+ state.mcpDraft.envText = value('mcp-env');
20557
+ state.mcpDraft.headersText = value('mcp-headers');
20558
+ var enabled = document.getElementById('mcp-enabled');
20559
+ state.mcpDraft.enabled = !!(enabled && enabled.checked);
20560
+ return state.mcpDraft;
20561
+ };
20562
+
20563
+ window.updateMcpTransportForm = function(capture) {
20564
+ if (capture !== false) window.captureMcpDraftFromForm();
20565
+ var transport = document.getElementById('mcp-transport');
20566
+ var stdio = document.getElementById('mcp-stdio-fields');
20567
+ var http = document.getElementById('mcp-http-fields');
20568
+ var isHttp = !!(transport && transport.value === 'http');
20569
+ if (state.mcpDraft) state.mcpDraft.transport = isHttp ? 'http' : 'stdio';
20570
+ if (stdio) stdio.hidden = isHttp;
20571
+ if (http) http.hidden = !isHttp;
20572
+ };
20573
+
20574
+ window.openMcpAddForm = function() {
20575
+ if (state.mcpMutationPending) return;
20576
+ state.mcpEditingId = '';
20577
+ state.mcpDraft = newMcpDraft({ enabled: false, transport: 'stdio', args: [] }, 'add');
20578
+ state.mcpFormFocusRequested = true;
20579
+ window.renderMcpManagerContent();
20580
+ };
20581
+
20582
+ window.editMcpServerAt = function(index) {
20583
+ if (state.mcpMutationPending) return;
20584
+ var server = (state.mcpServers || [])[index];
20585
+ if (!server) return;
20586
+ state.mcpEditingId = server.id || '';
20587
+ state.mcpDraft = newMcpDraft(server, 'edit');
20588
+ state.mcpFormFocusRequested = true;
20589
+ window.renderMcpManagerContent();
20590
+ };
20591
+
20592
+ window.cancelMcpForm = function() {
20593
+ if (state.mcpMutationPending) return;
20594
+ state.mcpEditingId = '';
20595
+ state.mcpDraft = null;
20596
+ window.renderMcpManagerContent();
20597
+ var addButton = document.getElementById('mcp-add');
20598
+ if (addButton) addButton.focus({ preventScroll: true });
18952
20599
  };
18953
20600
 
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';
20601
+ window.updateMcpSearch = function(value) {
20602
+ window.captureMcpDraftFromForm();
20603
+ state.mcpSearchQuery = String(value || '');
20604
+ window.renderMcpManagerContent();
20605
+ var search = document.getElementById('mcp-search');
20606
+ if (search) {
20607
+ search.focus({ preventScroll: true });
20608
+ search.setSelectionRange(search.value.length, search.value.length);
20609
+ }
18960
20610
  };
18961
- window.resetMcpForm = function() { state.mcpEditingId = ''; window.renderMcpManagerContent(); };
18962
- window.editMcpServer = function(id) { state.mcpEditingId = id; window.renderMcpManagerContent(); };
20611
+
20612
+ function parseMcpObject(text, fieldLabel) {
20613
+ if (!String(text || '').trim()) return undefined;
20614
+ var parsed = JSON.parse(text);
20615
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error(pluginText('plugins.mcpInvalidObject', { field: fieldLabel }));
20616
+ return parsed;
20617
+ }
20618
+
20619
+ function mcpMutationUiActive(generation) {
20620
+ return generation === state.mcpMutationGeneration
20621
+ && state.pluginActiveTab === 'mcp'
20622
+ && !!(state.activeSubWindowView && state.activeSubWindowView.name === 'plugins')
20623
+ && !!document.getElementById('plugin-panel');
20624
+ }
20625
+
18963
20626
  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 };
20627
+ if (!api.upsertMcpServer || state.mcpMutationPending) return;
20628
+ var draft = window.captureMcpDraftFromForm();
20629
+ if (!draft) return;
20630
+ var name = String(draft.name || '').trim();
20631
+ var transport = draft.transport === 'http' ? 'http' : 'stdio';
20632
+ if (!name) { showUiNotice(t('plugins.mcpRequiredName'), 'error', 'mcp-save'); return; }
20633
+ var input = { id: draft.id || state.mcpEditingId || undefined, name: name, transport: transport, enabled: !!draft.enabled };
18971
20634
  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); }
20635
+ if (transport === 'http') {
20636
+ var url = String(draft.url || '').trim();
20637
+ if (!/^https?:\/\//i.test(url)) throw new Error(t('plugins.mcpRequiredUrl'));
20638
+ input.url = url;
20639
+ var headers = parseMcpObject(draft.headersText, t('plugins.mcpHeaders'));
20640
+ if (headers !== undefined) input.headers = headers;
20641
+ } else {
20642
+ var command = String(draft.command || '').trim();
20643
+ if (!command) throw new Error(t('plugins.mcpRequiredCommand'));
20644
+ var args = String(draft.argsText || '').trim() ? JSON.parse(draft.argsText) : [];
20645
+ if (!Array.isArray(args)) throw new Error(t('plugins.mcpInvalidArgs'));
20646
+ input.command = command;
20647
+ input.args = args;
20648
+ input.cwd = String(draft.cwd || '').trim() || undefined;
20649
+ var env = parseMcpObject(draft.envText, t('plugins.mcpEnv'));
20650
+ if (env !== undefined) input.env = env;
20651
+ }
18974
20652
  } catch (error) {
18975
- showUiNotice(error.message || String(error), 'error', 'mcp-json');
20653
+ showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-save');
18976
20654
  return;
18977
20655
  }
18978
- api.upsertMcpServer(input).then(function(result) {
18979
- if (!result || !result.ok) throw new Error(result && result.error || 'MCP update failed');
20656
+ var generation = ++state.mcpMutationGeneration;
20657
+ state.mcpMutationPending = true;
20658
+ window.renderMcpManagerContent();
20659
+ Promise.resolve(api.upsertMcpServer(input)).then(function(result) {
20660
+ if (!result || result.ok !== true) throw new Error(result && result.error || t('plugins.mcpMutationFailed'));
20661
+ if (Array.isArray(result.servers)) state.mcpServers = result.servers;
18980
20662
  state.mcpEditingId = '';
18981
- window.renderMcpManager();
18982
- }).catch(function(error) { showUiNotice(error.message || String(error), 'error', 'mcp-save'); });
20663
+ state.mcpDraft = null;
20664
+ if (mcpMutationUiActive(generation)) showUiNotice(t('plugins.mcpSaved'), 'success', 'mcp-save');
20665
+ }).catch(function(error) {
20666
+ if (mcpMutationUiActive(generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-save');
20667
+ }).finally(function() {
20668
+ state.mcpMutationPending = false;
20669
+ if (mcpMutationUiActive(generation)) window.renderMcpManager();
20670
+ });
20671
+ };
20672
+
20673
+ window.toggleMcpServerAt = function(index, enabled) {
20674
+ if (!api.setMcpServerEnabled || state.mcpMutationPending) return;
20675
+ var server = (state.mcpServers || [])[index];
20676
+ if (!server) return;
20677
+ var generation = ++state.mcpMutationGeneration;
20678
+ state.mcpMutationPending = true;
20679
+ window.renderMcpManagerContent();
20680
+ Promise.resolve(api.setMcpServerEnabled(server.id, !!enabled)).then(function(result) {
20681
+ if (!result || result.ok !== true) throw new Error(result && result.error || t('plugins.mcpMutationFailed'));
20682
+ if (Array.isArray(result.servers)) state.mcpServers = result.servers;
20683
+ }).catch(function(error) {
20684
+ if (mcpMutationUiActive(generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-toggle');
20685
+ }).finally(function() {
20686
+ state.mcpMutationPending = false;
20687
+ if (mcpMutationUiActive(generation)) window.renderMcpManager();
20688
+ });
20689
+ };
20690
+
20691
+ window.removeMcpServerAt = function(index) {
20692
+ if (!api.removeMcpServer || state.mcpMutationPending) return;
20693
+ var server = (state.mcpServers || [])[index];
20694
+ if (!server || !confirm(pluginText('plugins.mcpRemoveConfirm', { name: server.name || '' }))) return;
20695
+ var generation = ++state.mcpMutationGeneration;
20696
+ state.mcpMutationPending = true;
20697
+ window.renderMcpManagerContent();
20698
+ Promise.resolve(api.removeMcpServer(server.id)).then(function(result) {
20699
+ if (!result || result.ok !== true) throw new Error(result && result.error || t('plugins.mcpMutationFailed'));
20700
+ if (Array.isArray(result.servers)) state.mcpServers = result.servers;
20701
+ if (state.mcpEditingId === server.id) { state.mcpEditingId = ''; state.mcpDraft = null; }
20702
+ }).catch(function(error) {
20703
+ if (mcpMutationUiActive(generation)) showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-remove');
20704
+ }).finally(function() {
20705
+ state.mcpMutationPending = false;
20706
+ if (mcpMutationUiActive(generation)) window.renderMcpManager();
20707
+ });
18983
20708
  };
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
20709
 
18987
20710
  window.filteredSkillMarket = function() {
18988
20711
  var market = state._skillMarketAll || [];
@@ -19003,7 +20726,7 @@ window.filteredSkillMarket = function() {
19003
20726
 
19004
20727
  window.renderSkillsMarketList = function() {
19005
20728
  var panel = document.getElementById('plugin-panel');
19006
- if (!panel) return;
20729
+ if (!panel || state.pluginActiveTab !== 'market' || !state.activeSubWindowView || state.activeSubWindowView.name !== 'plugins') return;
19007
20730
  var market = window.filteredSkillMarket();
19008
20731
  var total = (state._skillMarketAll || []).length;
19009
20732
  var query = state.skillMarketQuery || '';
@@ -19272,13 +20995,14 @@ window.renderLeftWsList = function() {
19272
20995
  var container = document.getElementById('left-ws-list');
19273
20996
  if (!container) return;
19274
20997
  var workspaces = state.workspaces || [];
20998
+ var hasActiveWorkspace = workspaces.some(function(item) { return workspaceIdentity(item) === String(state.currentWorkspaceId || ''); });
19275
20999
  var html = '';
19276
21000
  for (var i = 0; i < workspaces.length; i++) {
19277
21001
  var ws = workspaces[i];
19278
21002
  var identity = workspaceIdentity(ws);
19279
21003
  var active = identity === String(state.currentWorkspaceId || '');
19280
21004
  var runtimeStatus = workspaceRuntimeStatus(ws);
19281
- html += '<div class="left-ws-item' + (active ? ' active' : '') + '" onclick="window.switchToWorkspace(\'' + escAttr(identity) + '\')">' +
21005
+ 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
21006
  '<span class="ws-icon">' + esc((ws.name || '?')[0].toUpperCase()) + '</span>' +
19283
21007
  '<span class="ws-label">' + esc(ws.name || t('workspace.untitled')) + '</span>' +
19284
21008
  (runtimeStatus ? '<span class="ws-runtime-dot ' + escAttr(runtimeStatus) + '" title="' + escAttr(runtimeStatus) + '"></span>' : '') +
@@ -19291,6 +21015,24 @@ window.renderLeftWsList = function() {
19291
21015
  appendDomNodesInBatches(container, Array.from(holder.children), 'workspace-list', 24);
19292
21016
  };
19293
21017
 
21018
+ window.handleWorkspaceKey = function(event) {
21019
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229 || event.target !== event.currentTarget) return false;
21020
+ if (event.key === 'Enter' || event.key === ' ') return window.activateButtonLike(event);
21021
+ var items = Array.prototype.slice.call(document.querySelectorAll('#left-ws-list .left-ws-item'));
21022
+ var index = items.indexOf(event.currentTarget);
21023
+ if (index < 0 || !items.length) return false;
21024
+ var next = index;
21025
+ if (event.key === 'ArrowDown' || event.key === 'ArrowRight') next = (index + 1) % items.length;
21026
+ else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') next = (index - 1 + items.length) % items.length;
21027
+ else if (event.key === 'Home') next = 0;
21028
+ else if (event.key === 'End') next = items.length - 1;
21029
+ else return false;
21030
+ event.preventDefault();
21031
+ items[next].focus({ preventScroll:true });
21032
+ items[next].click();
21033
+ return true;
21034
+ };
21035
+
19294
21036
  window.toggleWorkspacePinned = function(identity) {
19295
21037
  if (!identity || !api.setWorkspacePinned) return;
19296
21038
  var nextPinned = false;
@@ -19395,7 +21137,7 @@ window.showNewWorkspaceDialog = function() {
19395
21137
  '<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
21138
  '</div>' +
19397
21139
  '<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()">' +
21140
+ '<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
21141
  '<div id="ws-ext-path" style="display:none;margin-top:8px;">' +
19400
21142
  '<label style="display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;">' + esc(t('workspace.folderPath')) + '</label>' +
19401
21143
  '<div style="display:flex;gap:6px;">' +
@@ -19651,7 +21393,7 @@ window.openWorkspaceManager = async function() {
19651
21393
  var ws = workspaces[i];
19652
21394
  var identity = workspaceIdentity(ws);
19653
21395
  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) + '\')">' +
21396
+ 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
21397
  '<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
21398
  '<span style="flex:1;font-size:12px;color:var(--text);">' + esc(ws.name || t('workspace.untitled')) + '</span>' +
19657
21399
  (isActive ? '<span style="font-size:10px;color:var(--accent2);">' + esc(t('workspace.current')) + '</span>' : '') +
@@ -19888,6 +21630,549 @@ function stopMarqueeJS() {
19888
21630
  if (marqueeRAF) { cancelAnimationFrame(marqueeRAF); marqueeRAF = null; }
19889
21631
  }
19890
21632
 
21633
+ // === Unified GUI Command Registry & Keyboard Routing ===
21634
+ function guiCommand(id, titleKey, category, options) {
21635
+ return Object.assign({ id: id, titleKey: titleKey, category: category, bindings: [], scope: 'palette', keywords: [] }, options || {});
21636
+ }
21637
+
21638
+ function guiIsMac() {
21639
+ return String(state.platform || navigator.platform || '').toLowerCase().indexOf('mac') >= 0;
21640
+ }
21641
+
21642
+ function guiBindingRecord(command, input) {
21643
+ return typeof input === 'string'
21644
+ ? { keys: input, scope: command.scope || 'palette' }
21645
+ : { keys: String((input && input.keys) || ''), scope: String((input && input.scope) || command.scope || 'palette') };
21646
+ }
21647
+
21648
+ function guiDisplayBinding(binding) {
21649
+ var keys = typeof binding === 'string' ? binding : String((binding && binding.keys) || '');
21650
+ return keys.replace(/\bMod\b/g, guiIsMac() ? 'Cmd' : 'Ctrl').replace(/ /g, ' then ');
21651
+ }
21652
+
21653
+ function guiSetSelectValue(id, value) {
21654
+ var select = document.getElementById(id);
21655
+ if (!select) return false;
21656
+ select.value = value;
21657
+ select.dispatchEvent(new Event('change', { bubbles: true }));
21658
+ return true;
21659
+ }
21660
+
21661
+ function guiSubWindowOpen() {
21662
+ return !!(els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open'));
21663
+ }
21664
+
21665
+ function guiFocusPrompt() {
21666
+ if (guiSubWindowOpen()) return false;
21667
+ if (els.prompt) {
21668
+ els.prompt.focus({ preventScroll: true });
21669
+ return true;
21670
+ }
21671
+ return false;
21672
+ }
21673
+
21674
+ function guiFocusTerminal() {
21675
+ if (guiSubWindowOpen()) return false;
21676
+ if (state.bottomCollapsed) window.toggleBottom();
21677
+ var input = document.querySelector('#terminal-body .terminal-pane.active .terminal-input') || document.querySelector('#terminal-body .terminal-input');
21678
+ if (input) {
21679
+ input.focus({ preventScroll: true });
21680
+ return true;
21681
+ }
21682
+ return false;
21683
+ }
21684
+
21685
+ function guiFocusBrowserAddress() {
21686
+ if (guiSubWindowOpen()) return false;
21687
+ window.switchRightTab('browser');
21688
+ requestAnimationFrame(function() {
21689
+ var input = document.getElementById('browser-url');
21690
+ if (input) { input.focus({ preventScroll: true }); input.select(); }
21691
+ });
21692
+ return true;
21693
+ }
21694
+
21695
+ function guiCycleConversation(direction) {
21696
+ var conversations = currentWorkspaceConversations ? currentWorkspaceConversations() : [];
21697
+ if (!conversations.length) return false;
21698
+ var current = Math.max(0, Math.min(conversations.length - 1, Number(state.activeConversation) || 0));
21699
+ var next = (current + direction + conversations.length) % conversations.length;
21700
+ window.switchConversation(next);
21701
+ return true;
21702
+ }
21703
+
21704
+ function guiHasSwitchableBranch() {
21705
+ var groups = state.conversationBranchGroups || [];
21706
+ var branches = state.conversationBranches || [];
21707
+ if (groups.length) return groups.some(function(group) { return (group.branches || []).length > 1; });
21708
+ return branches.length > 1;
21709
+ }
21710
+
21711
+ function guiToggleTheme() {
21712
+ var current = state.theme === 'light' ? 'light' : 'dark';
21713
+ window.setTheme(current === 'light' ? 'dark' : 'light');
21714
+ }
21715
+
21716
+ var NEWMARK_GUI_COMMANDS = [
21717
+ guiCommand('app.commandPalette', 'shortcuts.openPalette', 'general', { bindings: [{ keys:'Mod+Shift+P', scope:'global' }], run:function(){ window.openCommandSurface('palette'); } }),
21718
+ guiCommand('help.keyboardShortcuts', 'shortcuts.openHelp', 'general', { bindings: [{ keys:'F1', scope:'global' }], run:function(){ window.openCommandSurface('help'); } }),
21719
+ guiCommand('settings.open', 'settings.title', 'general', { bindings: [{ keys:'Mod+,', scope:'global' }, { keys:'Mod+K S', scope:'whenNotEditing' }], run:function(){ window.openSettings(); } }),
21720
+ guiCommand('window.minimize', 'top.minimize', 'general', { run:function(){ api.minimize(); } }),
21721
+ guiCommand('window.maximize', 'top.maximize', 'general', { run:function(){ api.maximize(); } }),
21722
+ guiCommand('view.toggleTheme', 'shortcuts.toggleTheme', 'general', { bindings:[{ keys:'Mod+K Q', scope:'whenNotEditing' }], run:guiToggleTheme }),
21723
+
21724
+ guiCommand('conversation.new', 'left.newChat', 'workspace', { bindings:[{ keys:'Mod+K N', scope:'whenNotEditing' }], run:function(){ if (state.currentWorkspaceId) window.newConversation(); else window.showNewConversationPage(); } }),
21725
+ guiCommand('conversation.next', 'shortcuts.nextConversation', 'workspace', { bindings:[{ keys:'Mod+Shift+ArrowRight', scope:'whenNotEditing' }], run:function(){ guiCycleConversation(1); }, available:function(){ return currentWorkspaceConversations().length > 1; } }),
21726
+ guiCommand('conversation.previous', 'shortcuts.previousConversation', 'workspace', { bindings:[{ keys:'Mod+Shift+ArrowLeft', scope:'whenNotEditing' }], run:function(){ guiCycleConversation(-1); }, available:function(){ return currentWorkspaceConversations().length > 1; } }),
21727
+ guiCommand('conversation.archive', 'archive.current', 'workspace', { run:function(){ window.archiveCurrent(); }, available:function(){ return !!state.currentWorkspaceId; } }),
21728
+ guiCommand('branch.previous', 'shortcuts.previousBranch', 'workspace', { bindings:[{ keys:'Alt+ArrowUp', scope:'whenNotEditing' }], run:function(){ window.switchConversationBranch(-1); }, available:function(){ return guiHasSwitchableBranch(); } }),
21729
+ guiCommand('branch.next', 'shortcuts.nextBranch', 'workspace', { bindings:[{ keys:'Alt+ArrowDown', scope:'whenNotEditing' }], run:function(){ window.switchConversationBranch(1); }, available:function(){ return guiHasSwitchableBranch(); } }),
21730
+ guiCommand('workspace.manager', 'workspace.manager', 'workspace', { bindings:[{ keys:'Mod+K W', scope:'whenNotEditing' }], run:function(){ window.openWorkspaceManager(); } }),
21731
+ guiCommand('workspace.new', 'workspace.new', 'workspace', { run:function(){ window.showNewWorkspaceDialog(); } }),
21732
+ guiCommand('workspace.settings', 'workspace.settingsTitle', 'workspace', { run:function(){ window.openWsSettings(); }, available:function(){ return !!state.currentWorkspaceId; } }),
21733
+
21734
+ guiCommand('view.plugins', 'plugins.title', 'navigation', { bindings:[{ keys:'Mod+K P', scope:'whenNotEditing' }], run:function(){ window.showPluginList(); } }),
21735
+ guiCommand('view.plugins.mcp', 'plugins.mcp', 'navigation', { run:function(){ window.showPluginList('mcp'); } }),
21736
+ guiCommand('view.plugins.dsh', 'plugins.dsh', 'navigation', { bindings:[{ keys:'Mod+K D', scope:'whenNotEditing' }], run:function(){ window.showPluginList('dsh'); } }),
21737
+ guiCommand('view.plugins.skills', 'plugins.management', 'navigation', { run:function(){ window.showPluginList('installed'); } }),
21738
+ guiCommand('view.plugins.market', 'plugins.market', 'navigation', { run:function(){ window.showPluginList('market'); } }),
21739
+ guiCommand('view.plugins.github', 'plugins.github', 'navigation', { run:function(){ window.showPluginList('github'); } }),
21740
+ guiCommand('view.memoryLab', 'memoryLab.title', 'navigation', { bindings:[{ keys:'Mod+K M', scope:'whenNotEditing' }], run:function(){ window.showMemoryLab(); } }),
21741
+ guiCommand('view.automation', 'automation.title', 'navigation', { bindings:[{ keys:'Mod+K A', scope:'whenNotEditing' }], run:function(){ window.showAutomationWindow(); } }),
21742
+ guiCommand('automation.new', 'automation.new', 'navigation', { run:function(){ window.showNewAutomationForm(); } }),
21743
+ guiCommand('view.flowEditor', 'flow.title', 'navigation', { bindings:[{ keys:'Mod+K F', scope:'whenNotEditing' }], run:function(){ window.showFlowEditor(); } }),
21744
+ guiCommand('flow.new', 'flow.new', 'navigation', { run:function(){ window.newFlowWork(); } }),
21745
+
21746
+ guiCommand('focus.primaryInput', 'shortcuts.focusPrimary', 'layout', { bindings:[{ keys:'Mod+K C', scope:'whenNotEditing' }], run:guiFocusPrompt, available:function(){ return !guiSubWindowOpen(); } }),
21747
+ guiCommand('focus.nextRegion', 'shortcuts.focusNext', 'layout', { bindings:[{ keys:'F6', scope:'global' }], run:function(){ window.cycleGuiRegionFocus(1); }, available:function(){ return !guiSubWindowOpen() && !window.commandSurfaceIsOpen(); } }),
21748
+ guiCommand('focus.previousRegion', 'shortcuts.focusPrevious', 'layout', { bindings:[{ keys:'Shift+F6', scope:'global' }], run:function(){ window.cycleGuiRegionFocus(-1); }, available:function(){ return !guiSubWindowOpen() && !window.commandSurfaceIsOpen(); } }),
21749
+ guiCommand('view.toggleLeft', 'shortcuts.toggleLeft', 'layout', { bindings:[{ keys:'Mod+B', scope:'whenNotEditing' }], run:function(){ window.toggleLeft(); } }),
21750
+ guiCommand('view.toggleWorkspacePanel', 'shortcuts.toggleWorkspacePanel', 'layout', { run:function(){ window.toggleSecondarySidebar(); } }),
21751
+ guiCommand('view.toggleRight', 'shortcuts.toggleRight', 'layout', { run:function(){ window.toggleRight(); } }),
21752
+ guiCommand('view.toggleTerminal', 'shortcuts.toggleTerminal', 'layout', { bindings:[{ keys:'Mod+`', scope:'whenNotEditing' }], run:function(){ window.toggleBottom(); } }),
21753
+ guiCommand('chat.scrollBottom', 'shortcuts.scrollBottom', 'layout', { bindings:[{ keys:'Mod+End', scope:'whenNotEditing' }], run:function(){ window.scrollToBottom(); } }),
21754
+
21755
+ guiCommand('right.files', 'right.files', 'navigation', { bindings:[{ keys:'Mod+K 1', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('file-tree'); } }),
21756
+ guiCommand('right.editor', 'right.editor', 'navigation', { bindings:[{ keys:'Mod+K 2', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('editor'); } }),
21757
+ guiCommand('right.plan', 'right.plan', 'navigation', { bindings:[{ keys:'Mod+K 3', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('plan'); } }),
21758
+ guiCommand('right.subagents', 'right.subagents', 'navigation', { run:function(){ window.switchRightTab('subagent'); } }),
21759
+ guiCommand('right.browser', 'right.browser', 'navigation', { bindings:[{ keys:'Mod+K 4', scope:'whenNotEditing' }], run:function(){ window.switchRightTab('browser'); } }),
21760
+ guiCommand('right.status', 'right.status', 'navigation', { run:function(){ window.switchRightTab('status'); } }),
21761
+ guiCommand('right.archives', 'right.archives', 'navigation', { run:function(){ window.switchRightTab('archives'); } }),
21762
+
21763
+ guiCommand('mode.build', 'mode.build', 'input', { bindings:[{ keys:'Mod+1', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','build'); } }),
21764
+ guiCommand('mode.plan', 'mode.plan', 'input', { bindings:[{ keys:'Mod+2', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','plan'); } }),
21765
+ guiCommand('mode.goal', 'mode.goal', 'input', { bindings:[{ keys:'Mod+3', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','goal'); } }),
21766
+ guiCommand('mode.flow', 'mode.flow', 'input', { bindings:[{ keys:'Mod+4', scope:'whenNotEditing' }], run:function(){ guiSetSelectValue('mode-select','flow'); } }),
21767
+ guiCommand('input.guide', 'input.guide', 'input', { bindings:[{ keys:'Mod+K G', scope:'whenNotEditing' }], run:function(){ window.setInputMode('guide'); } }),
21768
+ guiCommand('input.next', 'input.next', 'input', { bindings:[{ keys:'Mod+K E', scope:'whenNotEditing' }], run:function(){ window.setInputMode('next'); } }),
21769
+ guiCommand('goal.edit', 'goal.edit', 'input', { run:function(){ window.editGoal(); } }),
21770
+ guiCommand('goal.pauseResume', 'goal.pause', 'input', { bindings:[{ keys:'Mod+K Shift+G', scope:'whenNotEditing' }], run:function(){ window.toggleGoalPause(); }, available:function(){ return !!state.goalVisible; } }),
21771
+
21772
+ guiCommand('editor.save', 'common.save', 'editor', { bindings:[{ keys:'Mod+S', scope:'editor' }], run:function(){ window.saveEditor(); }, available:function(){ return !!state.editorPath; } }),
21773
+ guiCommand('editor.close', 'common.close', 'editor', { run:function(){ window.closeEditor(); }, available:function(){ return !!state.editorPath; } }),
21774
+ guiCommand('editor.markdownPreview', 'right.md', 'editor', { run:function(){ window.toggleEditorMarkdownPreview(); }, available:function(){ return !!state.editorPath; } }),
21775
+ guiCommand('editor.prediction', 'model.thinking', 'editor', { run:function(){ window.toggleEditorPrediction(); }, available:function(){ return !!state.editorPath; } }),
21776
+ guiCommand('editor.completion', 'model.validationCurrent', 'editor', { run:function(){ window.requestEditorCompletion({ force:true }); }, available:function(){ return !!state.editorPath; } }),
21777
+
21778
+ guiCommand('terminal.focus', 'shortcuts.focusTerminal', 'terminal', { bindings:[{ keys:'Mod+K T', scope:'whenNotEditing' }], run:guiFocusTerminal, available:function(){ return !guiSubWindowOpen(); } }),
21779
+ guiCommand('terminal.new', 'shortcuts.newTerminal', 'terminal', { run:function(){ window.addTerminalTab(); } }),
21780
+ guiCommand('terminal.clear', 'terminal.clear', 'terminal', { run:function(){ window.clearTerminal(); } }),
21781
+
21782
+ guiCommand('browser.focusAddress', 'shortcuts.focusBrowserAddress', 'browser', { bindings:[{ keys:'Mod+L', scope:'browser' }, { keys:'Mod+K B', scope:'whenNotEditing' }], run:guiFocusBrowserAddress, available:function(){ return !guiSubWindowOpen(); } }),
21783
+ guiCommand('browser.back', 'browser.back', 'browser', { bindings:[{ keys:'Alt+ArrowLeft', scope:'browser' }], run:function(){ window.browserBack(); } }),
21784
+ guiCommand('browser.forward', 'browser.forward', 'browser', { bindings:[{ keys:'Alt+ArrowRight', scope:'browser' }], run:function(){ window.browserForward(); } }),
21785
+ guiCommand('browser.reload', 'browser.reload', 'browser', { bindings:[{ keys:'Mod+R', scope:'browser' }, { keys:'F5', scope:'browser' }], run:function(){ window.browserReload(); } }),
21786
+ guiCommand('browser.computerUse', 'right.browser', 'browser', { run:function(){ window.toggleComputerUse(); } }),
21787
+
21788
+ guiCommand('settings.general', 'settings.general', 'context', { run:function(){ window.openSettings('general'); } }),
21789
+ guiCommand('settings.models', 'settings.models', 'context', { run:function(){ window.openSettings('models'); } }),
21790
+ guiCommand('settings.tools', 'settings.tools', 'context', { run:function(){ window.openSettings('tools'); } }),
21791
+ guiCommand('settings.archive', 'settings.archive', 'context', { run:function(){ window.openSettings('archive'); } }),
21792
+ guiCommand('settings.updates', 'settings.updates', 'context', { run:function(){ window.openSettings('updates'); } })
21793
+ ];
21794
+ window.NEWMARK_GUI_COMMANDS = NEWMARK_GUI_COMMANDS;
21795
+
21796
+ window.activateButtonLike = function(event) {
21797
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229) return false;
21798
+ if (event.key !== 'Enter' && event.key !== ' ') return false;
21799
+ if (event.target !== event.currentTarget) return false;
21800
+ event.preventDefault();
21801
+ event.currentTarget.click();
21802
+ return true;
21803
+ };
21804
+
21805
+ function guiCommandTitle(command) {
21806
+ return t(command.titleKey || command.id);
21807
+ }
21808
+
21809
+ function guiCommandCategory(command) {
21810
+ return t('shortcuts.category.' + (command.category || 'general'));
21811
+ }
21812
+
21813
+ function guiCommandAvailable(command) {
21814
+ try { return !command.available || command.available() !== false; }
21815
+ catch(e) { return false; }
21816
+ }
21817
+
21818
+ function guiKeyboardContext(event) {
21819
+ var target = event && event.target && event.target.nodeType === 1 ? event.target : document.activeElement;
21820
+ var closest = function(selector) { return target && target.closest ? target.closest(selector) : null; };
21821
+ var editable = !!(target && ((target.matches && target.matches('input, textarea, select, [role="textbox"], [role="combobox"]')) || target.isContentEditable || closest('[contenteditable]:not([contenteditable="false"])')));
21822
+ return {
21823
+ target: target,
21824
+ editable: editable,
21825
+ editor: !!closest('#native-editor, #panel-editor'),
21826
+ terminal: !!closest('#bottom, .terminal-pane'),
21827
+ browser: !!closest('#panel-browser, .browser-url-bar') || state.rightTab === 'browser' && !!closest('#right'),
21828
+ prompt: !!(els.prompt && (target === els.prompt || (els.prompt.contains && els.prompt.contains(target)))),
21829
+ dialog: !!closest('[role="dialog"]')
21830
+ };
21831
+ }
21832
+
21833
+ function guiBindingScopeMatches(scope, context) {
21834
+ if (scope === 'global') return true;
21835
+ if (scope === 'whenNotEditing') return !context.editable && !context.editor && !context.terminal && !context.browser && !context.dialog;
21836
+ if (scope === 'editor') return context.editor;
21837
+ if (scope === 'terminal') return context.terminal;
21838
+ if (scope === 'browser') return context.browser;
21839
+ if (scope === 'prompt') return context.prompt;
21840
+ if (scope === 'dialog') return context.dialog;
21841
+ return false;
21842
+ }
21843
+
21844
+ function guiEventSegment(event) {
21845
+ var key = String(event.key || '');
21846
+ if (!key || ['Control','Shift','Alt','Meta','AltGraph'].indexOf(key) >= 0) return '';
21847
+ var parts = [];
21848
+ var mac = guiIsMac();
21849
+ if ((mac && event.metaKey) || (!mac && event.ctrlKey)) parts.push('Mod');
21850
+ if ((mac && event.ctrlKey) || (!mac && event.metaKey)) parts.push(mac ? 'Ctrl' : 'Meta');
21851
+ if (event.altKey) parts.push('Alt');
21852
+ if (event.shiftKey) parts.push('Shift');
21853
+ var named = { ' ':'Space', 'Esc':'Escape', 'Left':'ArrowLeft', 'Right':'ArrowRight', 'Up':'ArrowUp', 'Down':'ArrowDown' };
21854
+ key = named[key] || key;
21855
+ if (key.length === 1 && /[a-z]/i.test(key)) key = key.toUpperCase();
21856
+ parts.push(key);
21857
+ return parts.join('+');
21858
+ }
21859
+
21860
+ var guiCommandSurfaceOriginFocus = null;
21861
+ var guiCommandSurfaceMode = 'palette';
21862
+ var guiCommandSurfaceMatches = [];
21863
+ var guiCommandSurfaceSelection = 0;
21864
+ var guiPendingChord = null;
21865
+ var guiPendingChordTimer = 0;
21866
+
21867
+ window.commandSurfaceIsOpen = function() {
21868
+ var overlay = document.getElementById('command-surface-overlay');
21869
+ return !!(overlay && overlay.classList.contains('open'));
21870
+ };
21871
+
21872
+ function guiClearChord() {
21873
+ guiPendingChord = null;
21874
+ if (guiPendingChordTimer) clearTimeout(guiPendingChordTimer);
21875
+ guiPendingChordTimer = 0;
21876
+ var hint = document.getElementById('shortcut-chord-hint');
21877
+ if (hint) hint.classList.remove('open');
21878
+ }
21879
+
21880
+ function guiShowChord(prefix, candidates) {
21881
+ guiClearChord();
21882
+ guiPendingChord = { prefix:prefix, candidates:candidates };
21883
+ var seconds = candidates.map(function(item){ return item.binding.keys.split(' ')[1]; }).filter(function(key,index,array){ return array.indexOf(key) === index; });
21884
+ var hint = document.getElementById('shortcut-chord-hint');
21885
+ if (hint) {
21886
+ hint.textContent = t('shortcuts.chordHint').replace('{prefix}', guiDisplayBinding(prefix)).replace('{keys}', seconds.join(' · '));
21887
+ hint.classList.add('open');
21888
+ }
21889
+ guiPendingChordTimer = setTimeout(guiClearChord, 1800);
21890
+ }
21891
+
21892
+ function guiNormalizeCommandSearchText(value) {
21893
+ return String(value || '')
21894
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
21895
+ .replace(/[._:/\\-]+/g, ' ')
21896
+ .replace(/\s+/g, ' ')
21897
+ .trim()
21898
+ .toLocaleLowerCase(uiLocale());
21899
+ }
21900
+
21901
+ function guiCommandSearchText(command) {
21902
+ var bindings = (command.bindings || []).map(guiDisplayBinding).join(' ');
21903
+ return guiNormalizeCommandSearchText([command.id, guiCommandTitle(command), guiCommandCategory(command), bindings].concat(command.keywords || []).join(' '));
21904
+ }
21905
+
21906
+ function guiCommandContextLabel(command) {
21907
+ var scopes = (command.bindings || []).map(function(binding){ return guiBindingRecord(command,binding).scope; });
21908
+ var scope = scopes[0] || command.scope || 'palette';
21909
+ if (scope === 'editor') return t('shortcuts.context.editor');
21910
+ if (scope === 'browser') return t('shortcuts.context.browser');
21911
+ if (scope === 'terminal') return t('shortcuts.context.terminal');
21912
+ if (scope === 'prompt') return t('shortcuts.context.prompt');
21913
+ if (scope === 'dialog') return t('shortcuts.context.dialog');
21914
+ return t('shortcuts.context.global');
21915
+ }
21916
+
21917
+ window.renderCommandSurface = function() {
21918
+ var list = document.getElementById('command-list');
21919
+ var search = document.getElementById('command-search');
21920
+ if (!list || !search) return;
21921
+ var query = guiNormalizeCommandSearchText(search.value);
21922
+ guiCommandSurfaceMatches = NEWMARK_GUI_COMMANDS.filter(function(command){ return !query || guiCommandSearchText(command).indexOf(query) >= 0; });
21923
+ if (guiCommandSurfaceSelection >= guiCommandSurfaceMatches.length) guiCommandSurfaceSelection = Math.max(0, guiCommandSurfaceMatches.length - 1);
21924
+ if (!guiCommandSurfaceMatches.length) {
21925
+ list.innerHTML = '<div class="command-empty">' + esc(t('shortcuts.noResults')) + '</div>';
21926
+ search.removeAttribute('aria-activedescendant');
21927
+ return;
21928
+ }
21929
+ var html = '';
21930
+ var priorCategory = '';
21931
+ for (var i = 0; i < guiCommandSurfaceMatches.length; i++) {
21932
+ var command = guiCommandSurfaceMatches[i];
21933
+ var category = guiCommandCategory(command);
21934
+ if (category !== priorCategory) {
21935
+ html += '<div class="command-category">' + esc(category) + '</div>';
21936
+ priorCategory = category;
21937
+ }
21938
+ var available = guiCommandAvailable(command);
21939
+ var keys = (command.bindings || []).map(function(binding){ return '<span class="command-key">' + esc(guiDisplayBinding(binding)) + '</span>'; }).join('');
21940
+ 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 + ')">' +
21941
+ '<span><span class="command-option-title">' + esc(guiCommandTitle(command)) + '</span><span class="command-option-context">' + esc(guiCommandContextLabel(command)) + '</span></span>' +
21942
+ '<span class="command-shortcuts">' + keys + '</span></button>';
21943
+ }
21944
+ list.innerHTML = html;
21945
+ search.setAttribute('aria-activedescendant', 'command-option-' + guiCommandSurfaceSelection);
21946
+ var active = document.getElementById('command-option-' + guiCommandSurfaceSelection);
21947
+ if (active) active.scrollIntoView({ block:'nearest' });
21948
+ };
21949
+
21950
+ function guiMoveCommandSelection(delta, edge) {
21951
+ if (!guiCommandSurfaceMatches.length) return;
21952
+ if (edge === 'start') guiCommandSurfaceSelection = 0;
21953
+ else if (edge === 'end') guiCommandSurfaceSelection = guiCommandSurfaceMatches.length - 1;
21954
+ else guiCommandSurfaceSelection = (guiCommandSurfaceSelection + delta + guiCommandSurfaceMatches.length) % guiCommandSurfaceMatches.length;
21955
+ window.renderCommandSurface();
21956
+ }
21957
+
21958
+ window.openCommandSurface = function(mode) {
21959
+ var overlay = document.getElementById('command-surface-overlay');
21960
+ var search = document.getElementById('command-search');
21961
+ if (!overlay || !search) return;
21962
+ guiClearChord();
21963
+ if (!window.commandSurfaceIsOpen() && document.activeElement && typeof document.activeElement.focus === 'function') guiCommandSurfaceOriginFocus = document.activeElement;
21964
+ guiCommandSurfaceMode = mode === 'help' ? 'help' : 'palette';
21965
+ document.getElementById('command-surface-title').textContent = t(guiCommandSurfaceMode === 'help' ? 'shortcuts.helpTitle' : 'shortcuts.paletteTitle');
21966
+ document.getElementById('command-surface-subtitle').textContent = t(guiCommandSurfaceMode === 'help' ? 'shortcuts.helpSubtitle' : 'shortcuts.paletteSubtitle');
21967
+ document.getElementById('command-surface-footer').textContent = t('shortcuts.footer');
21968
+ search.setAttribute('placeholder', t(guiCommandSurfaceMode === 'help' ? 'shortcuts.searchShortcuts' : 'shortcuts.searchCommands'));
21969
+ search.value = '';
21970
+ guiCommandSurfaceSelection = 0;
21971
+ overlay.classList.add('open');
21972
+ updateApplicationInertState();
21973
+ window.renderCommandSurface();
21974
+ requestAnimationFrame(function(){ search.focus({ preventScroll:true }); });
21975
+ };
21976
+
21977
+ window.closeCommandSurface = function(options) {
21978
+ options = options || {};
21979
+ var overlay = document.getElementById('command-surface-overlay');
21980
+ if (!overlay || !overlay.classList.contains('open')) return;
21981
+ overlay.classList.remove('open');
21982
+ updateApplicationInertState();
21983
+ var restore = guiCommandSurfaceOriginFocus;
21984
+ guiCommandSurfaceOriginFocus = null;
21985
+ if (options.restoreFocus !== false) requestAnimationFrame(function(){ if (restore && restore.isConnected && typeof restore.focus === 'function') restore.focus({ preventScroll:true }); });
21986
+ return restore;
21987
+ };
21988
+
21989
+ window.executeGuiCommand = function(id, options) {
21990
+ var command = NEWMARK_GUI_COMMANDS.find(function(item){ return item.id === id; });
21991
+ if (!command || !guiCommandAvailable(command) || typeof command.run !== 'function') return false;
21992
+ try {
21993
+ var result = command.run(options || {});
21994
+ if (result && typeof result.catch === 'function') result.catch(function(error){ showUiNotice(error && error.message ? error.message : String(error), 'error', 'gui-command-' + id); });
21995
+ return true;
21996
+ } catch(error) {
21997
+ showUiNotice(error && error.message ? error.message : String(error), 'error', 'gui-command-' + id);
21998
+ return false;
21999
+ }
22000
+ };
22001
+
22002
+ window.executeCommandSurfaceIndex = function(index) {
22003
+ var command = guiCommandSurfaceMatches[index];
22004
+ if (!command || !guiCommandAvailable(command)) return false;
22005
+ var restore = window.closeCommandSurface({ restoreFocus:false });
22006
+ setTimeout(function(){
22007
+ window.executeGuiCommand(command.id, { source:'commandSurface' });
22008
+ requestAnimationFrame(function() {
22009
+ if (!guiSubWindowOpen() && !window.commandSurfaceIsOpen() && restore && restore.isConnected && typeof restore.focus === 'function') restore.focus({ preventScroll:true });
22010
+ });
22011
+ }, 0);
22012
+ return true;
22013
+ };
22014
+
22015
+ function guiVisible(element) {
22016
+ return !!(element && element.isConnected && element.getClientRects().length && !element.closest('[inert]'));
22017
+ }
22018
+
22019
+ function guiRegionTargets() {
22020
+ var activeTerminalInput = document.querySelector('#terminal-body .terminal-pane.active .terminal-input') || document.getElementById('bottom-toggle-btn');
22021
+ var activeRightTab = document.querySelector('#right-tabs .tab-btn[data-tab].active') || document.querySelector('#right-tabs .tab-btn[data-tab]');
22022
+ return [
22023
+ { container:document.getElementById('left'), target:document.querySelector('#left-content .left-nav-icon') || document.querySelector('#left-thumb .lt-item') },
22024
+ { container:document.getElementById('left-secondary'), target:document.querySelector('#left-secondary button, #conversation-list button, #conversation-list [tabindex="0"]') },
22025
+ { container:document.getElementById('center'), target:els.prompt },
22026
+ { container:document.getElementById('bottom'), target:activeTerminalInput },
22027
+ { container:document.getElementById('right'), target:activeRightTab }
22028
+ ].filter(function(region){ return guiVisible(region.container) && guiVisible(region.target); });
22029
+ }
22030
+
22031
+ window.cycleGuiRegionFocus = function(direction) {
22032
+ var regions = guiRegionTargets();
22033
+ if (!regions.length) return false;
22034
+ var active = document.activeElement;
22035
+ var current = regions.findIndex(function(region){ return region.container === active || region.container.contains(active); });
22036
+ var next = current < 0 ? (direction > 0 ? 0 : regions.length - 1) : (current + direction + regions.length) % regions.length;
22037
+ regions[next].target.focus({ preventScroll:true });
22038
+ return true;
22039
+ };
22040
+
22041
+ window.validateGuiCommandRegistry = function() {
22042
+ var errors = [];
22043
+ var ids = Object.create(null);
22044
+ var bindings = Object.create(null);
22045
+ for (var i = 0; i < NEWMARK_GUI_COMMANDS.length; i++) {
22046
+ var command = NEWMARK_GUI_COMMANDS[i];
22047
+ if (!command.id || ids[command.id]) errors.push('duplicate command id: ' + command.id);
22048
+ ids[command.id] = true;
22049
+ if (typeof command.run !== 'function') errors.push('missing command handler: ' + command.id);
22050
+ for (var j = 0; j < (command.bindings || []).length; j++) {
22051
+ var binding = guiBindingRecord(command, command.bindings[j]);
22052
+ if (!binding.keys) { errors.push('empty binding: ' + command.id); continue; }
22053
+ var identity = binding.scope + '::' + binding.keys;
22054
+ if (bindings[identity]) errors.push('binding conflict: ' + binding.keys + ' (' + binding.scope + ')');
22055
+ bindings[identity] = command.id;
22056
+ if (/\bMeta\b/.test(binding.keys) || /(?:^|\+)Mod\+Alt\+/.test(binding.keys)) errors.push('reserved modifier: ' + binding.keys);
22057
+ if (/^(Alt\+F4|Alt\+Tab|Mod\+Alt\+Delete|Mod\+Shift\+Escape)$/.test(binding.keys)) errors.push('reserved shortcut: ' + binding.keys);
22058
+ }
22059
+ }
22060
+ return errors;
22061
+ };
22062
+
22063
+ function guiManifestHash(value) {
22064
+ var input = String(value || '');
22065
+ var hash = 2166136261;
22066
+ for (var i = 0; i < input.length; i++) {
22067
+ hash ^= input.charCodeAt(i);
22068
+ hash = Math.imul(hash, 16777619);
22069
+ }
22070
+ return ('00000000' + (hash >>> 0).toString(16)).slice(-8);
22071
+ }
22072
+
22073
+ window.getGuiCommandManifest = function() {
22074
+ var commands = NEWMARK_GUI_COMMANDS.map(function(command) {
22075
+ return {
22076
+ id: command.id,
22077
+ category: command.category || 'general',
22078
+ bindings: (command.bindings || []).map(function(binding) {
22079
+ var record = guiBindingRecord(command, binding);
22080
+ return { keys: record.keys, scope: record.scope };
22081
+ })
22082
+ };
22083
+ });
22084
+ var serialized = JSON.stringify(commands);
22085
+ return {
22086
+ schemaVersion: 1,
22087
+ revision: 'fnv1a-' + guiManifestHash(serialized),
22088
+ commands: commands,
22089
+ errors: window.validateGuiCommandRegistry()
22090
+ };
22091
+ };
22092
+
22093
+ function guiHandleCommandKeydown(event) {
22094
+ if (!event || event.defaultPrevented || event.isComposing || event.key === 'Process' || event.keyCode === 229) return false;
22095
+ if (window.commandSurfaceIsOpen()) {
22096
+ if (event.key === 'Escape' && !event.repeat) { event.preventDefault(); event.stopPropagation(); window.closeCommandSurface(); return true; }
22097
+ if (event.key === 'Tab') return trapNewmarkDialogFocus(event, document.getElementById('command-surface'));
22098
+ return false;
22099
+ }
22100
+ if (event.repeat) return false;
22101
+ var segment = guiEventSegment(event);
22102
+ if (!segment) return false;
22103
+ if (guiPendingChord) {
22104
+ var match = guiPendingChord.candidates.find(function(item){ return item.binding.keys.split(' ')[1] === segment; });
22105
+ guiClearChord();
22106
+ event.preventDefault();
22107
+ event.stopPropagation();
22108
+ if (match) window.executeGuiCommand(match.command.id, { source:'shortcut' });
22109
+ return true;
22110
+ }
22111
+ var context = guiKeyboardContext(event);
22112
+ var direct = [];
22113
+ var chords = [];
22114
+ for (var i = 0; i < NEWMARK_GUI_COMMANDS.length; i++) {
22115
+ var command = NEWMARK_GUI_COMMANDS[i];
22116
+ if (!guiCommandAvailable(command)) continue;
22117
+ for (var j = 0; j < (command.bindings || []).length; j++) {
22118
+ var binding = guiBindingRecord(command, command.bindings[j]);
22119
+ if (!guiBindingScopeMatches(binding.scope, context)) continue;
22120
+ var pieces = binding.keys.split(' ');
22121
+ if (pieces[0] !== segment) continue;
22122
+ if (pieces.length === 1) direct.push({ command:command, binding:binding });
22123
+ else if (pieces.length === 2) chords.push({ command:command, binding:binding });
22124
+ }
22125
+ }
22126
+ if (direct.length) {
22127
+ event.preventDefault();
22128
+ event.stopPropagation();
22129
+ window.executeGuiCommand(direct[0].command.id, { source:'shortcut' });
22130
+ return true;
22131
+ }
22132
+ if (chords.length) {
22133
+ event.preventDefault();
22134
+ event.stopPropagation();
22135
+ guiShowChord(segment, chords);
22136
+ return true;
22137
+ }
22138
+ return false;
22139
+ }
22140
+
22141
+ function setupGuiKeyboard() {
22142
+ if (state._guiKeyboardReady) return;
22143
+ state._guiKeyboardReady = true;
22144
+ var errors = window.validateGuiCommandRegistry();
22145
+ if (errors.length) console.error('[Keyboard] command registry conflicts:', errors);
22146
+ var search = document.getElementById('command-search');
22147
+ if (search) {
22148
+ search.addEventListener('input', function(){ guiCommandSurfaceSelection = 0; window.renderCommandSurface(); });
22149
+ search.addEventListener('keydown', function(event){
22150
+ if (event.isComposing || event.key === 'Process' || event.keyCode === 229) return;
22151
+ if (event.key === 'ArrowDown') { event.preventDefault(); guiMoveCommandSelection(1); }
22152
+ else if (event.key === 'ArrowUp') { event.preventDefault(); guiMoveCommandSelection(-1); }
22153
+ else if (event.key === 'Home' && !event.ctrlKey && !event.metaKey) { event.preventDefault(); guiMoveCommandSelection(0,'start'); }
22154
+ else if (event.key === 'End' && !event.ctrlKey && !event.metaKey) { event.preventDefault(); guiMoveCommandSelection(0,'end'); }
22155
+ else if (event.key === 'Enter') { event.preventDefault(); window.executeCommandSurfaceIndex(guiCommandSurfaceSelection); }
22156
+ });
22157
+ }
22158
+ document.addEventListener('keydown', guiHandleCommandKeydown, true);
22159
+ if (api.onKeyboardCommand) api.onKeyboardCommand(function(payload){ if (payload && payload.id) window.executeGuiCommand(String(payload.id), { source:'browserGuest' }); });
22160
+ var attributes = [
22161
+ ['#left-collapse-btn','view.toggleLeft'],
22162
+ ['#bottom-toggle-btn','view.toggleTerminal'],
22163
+ ['.sub-win-close','common.close']
22164
+ ];
22165
+ for (var i = 0; i < attributes.length; i++) {
22166
+ var element = document.querySelector(attributes[i][0]);
22167
+ var command = NEWMARK_GUI_COMMANDS.find(function(item){ return item.id === attributes[i][1]; });
22168
+ if (!element || !command || !command.bindings.length) continue;
22169
+ var direct = guiBindingRecord(command,command.bindings[0]).keys;
22170
+ if (direct.indexOf(' ') < 0) element.setAttribute('aria-keyshortcuts', direct.replace(/\bMod\b/g, guiIsMac() ? 'Meta' : 'Control'));
22171
+ }
22172
+ var manifest = window.getGuiCommandManifest();
22173
+ document.documentElement.dataset.keyboardRegistry = manifest.errors.length ? 'invalid' : manifest.revision;
22174
+ }
22175
+
19891
22176
  function schedulePostStartupUiRendering() {
19892
22177
  if (state._postStartupUiRendering && state._postStartupUiRendering.cancel) state._postStartupUiRendering.cancel();
19893
22178
  var tasks = [
@@ -19936,8 +22221,33 @@ function schedulePostStartupUiRendering() {
19936
22221
  (function initResize() {
19937
22222
  var handles = document.querySelectorAll('.resize-handle');
19938
22223
  for (var i = 0; i < handles.length; i++) {
22224
+ var resizeTarget = handles[i].getAttribute('data-target');
22225
+ if (resizeTarget === 'left') { handles[i].setAttribute('aria-valuemin', '220'); handles[i].setAttribute('aria-valuemax', '460'); }
22226
+ if (resizeTarget === 'right') { handles[i].setAttribute('aria-valuemin', '340'); handles[i].setAttribute('aria-valuemax', '680'); }
22227
+ handles[i].addEventListener('keydown', function(e) {
22228
+ if (!e || e.defaultPrevented || e.isComposing || e.key === 'Process' || e.keyCode === 229 || e.ctrlKey || e.metaKey || e.altKey) return;
22229
+ var target = this.getAttribute('data-target');
22230
+ var side = this.getAttribute('data-side');
22231
+ var el = target === 'left' ? (els.left || document.getElementById('left')) : (target === 'right' ? (els.right || document.getElementById('right')) : null);
22232
+ if (!el || (target === 'left' && state.leftCollapsed) || (target === 'right' && state.rightCollapsed)) return;
22233
+ var min = target === 'left' ? 220 : 340;
22234
+ var max = target === 'left' ? 460 : 680;
22235
+ var size = el.offsetWidth;
22236
+ var delta = e.shiftKey ? 32 : 12;
22237
+ if (e.key === 'Home') size = min;
22238
+ else if (e.key === 'End') size = max;
22239
+ else if (e.key === 'ArrowLeft') size += side === 'left' ? delta : -delta;
22240
+ else if (e.key === 'ArrowRight') size += side === 'left' ? -delta : delta;
22241
+ else return;
22242
+ e.preventDefault();
22243
+ size = Math.max(min, Math.min(max, size));
22244
+ if (target === 'left') { setLeftWidthPx(size); state.leftWidth = size; }
22245
+ else window.setRightWidthPx(size);
22246
+ this.setAttribute('aria-valuenow', String(Math.round(size)));
22247
+ });
19939
22248
  handles[i].addEventListener('mousedown', function(e) {
19940
22249
  e.preventDefault();
22250
+ var handle = this;
19941
22251
  var target = this.getAttribute('data-target');
19942
22252
  var side = this.getAttribute('data-side');
19943
22253
  var el = target === 'left' ? els.left : (target === 'right' ? els.right : null);
@@ -19957,10 +22267,12 @@ function schedulePostStartupUiRendering() {
19957
22267
  var leftSize = Math.max(220, Math.min(460, newSize));
19958
22268
  setLeftWidthPx(leftSize);
19959
22269
  state.leftWidth = leftSize;
22270
+ handle.setAttribute('aria-valuenow', String(Math.round(leftSize)));
19960
22271
  } else if (side === 'left') {
19961
22272
  var newSize2 = startSize - dx;
19962
22273
  var rightSize = Math.max(340, Math.min(680, newSize2));
19963
22274
  window.setRightWidthPx(rightSize);
22275
+ handle.setAttribute('aria-valuenow', String(Math.round(rightSize)));
19964
22276
  } else if (side === 'top') {
19965
22277
  var newSize3 = startSize - dy;
19966
22278
  el.style.height = Math.max(0, Math.min(400, newSize3)) + 'px';
@@ -20059,6 +22371,10 @@ function schedulePostStartupUiRendering() {
20059
22371
  var startupHydrationError = null;
20060
22372
  cacheEls();
20061
22373
  setupAgentWorkEvents();
22374
+ if (api.onEditorCompletionDelta && !state._editorCompletionDeltaReady) {
22375
+ state._editorCompletionDeltaReady = true;
22376
+ api.onEditorCompletionDelta(function(payload) { window.applyEditorCompletionDelta(payload); });
22377
+ }
20062
22378
  if (api.onBrowserEnsureGuest) {
20063
22379
  api.onBrowserEnsureGuest(function(target) {
20064
22380
  cancelBrowserGuestIdleDestroy();
@@ -20171,6 +22487,7 @@ function schedulePostStartupUiRendering() {
20171
22487
  startupHydrationError = e;
20172
22488
  }
20173
22489
  applyUiAppearance();
22490
+ setupGuiKeyboard();
20174
22491
  window.renderContextWindow();
20175
22492
  if (api.onAutomationUpdated) {
20176
22493
  api.onAutomationUpdated(function() {
@@ -20216,12 +22533,12 @@ function schedulePostStartupUiRendering() {
20216
22533
 
20217
22534
  function stopRunningFromEscape(event) {
20218
22535
  if (!event || event.key !== 'Escape' || event.defaultPrevented || escapeBelongsToFocusedControl(event)) return false;
20219
- if (currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
22536
+ if (currentFlowRunning() && flowTakeoverMatchesCurrent()) {
20220
22537
  event.preventDefault();
20221
22538
  window.stopFlowRun();
20222
22539
  return true;
20223
22540
  }
20224
- if (isCurrentConversationRunning() && !promptHasText()) {
22541
+ if (isCurrentConversationRunning()) {
20225
22542
  event.preventDefault();
20226
22543
  window.stopCurrentConversation();
20227
22544
  return true;
@@ -20234,22 +22551,30 @@ function schedulePostStartupUiRendering() {
20234
22551
  // after focus moved to the chat, title bar, button, or another non-editor
20235
22552
  // surface. Modal/editor/select Escape behavior remains owned by that UI.
20236
22553
  document.addEventListener('keydown', function(event) {
22554
+ if (!event || event.defaultPrevented || event.repeat || event.isComposing || event.key === 'Process' || event.keyCode === 229) return;
22555
+ if (event.key === 'Escape' && !event.defaultPrevented && els['sub-win-overlay'] && els['sub-win-overlay'].classList.contains('open')) {
22556
+ event.preventDefault();
22557
+ event.stopPropagation();
22558
+ window.closeSubWin();
22559
+ return;
22560
+ }
20237
22561
  if (stopRunningFromEscape(event)) event.stopPropagation();
20238
22562
  });
20239
22563
 
20240
22564
  if (els.prompt) {
20241
22565
  els.prompt.addEventListener('keydown', function(e) {
20242
- if (e.key === 'Escape' && currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
22566
+ if (e.isComposing || e.key === 'Process' || e.keyCode === 229) return;
22567
+ if (e.key === 'Escape' && currentFlowRunning() && flowTakeoverMatchesCurrent()) {
20243
22568
  e.preventDefault();
20244
22569
  window.stopFlowRun();
20245
22570
  return;
20246
22571
  }
20247
- if (e.key === 'Escape' && isCurrentConversationRunning() && !promptHasText()) {
22572
+ if (e.key === 'Escape' && isCurrentConversationRunning()) {
20248
22573
  e.preventDefault();
20249
22574
  window.stopCurrentConversation();
20250
22575
  return;
20251
22576
  }
20252
- if (e.key === 'Enter' && e.ctrlKey) {
22577
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey) {
20253
22578
  e.preventDefault();
20254
22579
  if (state.mode === 'flow' || (currentFlowRunning() && flowTakeoverMatchesCurrent())) {
20255
22580
  window.submitCurrentAction();
@@ -20259,9 +22584,9 @@ function schedulePostStartupUiRendering() {
20259
22584
  window.sendMessage(opposite);
20260
22585
  return;
20261
22586
  }
20262
- if (e.key === 'Enter' && !e.shiftKey) {
22587
+ if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
20263
22588
  e.preventDefault();
20264
- window.submitCurrentAction();
22589
+ window.submitCurrentAction('enter');
20265
22590
  }
20266
22591
  });
20267
22592
  els.prompt.addEventListener('input', function() {
@@ -20356,10 +22681,10 @@ function schedulePostStartupUiRendering() {
20356
22681
  els['editor-textarea'].addEventListener('select', window.handleEditorCaretChange);
20357
22682
  els['editor-textarea'].addEventListener('keyup', window.handleEditorCaretChange);
20358
22683
  els['editor-textarea'].addEventListener('keydown', function(e) {
22684
+ if (e.isComposing || e.key === 'Process' || e.keyCode === 229) return;
20359
22685
  if (state.editorCompletionText && e.key === 'Tab') { e.preventDefault(); window.acceptEditorCompletion(); return; }
20360
22686
  if (e.key === 'Escape' && (state.editorCompletionText || (els['editor-completion'] && els['editor-completion'].classList.contains('open')))) { e.preventDefault(); window.dismissEditorCompletion(); return; }
20361
22687
  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
22688
  if (state.editorVimEnabled && e.key === 'Escape') { state.editorVimMode = 'normal'; state.editorVimPending = ''; e.preventDefault(); window.renderNativeEditor(); return; }
20364
22689
  if (window.handleEditorVimKey(e)) return;
20365
22690
  });