newmark-agent 0.3.11 → 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 (69) hide show
  1. package/config.example.json +6 -0
  2. package/dist/cli-commands.d.ts +8 -0
  3. package/dist/cli-commands.js +216 -16
  4. package/dist/cli-discovery.d.ts +15 -0
  5. package/dist/cli-discovery.js +182 -0
  6. package/dist/cli-help.d.ts +2 -0
  7. package/dist/cli-help.js +25 -1
  8. package/dist/context/domain/types.d.ts +37 -0
  9. package/dist/context/services/context-orchestrator.js +2 -0
  10. package/dist/conversation-utility-host.bundle.cjs +1503 -214
  11. package/dist/conversation-utility-host.js +3 -0
  12. package/dist/core/agent.d.ts +157 -8
  13. package/dist/core/agent.js +1176 -112
  14. package/dist/core/agentKernel/agent-loop.js +29 -3
  15. package/dist/core/agentKernel/types.d.ts +7 -0
  16. package/dist/core/agentKernelRunner.d.ts +2 -0
  17. package/dist/core/agentKernelRunner.js +174 -27
  18. package/dist/core/config.d.ts +7 -2
  19. package/dist/core/config.js +24 -6
  20. package/dist/core/conversationKernel.d.ts +5 -0
  21. package/dist/core/conversationKernel.js +30 -1
  22. package/dist/core/dshCompatibility.d.ts +198 -0
  23. package/dist/core/dshCompatibility.js +600 -0
  24. package/dist/core/electronUtilityAgentClient.d.ts +4 -0
  25. package/dist/core/electronUtilityAgentClient.js +4 -0
  26. package/dist/core/electronUtilityRuntimePool.d.ts +19 -0
  27. package/dist/core/electronUtilityRuntimePool.js +76 -0
  28. package/dist/core/flow-runner.js +1 -1
  29. package/dist/core/mcpManager.d.ts +1 -0
  30. package/dist/core/mcpManager.js +100 -10
  31. package/dist/core/modelValidationStore.d.ts +4 -1
  32. package/dist/core/modelValidationStore.js +7 -1
  33. package/dist/core/subagent.d.ts +6 -0
  34. package/dist/core/subagent.js +22 -1
  35. package/dist/core/toolPolicy.d.ts +6 -0
  36. package/dist/core/toolPolicy.js +49 -1
  37. package/dist/core/types.d.ts +1 -1
  38. package/dist/core/utilityAgentProtocol.d.ts +8 -1
  39. package/dist/core/workspace.d.ts +15 -0
  40. package/dist/core/workspace.js +62 -1
  41. package/dist/core/wslAgentClient.d.ts +4 -0
  42. package/dist/core/wslAgentClient.js +4 -0
  43. package/dist/core/wslAgentProtocol.d.ts +8 -1
  44. package/dist/core/wslAgentRuntimePool.d.ts +12 -0
  45. package/dist/core/wslAgentRuntimePool.js +71 -0
  46. package/dist/launcher.js +48 -11
  47. package/dist/llm/provider.d.ts +9 -6
  48. package/dist/llm/provider.js +89 -36
  49. package/dist/main.js +326 -52
  50. package/dist/preload.js +17 -0
  51. package/dist/providers/chat-completions.adapter.js +42 -20
  52. package/dist/providers/provider-adapter.d.ts +3 -0
  53. package/dist/providers/provider-events.d.ts +7 -0
  54. package/dist/providers/provider-events.js +44 -0
  55. package/dist/providers/responses.adapter.js +1 -3
  56. package/dist/toolchain/registry/tool-registry.d.ts +13 -1
  57. package/dist/toolchain/registry/tool-registry.js +8 -0
  58. package/dist/toolchain/registry-seeder.js +51 -5
  59. package/dist/tools/index.js +11 -2
  60. package/dist/tools/nativeTools.js +5 -1
  61. package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
  62. package/dist/tui/src/app.js +47 -13
  63. package/dist/tui/src/render.js +23 -7
  64. package/dist/tui/src/state.js +61 -9
  65. package/dist/ui/index.html +2775 -284
  66. package/dist/ui/lucide-sprite.svg +26 -0
  67. package/dist/wsl-agent-host.bundle.cjs +1503 -214
  68. package/dist/wsl-agent-host.js +3 -0
  69. package/package.json +16 -5
@@ -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',
@@ -6063,9 +6566,16 @@ var NEWMARK_I18N = {
6063
6566
  'flow.nextDisabled': 'Flow only accepts Guide input; Next is disabled.',
6064
6567
  'flow.saved': 'Flow saved',
6065
6568
  'flow.emptyRun': 'Flow is empty and cannot run.',
6569
+ 'flow.noAvailable': 'No workflows are available to run.',
6066
6570
  'flow.started': 'Flow started',
6067
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',
6068
6577
  'plugins.mcp': 'MCP Management',
6578
+ 'plugins.dsh': 'DSH Plugin',
6069
6579
  'plugins.management': 'Skills Management',
6070
6580
  'plugins.market': 'Skills Market',
6071
6581
  'plugins.github': 'GitHub CLI',
@@ -6123,6 +6633,74 @@ var NEWMARK_I18N = {
6123
6633
  'plugins.mcpNoServers': 'No user MCP servers.',
6124
6634
  'plugins.mcpDiscovered': 'Discovered from plugins',
6125
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.',
6126
6704
  'workspace.new': 'New workspace',
6127
6705
  'workspace.type': 'Type',
6128
6706
  'workspace.internal': 'Internal workspace',
@@ -6222,6 +6800,21 @@ var NEWMARK_I18N = {
6222
6800
  'status.contextModeModel': 'model',
6223
6801
  'status.messages': 'messages',
6224
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.',
6225
6818
  'status.recentFiles': 'Recent file changes',
6226
6819
  'status.noFileChanges': 'No file changes recorded for the latest run.',
6227
6820
  'status.pendingOptions': 'Pending options',
@@ -6242,6 +6835,8 @@ var NEWMARK_I18N = {
6242
6835
  'conversation.reorderFailed': 'Could not save the conversation order.',
6243
6836
  'conversation.pin': 'Pin conversation',
6244
6837
  'conversation.unpin': 'Unpin conversation',
6838
+ 'conversation.branchCommunication': 'Allow branch communication',
6839
+ 'conversation.branchCommunicationBadge': 'Branch communication',
6245
6840
  'conversation.loadingIsolated': 'Loading isolated conversation...',
6246
6841
  'conversation.locked': 'Current conversation is locked while the agent is working.',
6247
6842
  'queue.next': 'Next',
@@ -6285,6 +6880,48 @@ var NEWMARK_I18N = {
6285
6880
  zh: {
6286
6881
  'app.ready': '就绪',
6287
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}',
6288
6925
  'top.minimize': '最小化',
6289
6926
  'top.maximize': '最大化',
6290
6927
  'top.close': '关闭',
@@ -6296,12 +6933,14 @@ var NEWMARK_I18N = {
6296
6933
  'left.flow': '工作流',
6297
6934
  'left.settings': '设置',
6298
6935
  'left.workspaces': '工作区',
6936
+ 'left.conversations': '对话',
6299
6937
  'left.new': '新建',
6300
6938
  'left.collapse': '折叠左侧栏',
6301
6939
  'left.collapseSecondary': '折叠工作区面板',
6302
6940
  'input.placeholder': '输入指令...',
6303
6941
  'input.send': '发送',
6304
6942
  'input.stop': '停止',
6943
+ 'input.continue': '继续',
6305
6944
  'input.guide': 'Guide',
6306
6945
  'input.next': 'Next',
6307
6946
  'mode.build': 'Build',
@@ -6628,9 +7267,16 @@ var NEWMARK_I18N = {
6628
7267
  'flow.nextDisabled': 'Flow 仅允许 Guide 输入,已禁用 Next。',
6629
7268
  'flow.saved': 'Flow 已保存',
6630
7269
  'flow.emptyRun': 'Flow 为空,无法运行。',
7270
+ 'flow.noAvailable': '当前没有可运行的工作流。',
6631
7271
  'flow.started': 'Flow 已启动',
6632
7272
  'plugins.title': '插件',
7273
+ 'plugins.tabMcp': 'MCP',
7274
+ 'plugins.tabDsh': 'DSH Plugin',
7275
+ 'plugins.tabSkills': 'Skills',
7276
+ 'plugins.tabMarket': 'Market',
7277
+ 'plugins.tabGithub': 'GitHub',
6633
7278
  'plugins.mcp': 'MCP 管理',
7279
+ 'plugins.dsh': 'DSH Plugin',
6634
7280
  'plugins.management': 'Skills 管理',
6635
7281
  'plugins.market': 'Skills Market',
6636
7282
  'plugins.github': 'GitHub CLI',
@@ -6688,6 +7334,74 @@ var NEWMARK_I18N = {
6688
7334
  'plugins.mcpNoServers': '暂无用户 MCP 服务器。',
6689
7335
  'plugins.mcpDiscovered': '从插件发现',
6690
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 工具。',
6691
7405
  'workspace.new': '新建工作区',
6692
7406
  'workspace.type': '类型',
6693
7407
  'workspace.internal': '内部工作区',
@@ -6787,6 +7501,21 @@ var NEWMARK_I18N = {
6787
7501
  'status.contextModeModel': '模型',
6788
7502
  'status.messages': '条消息',
6789
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': '对话运行中,暂时无法压缩上下文。',
6790
7519
  'status.recentFiles': '最近文件变更',
6791
7520
  'status.noFileChanges': '最近一轮没有记录文件变更。',
6792
7521
  'status.pendingOptions': '待处理选项',
@@ -6807,6 +7536,8 @@ var NEWMARK_I18N = {
6807
7536
  'conversation.reorderFailed': '无法保存对话顺序。',
6808
7537
  'conversation.pin': '置顶对话',
6809
7538
  'conversation.unpin': '取消置顶对话',
7539
+ 'conversation.branchCommunication': '允许分支交流',
7540
+ 'conversation.branchCommunicationBadge': '分支交流',
6810
7541
  'conversation.loadingIsolated': '正在加载隔离对话...',
6811
7542
  'conversation.locked': 'Agent 工作中,当前对话已锁定。',
6812
7543
  'queue.next': '下一轮',
@@ -6931,6 +7662,10 @@ function applyLanguageToUi() {
6931
7662
  }
6932
7663
  var wsHead = document.getElementById('left-ws-header');
6933
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'));
6934
7669
  var wsAdd = document.querySelector('#left-ws-add span:last-child');
6935
7670
  if (wsAdd) wsAdd.textContent = t('left.new');
6936
7671
  setTitleAndLabel('.secondary-top button[onclick="window.openWsSettings()"]', t('workspace.settingsTitle'));
@@ -6956,6 +7691,10 @@ function applyLanguageToUi() {
6956
7691
  if (browserUrl) browserUrl.setAttribute('placeholder', t('browser.url'));
6957
7692
  var terminalInputs = document.querySelectorAll('.terminal-input');
6958
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'));
6959
7698
  var terminalReady = document.querySelector('#terminal-pane-0 .terminal-output span');
6960
7699
  if (terminalReady && terminalReady.textContent === 'Terminal ready') terminalReady.textContent = t('terminal.ready');
6961
7700
  var terminalStatusSpans = document.querySelectorAll('.terminal-output span');
@@ -8218,6 +8957,8 @@ window.toggleWorkReview = function(head) {
8218
8957
  var review = head && head.closest ? head.closest('.work-review') : null;
8219
8958
  if (!review) return;
8220
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');
8221
8962
  };
8222
8963
 
8223
8964
  window.toggleWorkReviewFiles = function(button) {
@@ -8322,22 +9063,33 @@ function addWorkReview(diffs) {
8322
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>' +
8323
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>' +
8324
9065
  '<div class="work-review-actions"><button class="work-review-btn" onclick="window.openWorkReview(this);event.stopPropagation()">' + esc(t('review.open')) + '</button>' +
8325
- '<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>' +
8326
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>';
8327
9068
  els['chat-area'].appendChild(review);
8328
9069
  autoScrollIfAtBottom();
8329
9070
  return review;
8330
9071
  }
8331
9072
 
8332
- function updateMsg(div, text, role, mode, model) {
9073
+ var STREAMING_PLAIN_TEXT_THRESHOLD = 12000;
9074
+ var STREAMING_LARGE_TEXT_INTERVAL_MS = 100;
9075
+
9076
+ function updateMsg(div, text, role, mode, model, options) {
8333
9077
  if (!div) return;
8334
9078
  if (role) div.className = 'chat-msg ' + role;
8335
9079
  text = redactSensitiveText(text);
8336
9080
  div._newmarkMessageText = String(text || '');
8337
9081
  var body = div.querySelector('.msg-body');
8338
- if (body) body.innerHTML = renderMessageContent(text);
9082
+ var streaming = !!(options && options.streaming);
9083
+ if (body) {
9084
+ // Re-running the full Markdown parser and rebuilding the entire message
9085
+ // DOM for every streamed token makes long responses monopolize the
9086
+ // renderer event loop. Keep large in-flight responses as one safe text
9087
+ // node; the terminal `done`/`final_response` path renders Markdown once.
9088
+ if (streaming && String(text || '').length >= STREAMING_PLAIN_TEXT_THRESHOLD) body.textContent = text;
9089
+ else body.innerHTML = renderMessageContent(text);
9090
+ }
8339
9091
  var meta = div.querySelector('.meta');
8340
- if (meta && (mode || model)) {
9092
+ if (meta && (mode || model) && !streaming) {
8341
9093
  var roleLabel = div.classList.contains('user')
8342
9094
  ? (String(mode || '').toLowerCase() === 'flow-user-input' ? t('flow.userInput') : t('message.user'))
8343
9095
  : (div.classList.contains('workflow') ? t('message.workflow') : (div.classList.contains('system') ? t('message.system') : t('message.agent')));
@@ -8746,6 +9498,60 @@ function setConversationRuntimeState(target, status, runId, extra) {
8746
9498
  return next;
8747
9499
  }
8748
9500
 
9501
+ // A renderer send marks its target as provisionally running before the IPC
9502
+ // request reaches the target runtime. Escape/Stop can therefore arrive in
9503
+ // that handoff window while the backend still reports `not_running`. Remember
9504
+ // the exact provisional run so the pending send can be cancelled before it
9505
+ // allocates a provider request; once the backend has a real run, the normal
9506
+ // target-scoped stop path takes over.
9507
+ function markProvisionalStop(target, runId) {
9508
+ if (!state.pendingProvisionalStops) state.pendingProvisionalStops = {};
9509
+ state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)] = String(runId || '');
9510
+ }
9511
+
9512
+ function pendingProvisionalStopRunId(target) {
9513
+ var key = runtimeKeyFor(target.workspaceId, target.conversationId);
9514
+ if (!state.pendingProvisionalStops || !Object.prototype.hasOwnProperty.call(state.pendingProvisionalStops, key)) return undefined;
9515
+ return state.pendingProvisionalStops[key];
9516
+ }
9517
+
9518
+ function clearProvisionalStop(target) {
9519
+ if (state.pendingProvisionalStops) delete state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)];
9520
+ }
9521
+
9522
+ function consumeProvisionalStop(target, runId) {
9523
+ var pending = pendingProvisionalStopRunId(target);
9524
+ if (pending === undefined || String(pending) !== String(runId || '')) return false;
9525
+ clearProvisionalStop(target);
9526
+ return true;
9527
+ }
9528
+
9529
+ function requestBackendStopForProvisionalStart(target, runId) {
9530
+ var stopPromise = api.stopConversation
9531
+ ? api.stopConversation({ target: target, runId: String(runId || ''), force: false })
9532
+ : (api.abortConversation ? api.abortConversation(target) : Promise.resolve({ action: 'not_running' }));
9533
+ Promise.resolve(stopPromise).then(function(result) {
9534
+ var action = String(result && result.action || '');
9535
+ if (action === 'not_running' || action === 'stale') {
9536
+ return refreshConversationRuntimeAfterStopRace(target, runId, result);
9537
+ }
9538
+ if (action === 'force' || String(result && result.status || '') === 'force_interrupted') {
9539
+ setConversationRuntimeState(target, 'force_interrupted', runId, { provisional: false });
9540
+ return;
9541
+ }
9542
+ if (action === 'graceful') {
9543
+ setConversationRuntimeState(target, 'stopping', runId, { provisional: false });
9544
+ return;
9545
+ }
9546
+ if (result === true || ['idle', 'interrupted'].indexOf(String(result && result.status || '')) >= 0) {
9547
+ setConversationRuntimeState(target, 'interrupted', runId, { provisional: false });
9548
+ }
9549
+ }).catch(function(error) {
9550
+ setConversationRuntimeState(target, 'running', runId, { provisional: false });
9551
+ showUiNotice(error && error.message ? error.message : String(error), 'error', 'stop-provisional-' + runtimeKeyFor(target.workspaceId, target.conversationId) + '-' + String(runId || ''));
9552
+ });
9553
+ }
9554
+
8749
9555
  function formatWorkDuration(milliseconds) {
8750
9556
  var seconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000));
8751
9557
  var hours = Math.floor(seconds / 3600);
@@ -8792,7 +9598,7 @@ function publicWorkEvent(event) {
8792
9598
  var content = String(event && event.content || '');
8793
9599
  var toolArgs = String(event && event.toolArgs || '');
8794
9600
  if (/<think(?:\s|>)/i.test(content) || /<\/think>/i.test(content) || /<\/?think\b|(?:reasoning_content|thinking_delta)\s*[::]/i.test(toolArgs)) return false;
8795
- 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;
8796
9602
  }
8797
9603
 
8798
9604
  function publicToolNameForUi(value) {
@@ -9051,7 +9857,12 @@ function syncWorkRunsSnapshot(runs, target, branchId) {
9051
9857
  return item;
9052
9858
  });
9053
9859
  state.workRunsByBranch[workRunBranchKey(target, selectedBranchId)] = normalized;
9054
- state.workRunsByTarget[key] = normalized;
9860
+ // 串分支修复:全局显示列表(workRunsByTarget)只在 sync 的分支就是「浏览分支」时
9861
+ // 才更新。当运行分支(runtime)的 streaming 结果到达、而用户正在浏览另一个分支时,
9862
+ // 只按分支存储(workRunsByBranch),不污染当前浏览分支的显示。
9863
+ if (selectedBranchId === branchIds.viewed) {
9864
+ state.workRunsByTarget[key] = normalized;
9865
+ }
9055
9866
  return normalized;
9056
9867
  }
9057
9868
 
@@ -9343,6 +10154,23 @@ function renderWorkToolGroup(event, eventIndex) {
9343
10154
  activityRows + '</div></details>';
9344
10155
  }
9345
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
+
9346
10174
  function workEventLabel(event) {
9347
10175
  var type = String(event && event.type || 'status').toLowerCase();
9348
10176
  var content = String(event && event.content || '').trim();
@@ -9447,6 +10275,16 @@ function renderWorkRunEvents(run, includeGuides) {
9447
10275
  }
9448
10276
  if (!rawEvent) continue;
9449
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
+ }
9450
10288
  events.push(rawEvent);
9451
10289
  }
9452
10290
  flushPublicText();
@@ -9476,10 +10314,18 @@ function renderWorkRunEvents(run, includeGuides) {
9476
10314
  var responseLabel = type === 'response' && terminalInterrupted
9477
10315
  ? (currentLang() === 'zh' ? '未完成回复片段\n' : 'Incomplete response fragment\n') + workEventLabel(event)
9478
10316
  : workEventLabel(event);
9479
- return '<div class="conversation-work-event narrative"><div class="conversation-work-event-content">' + renderMessageContent(responseLabel) + '</div></div>';
10317
+ // While a run is still receiving text, this view can be rebuilt once per
10318
+ // event. Avoid invoking the full Markdown parser for a large live
10319
+ // narrative; the final response path still gets the normal rich render.
10320
+ var liveNarrative = ['running', 'stopping', 'force_restarting'].indexOf(String(run && run.status || '').toLowerCase()) >= 0;
10321
+ var narrativeHtml = liveNarrative && String(responseLabel || '').length >= STREAMING_PLAIN_TEXT_THRESHOLD
10322
+ ? esc(responseLabel)
10323
+ : renderMessageContent(responseLabel);
10324
+ return '<div class="conversation-work-event narrative"><div class="conversation-work-event-content">' + narrativeHtml + '</div></div>';
9480
10325
  }
9481
10326
  if (type.indexOf('guide') === 0 || event.guide) return renderWorkRunGuideMessage(event);
9482
10327
  if (type === 'tool_group') return renderWorkToolGroup(event, eventIndex);
10328
+ if (type === 'thought') return renderWorkThought(event, eventIndex);
9483
10329
  var activitySummary = type.indexOf('tool_') === 0 && type !== 'tool_call' && type !== 'tool_result';
9484
10330
  var cls = type.indexOf('guide') === 0 ? ' guide' : (type === 'error' ? ' error' : (activitySummary ? ' activity-summary' : ''));
9485
10331
  var label = activitySummary ? workToolActivityLabel(event.activity, Number(event.count || 1), !!event.completed) : workEventLabel(event) + (event.completed ? (currentLang() === 'zh' ? ' · 已完成' : ' · completed') : '');
@@ -10223,7 +11069,13 @@ function applyStreamingWorkflowText(ui, eventConversationId, mode, model) {
10223
11069
  }
10224
11070
  return;
10225
11071
  }
10226
- updateMsg(ensureActiveAssistantMsg(mode, model, eventConversationId), ui.activeWorkflowText, 'assistant', mode, model);
11072
+ var largeText = ui.activeWorkflowText.length >= STREAMING_PLAIN_TEXT_THRESHOLD;
11073
+ if (largeText) {
11074
+ var now = Date.now();
11075
+ if (ui._lastLargeStreamRenderAt && now - ui._lastLargeStreamRenderAt < STREAMING_LARGE_TEXT_INTERVAL_MS) return;
11076
+ ui._lastLargeStreamRenderAt = now;
11077
+ }
11078
+ updateMsg(ensureActiveAssistantMsg(mode, model, eventConversationId), ui.activeWorkflowText, 'assistant', mode, model, { streaming: true });
10227
11079
  }
10228
11080
 
10229
11081
  function scheduleStreamingWorkflowTextFlush(ui, eventConversationId, eventWorkspaceId) {
@@ -10254,6 +11106,7 @@ function renderAgentWorkEvent(event) {
10254
11106
  ui.activeWorkflowText = '';
10255
11107
  ui.lastCompletedWorkflow = null;
10256
11108
  ui._streamFlushPending = false;
11109
+ ui._lastLargeStreamRenderAt = 0;
10257
11110
  } else if (type === 'text') {
10258
11111
  ui.activeWorkflowText = (ui.activeWorkflowText || '') + content;
10259
11112
  if (workRun) return;
@@ -10264,6 +11117,7 @@ function renderAgentWorkEvent(event) {
10264
11117
  ui.activeWorkflowText = '';
10265
11118
  ui.activeWorkflowMsg = null;
10266
11119
  ui._streamFlushPending = false;
11120
+ ui._lastLargeStreamRenderAt = 0;
10267
11121
  } else if (type === 'final_response' && workRun) {
10268
11122
  ui.activeWorkflowText = '';
10269
11123
  ui.activeWorkflowMsg = null;
@@ -10297,6 +11151,7 @@ function renderAgentWorkEvent(event) {
10297
11151
  }
10298
11152
  ui.activeWorkflowMsg = null;
10299
11153
  ui.activeWorkflowText = '';
11154
+ ui._lastLargeStreamRenderAt = 0;
10300
11155
  finishToolBatch(eventConversationId);
10301
11156
  } else if (type === 'error') {
10302
11157
  flushStreamingWorkflowTextNow(ui, eventConversationId, event.mode || state.mode, event.model || state.model);
@@ -10366,7 +11221,14 @@ function appendAgentWorkEvent(event) {
10366
11221
  var visibleRuntimeBranch = active && isViewingRuntimeConversationBranch(target);
10367
11222
  if (active) markConversationTracked(id, state.conversationTrackMs || 300000, workspaceId);
10368
11223
  if (active || isConversationTracked(id, workspaceId)) cacheAgentWorkEvent(event);
10369
- if (event.type === 'start') setConversationRuntimeState(target, event.status || 'running', event.runId || '', { provisional: false, generation: event.generation || 0, runtimeKey: event.runtimeKey || '' });
11224
+ var pendingProvisionalStop = (event.type === 'start' || event.status === 'running')
11225
+ ? pendingProvisionalStopRunId(target)
11226
+ : undefined;
11227
+ if (pendingProvisionalStop !== undefined && event.runId) {
11228
+ clearProvisionalStop(target);
11229
+ setConversationRuntimeState(target, 'stopping', event.runId, { provisional: false, generation: event.generation || 0, runtimeKey: event.runtimeKey || '' });
11230
+ requestBackendStopForProvisionalStart(target, event.runId);
11231
+ } else if (event.type === 'start') setConversationRuntimeState(target, event.status || 'running', event.runId || '', { provisional: false, generation: event.generation || 0, runtimeKey: event.runtimeKey || '' });
10370
11232
  else if (event.status && ['running', 'stopping', 'force_restarting'].indexOf(String(event.status)) >= 0) setConversationRuntimeState(target, event.status, event.runId || '');
10371
11233
  else if (event.status && ['completed', 'interrupted', 'force_interrupted', 'error'].indexOf(String(event.status)) >= 0) {
10372
11234
  setConversationRuntimeState(target, event.status, event.runId || '');
@@ -10486,7 +11348,12 @@ function applyConversationSnapshot(s, requestedConversationId) {
10486
11348
  if (s && s.runtimeKey) registerRuntimeKey(snapshotTarget, s.runtimeKey);
10487
11349
  hydrateConversationBranchState(s);
10488
11350
  rebindQueueToRuntimeBranch(snapshotTarget);
10489
- 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
+ }
10490
11357
  applyAutoRouteRatingState(s);
10491
11358
  if (s && s.chatMessages) {
10492
11359
  renderChatMessages(s.chatMessages);
@@ -10549,9 +11416,9 @@ function addShellBlock(title, content) {
10549
11416
  var div = document.createElement('div');
10550
11417
  div.className = 'chat-msg assistant';
10551
11418
  div.innerHTML = '<div class="shell-block collapsed">' +
10552
- '<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\')">' +
10553
11420
  '<span class="arrow">&gt;</span> ' + esc(title) +
10554
- '</div><div class="shell-block-body">' + esc(content) + '</div></div>';
11421
+ '</button><div class="shell-block-body">' + esc(content) + '</div></div>';
10555
11422
  els['chat-area'].appendChild(div);
10556
11423
  autoScrollIfAtBottom();
10557
11424
  }
@@ -10560,10 +11427,10 @@ function addDiffBlock(title, adds, dels, linesHTML) {
10560
11427
  var div = document.createElement('div');
10561
11428
  div.className = 'chat-msg assistant';
10562
11429
  div.innerHTML = '<div class="diff-block collapsed">' +
10563
- '<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\')">' +
10564
11431
  '<span class="arrow">&gt;</span> ' + esc(title) +
10565
11432
  '<span class="diff-stat"><span class="diff-add">+' + adds + '</span><span class="diff-del">-' + dels + '</span></span>' +
10566
- '</div><div class="diff-block-body">' + linesHTML + '</div></div>';
11433
+ '</button><div class="diff-block-body">' + linesHTML + '</div></div>';
10567
11434
  els['chat-area'].appendChild(div);
10568
11435
  autoScrollIfAtBottom();
10569
11436
  }
@@ -10977,18 +11844,33 @@ window.stopCurrentConversation = async function() {
10977
11844
  if (!conversationId || !runtime) return false;
10978
11845
  var target = runtime.target || currentConversationTarget(conversationId);
10979
11846
  var runId = String(runtime.runId || '');
11847
+ var provisional = runtime.provisional === true;
10980
11848
  if (String(runtime.status || '') === 'force_restarting') return false;
11849
+ if (provisional) markProvisionalStop(target, runId);
10981
11850
  var force = String(runtime.status || '') === 'stopping';
10982
11851
  if (typeof pauseQueueForTarget === 'function') pauseQueueForTarget(target);
10983
11852
  setConversationRuntimeState(target, force ? 'force_restarting' : 'stopping', runId);
10984
11853
  updateSubmitButtonState();
10985
11854
  renderConversations();
11855
+ if (provisional) {
11856
+ // The backend may not have allocated a utility runtime yet. Keep the
11857
+ // exact local cancellation marker and let sendMessage consume it, or let
11858
+ // the first backend start event issue a stop with the real run id. An
11859
+ // empty-run stop request here races runtime allocation and can return
11860
+ // not_running before the prompt reaches the worker.
11861
+ if (window.renderInputStack) window.renderInputStack();
11862
+ return true;
11863
+ }
10986
11864
  try {
10987
11865
  var result;
10988
11866
  // The target runtime supervisor owns the checkpoint + cooperative-stop
10989
11867
  // transaction. A separate awaited checkpoint can itself be starved by a
10990
11868
  // blocked worker and would prevent the supervisor from recording the first
10991
11869
  // stop, making a second-click hard restart impossible.
11870
+ // A provisional renderer run has no backend run id yet. Omitting the
11871
+ // expected id lets a concurrently-created backend run be stopped, while
11872
+ // the pending marker above cancels a request that has not reached the
11873
+ // runtime at all.
10992
11874
  if (api.stopConversation) result = await api.stopConversation({ target: target, runId: runId, force: force });
10993
11875
  else if (api.abortConversation) result = await api.abortConversation(currentConversationTarget(conversationId));
10994
11876
  var resultAction = String(result && result.action || '');
@@ -10999,6 +11881,9 @@ window.stopCurrentConversation = async function() {
10999
11881
  await refreshConversationRuntimeAfterStopRace(target, runId, result);
11000
11882
  return true;
11001
11883
  }
11884
+ if (provisional && state.pendingProvisionalStops) {
11885
+ delete state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)];
11886
+ }
11002
11887
  var latestAfterStop = state.conversationRuntimeStates && state.conversationRuntimeStates[runtimeKeyFor(target.workspaceId, target.conversationId)];
11003
11888
  if (!force && latestAfterStop && String(latestAfterStop.runId || '') === runId && String(latestAfterStop.status || '') !== 'stopping') {
11004
11889
  // A second click or a terminal worker event won the race while the first
@@ -11027,7 +11912,7 @@ window.stopCurrentConversation = async function() {
11027
11912
  return true;
11028
11913
  };
11029
11914
 
11030
- window.submitCurrentAction = function() {
11915
+ window.submitCurrentAction = function(source) {
11031
11916
  if (currentFlowRunning() && flowTakeoverMatchesCurrent()) {
11032
11917
  if (!promptHasText()) {
11033
11918
  window.stopFlowRun();
@@ -11062,6 +11947,8 @@ window.submitCurrentAction = function() {
11062
11947
  return;
11063
11948
  }
11064
11949
  if (isCurrentConversationRunning() && !promptHasText()) {
11950
+ // 回车:运行中空输入不操作(打断仅通过 Esc 或点击打断按钮)。
11951
+ if (source === 'enter') return;
11065
11952
  window.stopCurrentConversation();
11066
11953
  return;
11067
11954
  }
@@ -11103,6 +11990,98 @@ window.renderContextWindow = function() {
11103
11990
  ring.style.mask = 'radial-gradient(farthest-side, transparent 58%, #000 60%)';
11104
11991
  var status = c.warning === 'over_limit' ? t('status.contextOverLimit') : (c.warning === 'near_limit' ? t('status.contextNearLimit') : t('status.contextTokens'));
11105
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
+ });
11106
12085
  };
11107
12086
 
11108
12087
  window.contextWindowTooltipHtml = function() {
@@ -11723,6 +12702,7 @@ window.renderQueuePanel = function() {
11723
12702
  }
11724
12703
  var toggle = panel.querySelector('#queue-expand-btn');
11725
12704
  if (toggle) {
12705
+ toggle.setAttribute('aria-expanded', state.queueCollapsed ? 'false' : 'true');
11726
12706
  toggle.title = state.queueCollapsed ? t('queue.expand') : t('queue.collapse');
11727
12707
  toggle.innerHTML = iconSvg(state.queueCollapsed ? 'chevron-up' : 'chevron-down', toggle.title, 'tiny');
11728
12708
  }
@@ -12012,7 +12992,14 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12012
12992
  var displayText = optimisticAttachments.length
12013
12993
  ? rawText + (rawText ? '\n\n' : '') + '[' + optimisticAttachments.length + ' image attachment' + (optimisticAttachments.length === 1 ? '' : 's') + ']'
12014
12994
  : text;
12015
- 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
+ }
12016
13003
 
12017
13004
  var lockedConversationId = activeConversationId();
12018
13005
  var lockedTarget = currentConversationTarget(lockedConversationId);
@@ -12233,6 +13220,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12233
13220
  var executionMode = opts.goalDeclaration || requestedMode === 'goal' ? 'build'
12234
13221
  : ((opts.forceBuild || idleBuildNextImmediate) && requestedMode === 'build' ? 'build' : requestedMode);
12235
13222
  await syncConversationExecutionState(executionMode, effectiveInputMode);
13223
+ if (consumeProvisionalStop(lockedTarget, localRunId)) {
13224
+ // Stop/Escape won before the backend accepted the prompt. Do not send the
13225
+ // provider request after the user has already cancelled it.
13226
+ delete state.activeSendCallsByTarget[lockedRuntimeKey];
13227
+ setConversationRuntimeState(lockedTarget, 'interrupted', localRunId, { provisional: false });
13228
+ setWorking(!!runningConversationRecord(activeConversationId()));
13229
+ window.renderInputStack();
13230
+ return { status: 'interrupted', runId: localRunId, target: lockedTarget, cancelledBeforeStart: true };
13231
+ }
12236
13232
  var renderOnViewedBranch = !queuedRuntimeBranch || queueBranchPathForTarget(lockedTarget, 'viewed') === queueBranchPathForTarget(lockedTarget, 'runtime');
12237
13233
  if (isActiveConversationTarget(lockedTarget) && renderOnViewedBranch) {
12238
13234
  if (opts.resumeGuideRunId && requestMessage.clientMessageId) {
@@ -12262,10 +13258,12 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12262
13258
  renderAutoRouteRatingControls();
12263
13259
  }
12264
13260
  var responseMsg = null;
13261
+ var sendFailure = '';
12265
13262
  try {
12266
13263
  var sendPromise = api.sendMessage(requestMessage, lockedTarget);
12267
13264
  if (requestedMode === 'goal') activateSubmittedGoal(opts.goalObjective || rawText);
12268
13265
  var r = await sendPromise;
13266
+ if (r && r.error) sendFailure = String(r.error);
12269
13267
  if (isActiveConversationTarget(lockedTarget)) applyReturnedGoalState(r);
12270
13268
  if (r && r.runId) {
12271
13269
  var anchorStore = workRunAnchorIndexStore(lockedTarget);
@@ -12346,9 +13344,10 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12346
13344
  }
12347
13345
  }
12348
13346
  } catch(e) {
13347
+ sendFailure = e && e.message ? String(e.message) : String(e || 'Agent run failed.');
12349
13348
  if (isActiveConversationTarget(lockedTarget)) {
12350
13349
  responseMsg = conversationWorkUiState(lockedConversationId, lockedTarget.workspaceId).activeWorkflowMsg || addMsg('assistant', '', state.mode, state.model);
12351
- updateMsg(responseMsg, formatChatError(e && e.message, 'Agent run failed.'), 'error', state.mode, state.model);
13350
+ updateMsg(responseMsg, formatChatError(sendFailure, 'Agent run failed.'), 'error', state.mode, state.model);
12352
13351
  }
12353
13352
  }
12354
13353
  if (api.getState) {
@@ -12399,7 +13398,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
12399
13398
  if (resumedModeResult != null) state._syncedMode = state.mode;
12400
13399
  }
12401
13400
  var finalRuntime = state.conversationRuntimeStates && state.conversationRuntimeStates[lockedRuntimeKey];
12402
- if (!finalRuntime || !finalRuntime.runId || finalRuntime.runId === localRunId || (r && finalRuntime.runId === r.runId)) {
13401
+ var finalRunMatches = !finalRuntime || !finalRuntime.runId || finalRuntime.runId === localRunId || (r && finalRuntime.runId === r.runId);
13402
+ if (sendFailure) {
13403
+ // The IPC contract returns terminal failures as { error } while the
13404
+ // target-scoped error work event may arrive on the next task. Never let
13405
+ // the renderer's provisional run fall through to completed in that gap.
13406
+ if (finalRunMatches) {
13407
+ setConversationRuntimeState(lockedTarget, 'error', (r && r.runId) || (finalRuntime && finalRuntime.runId) || localRunId, { provisional: false });
13408
+ }
13409
+ } else if (finalRunMatches) {
12403
13410
  setConversationRuntimeState(lockedTarget, 'completed', (r && r.runId) || localRunId);
12404
13411
  }
12405
13412
  setWorking(!!runningConversationRecord(activeConversationId()));
@@ -12740,19 +13747,25 @@ window.toggleBottom = function() {
12740
13747
  };
12741
13748
 
12742
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
+
12743
13754
  window.upgradeRightSidebar = function() {
12744
13755
  var rightTabs = els['right-tabs'] || document.getElementById('right-tabs');
12745
13756
  var rightContent = els['right-content'] || document.getElementById('right-content');
12746
13757
  if (!rightTabs || !rightContent || rightTabs.getAttribute('data-upgraded') === 'true') return;
12747
13758
  rightTabs.setAttribute('data-upgraded', 'true');
13759
+ rightTabs.setAttribute('role', 'tablist');
13760
+ rightTabs.setAttribute('aria-label', t('right.files'));
12748
13761
  rightTabs.innerHTML =
12749
- '<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>' +
12750
- '<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>' +
12751
- '<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>' +
12752
- '<button class="tab-btn" data-tab="subagent" onclick="window.switchRightTab(&quot;subagent&quot;)" title="' + escAttr(t('right.subagents')) + '">' + iconOnly('bot', t('right.subagents')) + '</button>' +
12753
- '<button class="tab-btn" data-tab="browser" onclick="window.switchRightTab(&quot;browser&quot;)" title="' + escAttr(t('right.browser')) + '">' + iconOnly('globe', t('right.browser')) + '</button>' +
12754
- '<button class="tab-btn" data-tab="status" onclick="window.switchRightTab(&quot;status&quot;)" title="' + escAttr(t('right.status')) + '">' + iconOnly('activity', t('right.status')) + '</button>' +
12755
- '<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') +
12756
13769
  '<div class="tab-divider"></div>' +
12757
13770
  '<button class="tab-btn" onclick="window.toggleRight()" title="' + escAttr(t('right.close')) + '">' + iconOnly('x', t('right.close')) + '</button>';
12758
13771
 
@@ -12760,7 +13773,7 @@ window.upgradeRightSidebar = function() {
12760
13773
  if (filePanel && !filePanel.getAttribute('data-upgraded')) {
12761
13774
  filePanel.setAttribute('data-upgraded', 'true');
12762
13775
  var tree = document.getElementById('file-tree-container');
12763
- 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>';
12764
13777
  if (tree) document.getElementById('file-tree-container').innerHTML = tree.innerHTML;
12765
13778
  }
12766
13779
  var subPanel = document.getElementById('panel-subagent');
@@ -12791,6 +13804,14 @@ window.upgradeRightSidebar = function() {
12791
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>';
12792
13805
  rightContent.appendChild(archivesPanel);
12793
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
+ }
12794
13815
  };
12795
13816
 
12796
13817
  window.switchRightTab = function(tab) {
@@ -12804,11 +13825,16 @@ window.switchRightTab = function(tab) {
12804
13825
  if (!rightTabs || !rightContent) return;
12805
13826
  var btns = rightTabs.querySelectorAll('.tab-btn[data-tab]');
12806
13827
  for (var i = 0; i < btns.length; i++) {
12807
- 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;
12808
13832
  }
12809
13833
  var panels = rightContent.querySelectorAll('.tab-panel');
12810
13834
  for (var j = 0; j < panels.length; j++) {
12811
- 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;
12812
13838
  }
12813
13839
  // Load content on first access
12814
13840
  if (tab === 'file-tree') window.loadFileTree();
@@ -12900,7 +13926,7 @@ window.renderConversationPlan = function() {
12900
13926
  var plan = normalizeConversationPlan(state.conversationPlan);
12901
13927
  var items = plan.items || [];
12902
13928
  var html = '<div class="plan-compose">' +
12903
- '<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()">' +
12904
13930
  '<button class="sec-btn primary" onclick="window.addConversationPlanItem()">' + esc(t('plan.add')) + '</button>' +
12905
13931
  '</div>';
12906
13932
  if (!items.length) {
@@ -12968,7 +13994,7 @@ window.editConversationPlanItem = function(idx) {
12968
13994
  var rows = target.querySelectorAll('.plan-row');
12969
13995
  var row = rows[idx];
12970
13996
  if (!row) return;
12971
- 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 + ')">';
12972
13998
  var actions = row.querySelector('.plan-actions');
12973
13999
  if (actions) {
12974
14000
  actions.innerHTML = '<button class="archive-action-btn" onclick="window.saveConversationPlanEdit(' + idx + ')">' + esc(t('common.save')) + '</button>' +
@@ -13119,6 +14145,8 @@ window.renderRightStatusPanel = function() {
13119
14145
  if (contextWindow) {
13120
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">' +
13121
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)) : '') +
13122
14150
  '</span></div></div>';
13123
14151
  }
13124
14152
  if (compression) {
@@ -13302,7 +14330,7 @@ window.terminalSend = function() {
13302
14330
  var startedPane = document.querySelector('.terminal-pane[data-session="' + resp.sessionId + '"]');
13303
14331
  var startedInput = startedPane && startedPane.querySelector('.terminal-input');
13304
14332
  if (startedInput && startedInput.value === cmd) startedInput.value = '';
13305
- return api.terminalWrite(resp.sessionId, cmd + '\r\n');
14333
+ return api.terminalWrite(resp.sessionId, cmd + '\r');
13306
14334
  }).catch(function(err) {
13307
14335
  input.disabled = false;
13308
14336
  var currentPane = window.getActiveTerminalPane();
@@ -13311,7 +14339,7 @@ window.terminalSend = function() {
13311
14339
  });
13312
14340
  }
13313
14341
  input.value = '';
13314
- api.terminalWrite(sessionId, cmd + '\r\n').catch(function(err) {
14342
+ api.terminalWrite(sessionId, cmd + '\r').catch(function(err) {
13315
14343
  if (output) output.innerHTML += '\r\n<span style="color:#ff6666;">[' + esc(t('common.error')) + '] ' + esc(err.message) + '</span>';
13316
14344
  });
13317
14345
  };
@@ -13342,6 +14370,35 @@ window.spawnTerminal = function(shellId, options) {
13342
14370
  return window.addTerminalTab(shellId, options);
13343
14371
  };
13344
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
+
13345
14402
  window.addTerminalTab = function(shellId, options) {
13346
14403
  options = options || {};
13347
14404
  shellId = normalizeTerminalShell(shellId || state._terminalShell);
@@ -13359,13 +14416,23 @@ window.addTerminalTab = function(shellId, options) {
13359
14416
  // Create tab button
13360
14417
  var tab = document.createElement('div');
13361
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;
13362
14424
  tab.setAttribute('data-tab-id', tabId);
13363
- 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>';
13364
14426
  tab.onclick = function() { window.switchTerminalTab(tabId); };
14427
+ tab.addEventListener('keydown', window.handleTerminalTabKey);
13365
14428
 
13366
14429
  // Deactivate all other tabs
13367
14430
  var allTabs = tabsContainer.querySelectorAll('.terminal-tab');
13368
- 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
+ }
13369
14436
 
13370
14437
  tabsContainer.appendChild(tab);
13371
14438
 
@@ -13373,18 +14440,20 @@ window.addTerminalTab = function(shellId, options) {
13373
14440
  var pane = document.createElement('div');
13374
14441
  pane.className = 'terminal-pane active';
13375
14442
  pane.id = 'terminal-pane-' + tabId;
14443
+ pane.setAttribute('role', 'tabpanel');
14444
+ pane.setAttribute('aria-labelledby', tab.id);
13376
14445
  pane.setAttribute('data-tab-id', tabId);
13377
14446
  pane.setAttribute('data-session', '');
13378
14447
  pane.innerHTML =
13379
14448
  '<div class="terminal-output"><span style="color:var(--accent2);">Connecting ' + shellId + '...</span>\r\n</div>' +
13380
14449
  '<div class="terminal-input-row">' +
13381
14450
  '<span class="terminal-prompt">' + shellLabel + '></span>' +
13382
- '<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()">' +
13383
14452
  '</div>';
13384
14453
 
13385
14454
  // Deactivate all other panes
13386
14455
  var allPanes = body.querySelectorAll('.terminal-pane');
13387
- 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; }
13388
14457
 
13389
14458
  body.appendChild(pane);
13390
14459
 
@@ -13435,21 +14504,33 @@ window.ensureTerminalTakeoverPane = function(session) {
13435
14504
 
13436
14505
  var tab = document.createElement('div');
13437
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;
13438
14512
  tab.setAttribute('data-tab-id', tabId);
13439
14513
  tab.setAttribute('data-takeover-session', key);
13440
14514
  tab.setAttribute('data-takeover-workspace-id', session.workspaceId || '');
13441
14515
  tab.setAttribute('data-takeover-conversation', session.conversationId || 'default');
13442
- 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>';
13443
14517
  tab.onclick = function() { window.switchTerminalTab(tabId); };
14518
+ tab.addEventListener('keydown', window.handleTerminalTabKey);
13444
14519
 
13445
14520
  var allTabs = tabsContainer.querySelectorAll('.terminal-tab');
13446
- 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
+ }
13447
14526
  tab.classList.add('active');
13448
14527
  tabsContainer.appendChild(tab);
13449
14528
 
13450
14529
  var pane = document.createElement('div');
13451
14530
  pane.className = 'terminal-pane active agent-takeover marquee-border';
13452
14531
  pane.id = 'terminal-pane-' + tabId;
14532
+ pane.setAttribute('role', 'tabpanel');
14533
+ pane.setAttribute('aria-labelledby', tab.id);
13453
14534
  pane.setAttribute('data-tab-id', tabId);
13454
14535
  pane.setAttribute('data-session', '');
13455
14536
  pane.setAttribute('data-takeover-session', key);
@@ -13461,11 +14542,11 @@ window.ensureTerminalTakeoverPane = function(session) {
13461
14542
  '<div class="terminal-output"></div>' +
13462
14543
  '<div class="terminal-input-row">' +
13463
14544
  '<span class="terminal-prompt">Agent&gt;</span>' +
13464
- '<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()">' +
13465
14546
  '</div>';
13466
14547
 
13467
14548
  var allPanes = body.querySelectorAll('.terminal-pane');
13468
- 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; }
13469
14550
  body.appendChild(pane);
13470
14551
  tabsContainer.scrollLeft = tabsContainer.scrollWidth;
13471
14552
  window.switchTerminalTab(tabId);
@@ -13560,12 +14641,17 @@ window.switchTerminalTab = function(tabId) {
13560
14641
  // Update tabs
13561
14642
  var allTabs = document.querySelectorAll('.terminal-tab');
13562
14643
  for (var i = 0; i < allTabs.length; i++) {
13563
- 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;
13564
14648
  }
13565
14649
  // Update panes
13566
14650
  var allPanes = document.querySelectorAll('.terminal-pane');
13567
14651
  for (var j = 0; j < allPanes.length; j++) {
13568
- 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;
13569
14655
  }
13570
14656
  // Update active session
13571
14657
  var activePane = document.querySelector('.terminal-pane.active');
@@ -13751,6 +14837,7 @@ function renderTodo() {
13751
14837
  label.textContent = t('goal.list') + (items.length ? ' ' + items.length : '');
13752
14838
  var toggle = wrap.querySelector('#todo-header .stack-icon-btn');
13753
14839
  if (toggle) {
14840
+ toggle.setAttribute('aria-expanded', state.todoCollapsed ? 'false' : 'true');
13754
14841
  var title = state.todoCollapsed ? t('queue.expand') : t('queue.collapse');
13755
14842
  toggle.title = title;
13756
14843
  toggle.innerHTML = iconSvg(state.todoCollapsed ? 'chevron-up' : 'chevron-down', title, 'tiny');
@@ -13763,10 +14850,10 @@ function renderTodo() {
13763
14850
  for (var i = 0; i < items.length; i++) {
13764
14851
  var item = items[i];
13765
14852
  var done = item.status === 'done';
13766
- 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') + '">' +
13767
14854
  '<span class="todo-check">' + (done ? iconSvg('check', 'done', 'tiny') : '') + '</span>' +
13768
14855
  '<span class="todo-text">' + esc(item.text) + '</span>' +
13769
- '</div>';
14856
+ '</button>';
13770
14857
  }
13771
14858
  list.innerHTML = html;
13772
14859
  }
@@ -13867,8 +14954,61 @@ window.setInputMode = function(mode, persist) {
13867
14954
  };
13868
14955
 
13869
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
+
13870
15009
  window.openSubWin = function(title, html) {
13871
15010
  var overlayOpen = els['sub-win-overlay'].classList.contains('open');
15011
+ if (!overlayOpen && document.activeElement && typeof document.activeElement.focus === 'function') subWindowOriginFocus = document.activeElement;
13872
15012
  if (overlayOpen && !state.restoringSubWindow && state.activeSubWindowView) {
13873
15013
  if (!state.subWindowStack) state.subWindowStack = [];
13874
15014
  state.subWindowStack.push({
@@ -13882,6 +15022,25 @@ window.openSubWin = function(title, html) {
13882
15022
  els['sub-win-body'].innerHTML = html;
13883
15023
  els['sub-win'].classList.toggle('memory-lab-window', !!(state.activeSubWindowView && state.activeSubWindowView.name === 'memoryLab'));
13884
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();
13885
15044
  };
13886
15045
 
13887
15046
  window.closeSubWin = function() {
@@ -13895,6 +15054,7 @@ window.closeSubWin = function() {
13895
15054
  requestAnimationFrame(function() {
13896
15055
  els['sub-win-body'].scrollTop = previous.scrollTop || 0;
13897
15056
  state.restoringSubWindow = false;
15057
+ focusSubWindowInitial();
13898
15058
  });
13899
15059
  return;
13900
15060
  }
@@ -13902,6 +15062,12 @@ window.closeSubWin = function() {
13902
15062
  state.activeSubWindowView = null;
13903
15063
  var win = els['sub-win'];
13904
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
+ });
13905
15071
  };
13906
15072
 
13907
15073
  function rerenderActiveSubWindowForLanguage() {
@@ -13950,19 +15116,19 @@ window.openSettings = function(tab) {
13950
15116
  var activeTab = tab || state.settingsActiveTab || 'general';
13951
15117
  var refreshingSettings = els['sub-win-overlay'].classList.contains('open') && state.activeSubWindowView && state.activeSubWindowView.name === 'settings';
13952
15118
  window._settingsTabCache = window._settingsTabCache || {};
13953
- var html = '<div class="settings-tabs">' +
13954
- '<button class="stab-btn" data-stab="general" onclick="window.settingsTab(\'general\')">' + esc(t('settings.general')) + '</button>' +
13955
- '<button class="stab-btn" data-stab="models" onclick="window.settingsTab(\'models\')">' + esc(t('settings.models')) + '</button>' +
13956
- '<button class="stab-btn" data-stab="tools" onclick="window.settingsTab(\'tools\')">' + esc(t('settings.tools')) + '</button>' +
13957
- '<button class="stab-btn" data-stab="archive" onclick="window.settingsTab(\'archive\')">' + esc(t('settings.archive')) + '</button>' +
13958
- '<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>' +
13959
15125
  '</div>' +
13960
- '<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>' +
13961
15127
  // Lazy: only render active tab on open; others render on first click
13962
- (activeTab === 'models' ? '<div class="stab-panel" id="stab-models">' + renderModelSettings() + '</div>' : '<div class="stab-panel" id="stab-models" data-lazy="1"></div>') +
13963
- (activeTab === 'tools' ? '<div class="stab-panel" id="stab-tools">' + renderToolSettings() + '</div>' : '<div class="stab-panel" id="stab-tools" data-lazy="1"></div>') +
13964
- (activeTab === 'archive' ? '<div class="stab-panel" id="stab-archive">' + renderArchiveSettings() + '</div>' : '<div class="stab-panel" id="stab-archive" data-lazy="1"></div>') +
13965
- (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>');
13966
15132
  state.activeSubWindowView = { name: 'settings', tab: activeTab };
13967
15133
  if (refreshingSettings) state.restoringSubWindow = true;
13968
15134
  window.openSubWin(t('settings.title'), html);
@@ -14329,7 +15495,12 @@ window.settingsTab = function(name) {
14329
15495
  state.settingsActiveTab = name || 'general';
14330
15496
  var btns = document.querySelectorAll('.stab-btn');
14331
15497
  for (var i = 0; i < btns.length; i++) {
14332
- 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
+ }
14333
15504
  }
14334
15505
  // Render lazy panels on first access for faster initial open
14335
15506
  var panelId = 'stab-' + name;
@@ -14355,6 +15526,7 @@ window.settingsTab = function(name) {
14355
15526
  var panels = document.querySelectorAll('.stab-panel');
14356
15527
  for (var j = 0; j < panels.length; j++) {
14357
15528
  panels[j].classList.toggle('active', panels[j].id === panelId);
15529
+ panels[j].hidden = panels[j].id !== panelId;
14358
15530
  }
14359
15531
  if (name === 'models' && panel && panel.getAttribute('data-lazy') !== '1') window.loadGlobalAgentPrompt();
14360
15532
  };
@@ -15154,36 +16326,29 @@ renderArchiveSettings = function() {
15154
16326
  };
15155
16327
 
15156
16328
  window.archiveCurrent = function() {
15157
- if (!api.archive) return;
15158
16329
  var currentId = activeConversationId();
15159
- if (currentId && runningConversationRecord(currentId)) {
15160
- showUiNotice(currentLang() === 'zh' ? '请先停止当前对话再归档。' : 'Stop the active conversation before archiving it.', 'error', 'archive-running-' + currentRuntimeKey(currentId));
15161
- return;
15162
- }
15163
- api.archive(currentConversationTarget(currentId)).then(function(receipt) {
15164
- if (!receipt || !receipt.ok) throw new Error((receipt && receipt.error) || 'Archive failed');
15165
- showUiNotice('[Archive] ' + t('archive.saved') + ': ' + receipt.fileName, 'success', 'archive-saved-' + receipt.conversationId);
15166
- return Promise.all([api.listArchives ? api.listArchives('workspace') : [], api.getState ? api.getState() : null]);
15167
- }).then(function(result) {
15168
- var items = result[0] || [];
15169
- var refreshed = result[1];
15170
- state.workspaceArchives = (items || []).map(function(a) {
15171
- return { id: a.id || a.name || String(a), name: a.name || String(a), firstLine: a.firstLine || '', date: a.date || '', scope: a.scope || 'workspace', workspace: a.workspace || state.currentWorkspace || '', restorable: !!a.restorable, conversationId: a.conversationId || '' };
15172
- });
15173
- state.allArchives = [];
15174
- if (refreshed && refreshed.conversations) {
15175
- state.activeBackendConversationId = refreshed.conversationId || state.activeBackendConversationId;
15176
- applyBackendConversations(refreshed.conversations, state.activeBackendConversationId, refreshed.workspaceId || runtimeWorkspaceId(''));
15177
- if (refreshed.chatMessages) renderChatMessages(refreshed.chatMessages);
15178
- renderConversations();
15179
- }
15180
- window.renderRightArchives();
15181
- var stab = document.getElementById('stab-archive');
15182
- if (stab) stab.innerHTML = renderArchiveSettings();
15183
- if (state.settingsActiveTab === 'archive') window.settingsTab('archive');
15184
- }).catch(function(err) {
15185
- addMsg('assistant', '[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', '');
15186
- });
16330
+ if (!currentId || !window.archiveConv) return;
16331
+ // Use the same optimistic path as the conversation-row action. This keeps
16332
+ // the active conversation immediately gone even while a running/queued
16333
+ // runtime is being hard-stopped by the main process.
16334
+ return window.archiveConv(currentId);
16335
+ };
16336
+
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();
15187
16352
  };
15188
16353
 
15189
16354
  window.loadArchive = function(idx, scope) {
@@ -15520,10 +16685,10 @@ function renderFlowItem(work, idx, expanded) {
15520
16685
  componentsHtml += '</div>';
15521
16686
  }
15522
16687
  return '<div class="flow-item">' +
15523
- '<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\')">' +
15524
16689
  '<span class="arrow">></span><span class="flow-item-name">' + esc(work.name || t('flow.untitled')) + '</span>' +
15525
16690
  '<span class="flow-item-type">' + (work.components ? work.components.length : 0) + ' ' + esc(t('flow.components')) + '</span>' +
15526
- '</div>' +
16691
+ '</button>' +
15527
16692
  '<div class="flow-item-children" style="margin-bottom:4px;">' +
15528
16693
  '<div style="display:flex;gap:4px;padding:4px 0;">' +
15529
16694
  '<button class="sec-btn" style="flex:1;font-size:10px;" onclick="window.addFlowComp(' + idx + ',\'dialog\')">+ ' + esc(t('flow.dialog')) + '</button>' +
@@ -15538,7 +16703,7 @@ function renderFlowItem(work, idx, expanded) {
15538
16703
  window.newFlowWork = function() {
15539
16704
  var html = '<div style="padding:10px;">' +
15540
16705
  '<label style="display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;">' + esc(t('flow.workflowName')) + '</label>' +
15541
- '<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()">' +
15542
16707
  '<div style="margin-top:12px;display:flex;gap:8px;">' +
15543
16708
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewFlowWork()">' + esc(t('common.create')) + '</button>' +
15544
16709
  '<button class="sec-btn" onclick="window.showFlowEditor()">' + esc(t('common.cancel')) + '</button>' +
@@ -16223,11 +17388,17 @@ window.loadFileTree = async function(options) {
16223
17388
  };
16224
17389
 
16225
17390
  window.submitSelectedFlow = function() {
17391
+ if (!state._flowsLoaded && window.ensureFlowsLoaded) {
17392
+ return window.ensureFlowsLoaded().then(function() { return window.submitSelectedFlow(); });
17393
+ }
16226
17394
  var selected = String(state.defaultFlow || (document.getElementById('flow-select') && document.getElementById('flow-select').value) || '');
16227
17395
  var workIdx = state.flowWorks.findIndex(function(work) { return String(work && work.name || '') === selected; });
16228
17396
  if (workIdx < 0) {
16229
- showUiNotice(currentLang() === 'zh' ? '请先选择一个 Flow。' : 'Select a Flow first.', 'error', 'flow-select-required');
16230
- return;
17397
+ var message = state.flowWorks.length
17398
+ ? (currentLang() === 'zh' ? '请先选择一个 Flow。' : 'Select a Flow first.')
17399
+ : t('flow.noAvailable');
17400
+ showUiNotice(message, 'error', 'flow-select-required');
17401
+ return { ok: false, error: message };
16231
17402
  }
16232
17403
  return window.runFlowWork(workIdx);
16233
17404
  };
@@ -16241,14 +17412,25 @@ function renderTreeNodes(nodes, parent, depth) {
16241
17412
  var n = nodes[i];
16242
17413
  var div = document.createElement('div');
16243
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
+ });
16244
17424
  div.style.paddingLeft = (8 + depth * 14) + 'px';
16245
17425
  var icon = n.type === 'directory' ? iconSvg('folder', 'Folder', 'small') : iconSvg('file', 'File', 'small');
16246
17426
  if (n.type === 'directory') {
17427
+ div.setAttribute('aria-expanded', 'false');
16247
17428
  div.innerHTML = '<span class="ft-toggle collapsed">' + iconSvg('chevron-right', t('fileTree.toggleFolder'), 'tiny') + '</span>' +
16248
17429
  '<span class="ft-icon">' + icon + '</span><span class="ft-name">' + esc(n.name) + '</span>';
16249
17430
  parent.appendChild(div);
16250
17431
  var childContainer = document.createElement('div');
16251
17432
  childContainer.className = 'ft-children';
17433
+ childContainer.setAttribute('role', 'group');
16252
17434
  childContainer.style.display = 'none';
16253
17435
  parent.appendChild(childContainer);
16254
17436
  div.onclick = function(node, toggle, children, childDepth) {
@@ -16257,6 +17439,7 @@ function renderTreeNodes(nodes, parent, depth) {
16257
17439
  var opening = children.style.display === 'none';
16258
17440
  children.style.display = opening ? 'block' : 'none';
16259
17441
  toggle.classList.toggle('collapsed', !opening);
17442
+ this.setAttribute('aria-expanded', opening ? 'true' : 'false');
16260
17443
  if (!opening || children.getAttribute('data-loaded') === 'true') return;
16261
17444
  children.setAttribute('data-loading', 'true');
16262
17445
  try {
@@ -16364,8 +17547,7 @@ window.renderNativeEditor = function() {
16364
17547
  }
16365
17548
  window.renderEditorGutter(code);
16366
17549
  if (els['editor-language']) els['editor-language'].textContent = language;
16367
- var before = code.slice(0, ta.selectionStart || 0).split('\n');
16368
- if (els['editor-position']) els['editor-position'].textContent = before.length + ':' + (before[before.length - 1].length + 1);
17550
+ window.renderEditorCaretStatus();
16369
17551
  if (els['editor-dirty']) els['editor-dirty'].textContent = code !== state.editorOriginal ? 'modified' : '';
16370
17552
  if (els['editor-vim-mode']) els['editor-vim-mode'].textContent = state.editorVimEnabled ? state.editorVimMode.toUpperCase() : 'INSERT';
16371
17553
  if (state.editorPreview && els['editor-md-preview']) els['editor-md-preview'].innerHTML = renderMessageContent(code);
@@ -16407,6 +17589,7 @@ window.editorSetValue = function(value, preserveUndo) {
16407
17589
  if (!ta) return;
16408
17590
  if (!preserveUndo && ta.value !== value) { state.editorUndo.push(ta.value); if (state.editorUndo.length > 100) state.editorUndo.shift(); state.editorRedo = []; }
16409
17591
  ta.value = value;
17592
+ state.editorCompletionCache = [];
16410
17593
  window.renderNativeEditor();
16411
17594
  };
16412
17595
 
@@ -16435,23 +17618,100 @@ window.editorReplaceSelection = function(text) {
16435
17618
  window.renderNativeEditor();
16436
17619
  };
16437
17620
 
16438
- 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;
16439
17671
  var ta = els['editor-textarea']; if (!ta || !state.editorPath || !api.editorComplete) return;
16440
17672
  var requestId = ++state.editorCompletionRequest;
16441
17673
  var pos = ta.selectionStart;
16442
17674
  var anchor = { path: state.editorPath, value: ta.value, start: pos, end: ta.selectionEnd };
16443
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
+ }
16444
17686
  if (els['editor-completion']) { els['editor-completion'].textContent = 'Predicting...'; els['editor-completion'].classList.add('open'); }
16445
- var beforeStart = Math.max(0, pos - 6000);
16446
- var afterEnd = Math.min(ta.value.length, pos + 1600);
16447
- 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
+ }
16448
17699
  if (requestId !== state.editorCompletionRequest) return;
16449
- if (!window.editorAnchorMatches(anchor)) { window.dismissEditorCompletion(); window.scheduleEditorCompletion(); return; }
16450
- 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);
16451
17709
  window.renderEditorGhostText();
16452
17710
  if (els['editor-completion']) {
16453
- els['editor-completion'].textContent = state.editorCompletionText ? '' : (result.error || 'No completion');
16454
- 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');
16455
17715
  }
16456
17716
  };
16457
17717
 
@@ -16480,7 +17740,7 @@ window.scheduleEditorCompletion = function() {
16480
17740
  state.editorCompletionTimer = setTimeout(function() {
16481
17741
  state.editorCompletionTimer = null;
16482
17742
  window.requestEditorCompletion();
16483
- }, 180);
17743
+ }, 300);
16484
17744
  };
16485
17745
 
16486
17746
  window.editorCaretSignature = function() {
@@ -16499,7 +17759,8 @@ window.handleEditorCaretChange = function() {
16499
17759
  var signature = window.editorCaretSignature();
16500
17760
  if (signature === state.editorCaretSignature) return;
16501
17761
  state.editorCaretSignature = signature;
16502
- window.renderNativeEditor();
17762
+ window.renderEditorCaretStatus();
17763
+ window.renderEditorGhostText();
16503
17764
  window.scheduleEditorCompletion();
16504
17765
  };
16505
17766
 
@@ -16507,7 +17768,24 @@ window.acceptEditorCompletion = function() {
16507
17768
  if (state.editorCompletionText && window.editorCompletionAnchorIsCurrent()) window.editorReplaceSelection(state.editorCompletionText);
16508
17769
  window.dismissEditorCompletion();
16509
17770
  };
16510
- 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
+ };
16511
17789
 
16512
17790
  window.requestEditorAssist = async function() {
16513
17791
  var ta = els['editor-textarea']; if (!ta || !state.editorPath || !api.editorAssist) return;
@@ -17113,6 +18391,42 @@ function rememberBrowserUrl(url, force, target) {
17113
18391
  }
17114
18392
  }
17115
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
+
17116
18430
  function waitForBrowserCreationFloor() {
17117
18431
  var remaining = NEWMARK_BROWSER_MIN_CREATE_DELAY_MS - (Date.now() - newmarkRendererStartedAt);
17118
18432
  if (remaining <= 0) return Promise.resolve();
@@ -17332,7 +18646,14 @@ window.ensureBrowserPanel = function(options) {
17332
18646
  var targetKey = browserTargetKey(target);
17333
18647
  cancelBrowserGuestIdleDestroy();
17334
18648
  if (!browserGuestCreatePromises[targetKey]) {
17335
- browserGuestCreatePromises[targetKey] = waitForBrowserCreationFloor().then(function() {
18649
+ // Keep background Browser-Use demand off the startup hot path, but never
18650
+ // make a deliberate visible-tab click wait behind that memory guard. A
18651
+ // user explicitly opening Browser is an interaction deadline, not a
18652
+ // background prewarm request.
18653
+ var creationWait = options.activate === true
18654
+ ? Promise.resolve()
18655
+ : waitForBrowserCreationFloor();
18656
+ browserGuestCreatePromises[targetKey] = creationWait.then(function() {
17336
18657
  return initializeBrowserGuest(createBrowserGuestElement(target), target);
17337
18658
  }).catch(function(error) {
17338
18659
  delete browserGuestCreatePromises[targetKey];
@@ -17438,7 +18759,8 @@ function applyBackendConversations(items, activeId, workspaceId) {
17438
18759
  updatedAt: item.updatedAt || '',
17439
18760
  pinned: !!item.pinned,
17440
18761
  pinnedAt: item.pinnedAt || '',
17441
- order: Number(item.order || 0)
18762
+ order: Number(item.order || 0),
18763
+ branchCommunication: !!item.branchCommunication
17442
18764
  });
17443
18765
  }
17444
18766
  // A stale list response can arrive between creating a conversation locally
@@ -17565,16 +18887,22 @@ function renderConversations() {
17565
18887
  }
17566
18888
  var div = document.createElement('div');
17567
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');
17568
18893
  div.setAttribute('draggable', 'true');
17569
18894
  div.setAttribute('data-conversation-id', String(conv.id || ''));
17570
18895
  if (runtimeState && ['running', 'stopping', 'force_restarting'].indexOf(String(runtimeState.status || '')) >= 0) div.classList.add('marquee-border');
17571
18896
  var runtimeBadge = runtimeState && runtimeState.status && runtimeState.status !== 'idle'
17572
18897
  ? '<span class="conv-runtime-badge ' + escAttr(String(runtimeState.status)) + '">' + esc(String(runtimeState.status)) + '</span>' : '';
17573
- 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 +
17574
18901
  '<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
17575
- '<button class="conv-archive-btn" ' + (runtimeState && ['running', 'stopping', 'force_restarting'].indexOf(String(runtimeState.status || '')) >= 0 ? 'disabled ' : '') + '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>' +
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>' +
17576
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>';
17577
18904
  div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
18905
+ div.addEventListener('keydown', window.handleConversationKey);
17578
18906
  div.addEventListener('dragstart', function(event) {
17579
18907
  this.classList.add('dragging');
17580
18908
  if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', this.getAttribute('data-conversation-id') || ''); }
@@ -17604,6 +18932,30 @@ function renderConversations() {
17604
18932
  updateWorkspaceGate();
17605
18933
  }
17606
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
+
17607
18959
  window.newConversationOld = function() {
17608
18960
  var id = 'conv-' + Date.now();
17609
18961
  var summary = t('left.newChat') + ' ' + (state.conversations.length + 1);
@@ -17637,7 +18989,7 @@ window.archiveConvOld = function(idx) {
17637
18989
  }
17638
18990
  };
17639
18991
 
17640
- window.newConversation = function(workspaceReference) {
18992
+ window.newConversation = function(workspaceReference, branchCommunication) {
17641
18993
  if (workspaceReference && workspaceReference !== state.currentWorkspaceId) {
17642
18994
  window.selectWorkspace(workspaceReference);
17643
18995
  }
@@ -17667,6 +19019,13 @@ window.newConversation = function(workspaceReference) {
17667
19019
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
17668
19020
  state.activeBackendConversationId = String((s && s.conversationId) || id);
17669
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
+ }
17670
19029
  }).then(function() {
17671
19030
  if (createdWorkspaceKey !== currentWorkspaceKey() || id !== String(activeConversationId() || 'default')) return;
17672
19031
  state.foregroundConversationHoldId = '';
@@ -17693,6 +19052,7 @@ window.showNewConversationPage = function() {
17693
19052
  var html = '<div style="display:flex;flex-direction:column;gap:12px;padding:4px 2px;">' +
17694
19053
  '<div style="font-size:12px;color:var(--text-dim);line-height:1.5;">' + esc(t('workspace.newConversationDesc')) + '</div>' +
17695
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>' +
17696
19056
  '<div style="display:flex;gap:8px;">' +
17697
19057
  '<button class="sec-btn primary" style="flex:1;" onclick="window.doNewConversationFromPage()">' + esc(t('workspace.createConversation')) + '</button>' +
17698
19058
  '<button class="sec-btn" style="flex:1;" onclick="window.showNewWorkspaceDialog()">' + esc(t('workspace.createAction')) + '</button>' +
@@ -17709,11 +19069,13 @@ window.doNewConversationFromPage = function() {
17709
19069
  window.showNewWorkspaceDialog();
17710
19070
  return;
17711
19071
  }
19072
+ var branchComm = document.getElementById('new-conv-branch-comm');
19073
+ var branchCommunication = !!(branchComm && branchComm.checked);
17712
19074
  window.switchToWorkspace(identity).then(function(ws) {
17713
19075
  state.currentWorkspace = (ws && ws.name) || state.currentWorkspace;
17714
19076
  state.currentWorkspaceId = workspaceIdentity(ws) || state.currentWorkspaceId || identity;
17715
19077
  window.closeSubWin();
17716
- window.newConversation(state.currentWorkspaceId);
19078
+ window.newConversation(state.currentWorkspaceId, branchCommunication);
17717
19079
  }).catch(function(err) {
17718
19080
  showUiNotice(t('workspace.selectAction') + ': ' + err.message, 'error', 'workspace-new-conversation-' + identity);
17719
19081
  });
@@ -17774,8 +19136,13 @@ function refreshConversationArchivesAfterBatch() {
17774
19136
  state.workspaceArchives = (items || []).map(function(a) {
17775
19137
  return { id: a.id || a.name || String(a), name: a.name || String(a), firstLine: a.firstLine || '', date: a.date || '', scope: a.scope || 'workspace', workspace: a.workspace || state.currentWorkspace || '', restorable: !!a.restorable, conversationId: a.conversationId || '' };
17776
19138
  });
17777
- state.allArchives = [];
19139
+ var archiveSettingsOpen = typeof document !== 'undefined'
19140
+ && state.settingsActiveTab === 'archive'
19141
+ && !!document.getElementById('stab-archive');
19142
+ if (!archiveSettingsOpen) state.allArchives = [];
17778
19143
  window.renderRightArchives();
19144
+ var stab = typeof document !== 'undefined' ? document.getElementById('stab-archive') : null;
19145
+ if (stab && archiveSettingsOpen) stab.innerHTML = renderArchiveSettings();
17779
19146
  return items || [];
17780
19147
  });
17781
19148
  }
@@ -17810,10 +19177,6 @@ window.archiveConv = function(conversationId) {
17810
19177
  var targetRuntime = currentConversationTarget(targetId);
17811
19178
  var workspaceKey = currentWorkspaceKey();
17812
19179
  var pendingKey = workspaceKey + '::' + targetId;
17813
- if (runningConversationRecord(targetId)) {
17814
- showUiNotice(currentLang() === 'zh' ? '运行中的对话不能归档。' : 'A running conversation cannot be archived.', 'error', 'archive-running-' + currentRuntimeKey(targetId));
17815
- return;
17816
- }
17817
19180
  if (state.conversationArchivePending[pendingKey]) return;
17818
19181
  var priorActiveId = String(((convs.find(function(item) { return item && item.active; }) || {}).id) || '');
17819
19182
  var rollbackOrder = convs.map(function(item) { return String(item && item.id || 'default'); });
@@ -17845,6 +19208,25 @@ window.archiveConv = function(conversationId) {
17845
19208
  state.activeBackendConversationId = nextActiveId;
17846
19209
  if (els['chat-area']) els['chat-area'].innerHTML = '';
17847
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
+ }
17848
19230
  setConversationRuntimeState(targetRuntime, 'idle', '');
17849
19231
  setWorking(!!runningConversationRecord(activeConversationId()));
17850
19232
  renderConversations();
@@ -17853,39 +19235,37 @@ window.archiveConv = function(conversationId) {
17853
19235
  archivePromise.then(function(receipt) {
17854
19236
  if (!receipt || receipt.ok !== true) throw new Error((receipt && receipt.error) || 'Archive failed');
17855
19237
  delete state.conversationArchivePending[pendingKey];
19238
+ var optimisticArchive = {
19239
+ id: receipt.fileName,
19240
+ name: receipt.fileName,
19241
+ firstLine: target.summary || '',
19242
+ date: new Date().toISOString(),
19243
+ scope: 'workspace',
19244
+ workspace: state.currentWorkspace || '',
19245
+ restorable: true,
19246
+ conversationId: receipt.conversationId || targetId,
19247
+ };
19248
+ state.workspaceArchives = (state.workspaceArchives || []).filter(function(item) {
19249
+ return String(item && item.id || item && item.name || '') !== String(optimisticArchive.id);
19250
+ });
19251
+ state.workspaceArchives.unshift(optimisticArchive);
19252
+ if (Array.isArray(state.allArchives) && state.allArchives.length) {
19253
+ state.allArchives = state.allArchives.filter(function(item) {
19254
+ return String(item && item.id || item && item.name || '') !== String(optimisticArchive.id);
19255
+ });
19256
+ state.allArchives.unshift(optimisticArchive);
19257
+ }
19258
+ window.renderRightArchives();
19259
+ var archiveSettings = typeof document !== 'undefined' ? document.getElementById('stab-archive') : null;
19260
+ if (archiveSettings && state.settingsActiveTab === 'archive') archiveSettings.innerHTML = renderArchiveSettings();
17856
19261
  showUiNotice('[Archive] ' + t('archive.saved') + ': ' + receipt.fileName, 'success', 'archive-saved-' + targetId);
17857
19262
  scheduleConversationArchiveRefresh(workspaceKey);
17858
19263
  }).catch(function(err) {
17859
- var pending = state.conversationArchivePending[pendingKey];
17860
19264
  delete state.conversationArchivePending[pendingKey];
17861
- var workspaceConversations = state.workspaceConversations[workspaceKey] || [];
17862
- if (pending && !workspaceConversations.some(function(item) { return String(item && item.id || '') === targetId; })) {
17863
- var restoreAt = -1;
17864
- var orderIds = Array.isArray(pending.orderIds) ? pending.orderIds : [];
17865
- var targetOrderIndex = orderIds.indexOf(targetId);
17866
- for (var beforeIndex = targetOrderIndex - 1; beforeIndex >= 0 && restoreAt < 0; beforeIndex--) {
17867
- var precedingIndex = workspaceConversations.findIndex(function(item) { return String(item && item.id || '') === orderIds[beforeIndex]; });
17868
- if (precedingIndex >= 0) restoreAt = precedingIndex + 1;
17869
- }
17870
- for (var afterIndex = targetOrderIndex + 1; afterIndex < orderIds.length && restoreAt < 0; afterIndex++) {
17871
- var followingIndex = workspaceConversations.findIndex(function(item) { return String(item && item.id || '') === orderIds[afterIndex]; });
17872
- if (followingIndex >= 0) restoreAt = followingIndex;
17873
- }
17874
- if (restoreAt < 0) restoreAt = Math.min(Number(pending.index || 0), workspaceConversations.length);
17875
- workspaceConversations.splice(restoreAt, 0, pending.target);
17876
- if (pending.wasActive) {
17877
- for (var j = 0; j < workspaceConversations.length; j++) workspaceConversations[j].active = String(workspaceConversations[j].id || '') === targetId;
17878
- }
17879
- }
17880
- if (workspaceKey === currentWorkspaceKey()) {
17881
- state.conversations = workspaceConversations;
17882
- state.activeConversation = Math.max(0, workspaceConversations.findIndex(function(item) { return item && item.active; }));
17883
- if (pending && pending.wasActive) {
17884
- state.activeBackendConversationId = targetId;
17885
- if (els['chat-area']) els['chat-area'].innerHTML = pending.chatHtml || '';
17886
- }
17887
- renderConversations();
17888
- }
19265
+ // Keep the optimistic removal authoritative for this renderer session.
19266
+ // A failed IPC receipt is surfaced, but never resurrects the row under
19267
+ // the user's pointer; the next explicit workspace refresh is the only
19268
+ // path allowed to reconcile a failed destructive operation.
17889
19269
  showUiNotice('[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', 'archive-failed-' + targetId);
17890
19270
  });
17891
19271
  }
@@ -18693,37 +20073,117 @@ window.reindexMemoryLab = function() {
18693
20073
  });
18694
20074
  };
18695
20075
 
18696
- // === Plugin List Placeholder ===
18697
- window.showPluginList = function() {
18698
- 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
+ }
18699
20127
  };
18700
20128
 
18701
- // === Plugin / Skills Market ===
18702
- window.showPluginList = function(tab) {
18703
- var activeTab = tab || state.pluginActiveTab || 'mcp';
18704
- var refreshingPlugins = els['sub-win-overlay'].classList.contains('open') && state.activeSubWindowView && state.activeSubWindowView.name === 'plugins';
18705
- state.activeSubWindowView = { name: 'plugins', tab: activeTab };
18706
- state.pluginActiveTab = activeTab;
18707
- var html = '<div class="settings-tabs">' +
18708
- '<button class="stab-btn' + (state.pluginActiveTab === 'mcp' ? ' active' : '') + '" onclick="window.showPluginList(\'mcp\')">' + esc(t('plugins.mcp')) + '</button>' +
18709
- '<button class="stab-btn' + (state.pluginActiveTab === 'installed' ? ' active' : '') + '" onclick="window.showPluginList(\'installed\')">' + esc(t('plugins.management')) + '</button>' +
18710
- '<button class="stab-btn' + (state.pluginActiveTab === 'market' ? ' active' : '') + '" onclick="window.showPluginList(\'market\')">' + esc(t('plugins.market')) + '</button>' +
18711
- '<button class="stab-btn' + (state.pluginActiveTab === 'github' ? ' active' : '') + '" onclick="window.showPluginList(\'github\')">' + esc(t('plugins.github')) + '</button>' +
18712
- '</div><div id="plugin-panel" style="margin-top:10px;">' + esc(t('common.loading')) + '</div>';
18713
- if (refreshingPlugins) state.restoringSubWindow = true;
18714
- window.openSubWin(t('plugins.title'), html);
18715
- state.restoringSubWindow = false;
18716
- if (state.pluginActiveTab === 'mcp') window.renderMcpManager();
18717
- else if (state.pluginActiveTab === 'market') window.renderSkillsMarket();
18718
- 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();
18719
20177
  else window.renderInstalledSkills();
18720
20178
  };
18721
20179
 
18722
20180
  window.renderInstalledSkills = function() {
18723
20181
  var panel = document.getElementById('plugin-panel');
18724
- if (!panel) return;
20182
+ if (!panel || state.pluginActiveTab !== 'installed') return;
20183
+ var generation = ++state.pluginPanelGeneration;
18725
20184
  var render = function(items) {
18726
20185
  state.skills = items || [];
20186
+ if (!pluginRequestActive('installed', generation)) return;
18727
20187
  if (!items || !items.length) {
18728
20188
  panel.innerHTML = '<div style="font-size:12px;color:var(--text-dim);padding:18px;text-align:center;">' + esc(t('plugins.noInstalled')) + '</div>';
18729
20189
  return;
@@ -18764,14 +20224,15 @@ window.refreshSkillsRuntime = function(next) {
18764
20224
 
18765
20225
  window.renderSkillsMarket = function() {
18766
20226
  var panel = document.getElementById('plugin-panel');
18767
- if (!panel) return;
20227
+ if (!panel || state.pluginActiveTab !== 'market') return;
20228
+ var generation = ++state.pluginPanelGeneration;
18768
20229
  panel.innerHTML = '<div class="provider-card marquee-border">' + esc(t('plugins.discovering')) + '</div>';
18769
20230
  state._skillMarketAll = [];
18770
20231
  state._skillMarketSources = [];
18771
20232
  var done = 0;
18772
20233
  var finish = function() {
18773
20234
  done++;
18774
- if (done >= 2) window.renderSkillsMarketList();
20235
+ if (done >= 2 && pluginRequestActive('market', generation)) window.renderSkillsMarketList();
18775
20236
  };
18776
20237
  if (api.marketSkillSources) api.marketSkillSources().then(function(sources) {
18777
20238
  state._skillMarketSources = sources || [];
@@ -18785,88 +20246,466 @@ window.renderSkillsMarket = function() {
18785
20246
  else finish();
18786
20247
  };
18787
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
+
18788
20441
  window.renderMcpManager = function() {
18789
20442
  var panel = document.getElementById('plugin-panel');
18790
- if (!panel) return;
18791
- 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');
18792
20446
  if (!api.listMcpServers) {
18793
- panel.innerHTML = '<div class="settings-empty">' + esc(t('plugins.ghUnavailable')) + '</div>';
20447
+ panel.innerHTML = pluginPanelState(t('plugins.mcpUnavailable'), 'error');
18794
20448
  return;
18795
20449
  }
18796
- api.listMcpServers().then(function(result) {
20450
+ Promise.resolve(api.listMcpServers()).then(function(result) {
20451
+ if (!pluginRequestActive('mcp', generation)) return;
18797
20452
  state.mcpServers = Array.isArray(result && result.servers) ? result.servers : [];
18798
20453
  state.mcpDiscovered = Array.isArray(result && result.discovered) ? result.discovered : [];
18799
20454
  window.renderMcpManagerContent();
18800
20455
  }).catch(function(error) {
18801
- 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()');
18802
20459
  });
18803
20460
  };
18804
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
+
18805
20497
  window.renderMcpManagerContent = function() {
18806
20498
  var panel = document.getElementById('plugin-panel');
18807
- if (!panel) return;
18808
- var servers = state.mcpServers || [];
18809
- var discovered = state.mcpDiscovered || [];
18810
- var editing = servers.find(function(server) { return server.id === state.mcpEditingId; }) || {};
18811
- var transport = editing.transport === 'http' ? 'http' : 'stdio';
18812
- var html = '<div style="font-size:11px;color:var(--text-dim);margin-bottom:10px;">' + esc(t('plugins.mcpHelp')) + '</div>' +
18813
- '<div class="provider-card" style="margin-bottom:12px;">' +
18814
- '<div style="display:grid;grid-template-columns:1fr 120px;gap:8px;margin-bottom:8px;">' +
18815
- '<input id="mcp-name" value="' + escAttr(editing.name || '') + '" placeholder="' + escAttr(t('plugins.mcpName')) + '" class="github-repo-select">' +
18816
- '<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>' +
18817
- '<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;">' +
18818
- '<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>' +
18819
- '<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>' +
18820
- '<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>' +
18821
- '<h4 style="margin:0 0 8px;">' + esc(t('plugins.mcpStored')) + '</h4>';
18822
- 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>';
18823
20514
  servers.forEach(function(server) {
18824
- var endpoint = server.transport === 'http' ? server.url : [server.command].concat(server.args || []).join(' ');
18825
- 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>' +
18826
- '<button class="sec-btn" onclick="window.editMcpServer(\'' + escAttr(server.id) + '\')">' + esc(t('common.edit')) + '</button>' +
18827
- '<button class="sec-btn" onclick="window.toggleMcpServer(\'' + escAttr(server.id) + '\',' + (!server.enabled) + ')">' + esc(server.enabled ? t('common.disable') : t('common.enable')) + '</button>' +
18828
- '<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>';
18829
20523
  });
18830
- html += '<h4 style="margin:14px 0 8px;">' + esc(t('plugins.mcpDiscovered')) + '</h4>';
18831
- 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>';
18832
20527
  discovered.forEach(function(server) {
18833
- 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>';
18834
20532
  });
20533
+ html += '</section>';
18835
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;
18836
20572
  };
18837
20573
 
18838
- window.updateMcpTransportForm = function() {
18839
- var transport = document.getElementById('mcp-transport').value;
18840
- var args = document.getElementById('mcp-args');
18841
- var env = document.getElementById('mcp-env');
18842
- if (args) { args.value = ''; args.placeholder = transport === 'http' ? t('plugins.mcpHeaders') : t('plugins.mcpArgs'); }
18843
- if (env) env.style.display = transport === 'stdio' ? 'block' : 'none';
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();
18844
20580
  };
18845
- window.resetMcpForm = function() { state.mcpEditingId = ''; window.renderMcpManagerContent(); };
18846
- window.editMcpServer = function(id) { state.mcpEditingId = id; window.renderMcpManagerContent(); };
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 });
20599
+ };
20600
+
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
+ }
20610
+ };
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
+
18847
20626
  window.saveMcpServer = function() {
18848
- if (!api.upsertMcpServer) return;
18849
- var name = document.getElementById('mcp-name').value.trim();
18850
- var transport = document.getElementById('mcp-transport').value;
18851
- var endpoint = document.getElementById('mcp-command').value.trim();
18852
- var argsText = document.getElementById('mcp-args').value.trim();
18853
- var envText = document.getElementById('mcp-env').value.trim();
18854
- 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 };
18855
20634
  try {
18856
- if (transport === 'http') { input.url = endpoint; if (argsText) input.headers = JSON.parse(argsText); }
18857
- 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
+ }
18858
20652
  } catch (error) {
18859
- showUiNotice(error.message || String(error), 'error', 'mcp-json');
20653
+ showUiNotice(error && error.message ? error.message : String(error), 'error', 'mcp-save');
18860
20654
  return;
18861
20655
  }
18862
- api.upsertMcpServer(input).then(function(result) {
18863
- 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;
18864
20662
  state.mcpEditingId = '';
18865
- window.renderMcpManager();
18866
- }).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
+ });
18867
20708
  };
18868
- window.toggleMcpServer = function(id, enabled) { if (api.setMcpServerEnabled) api.setMcpServerEnabled(id, enabled).then(window.renderMcpManager); };
18869
- window.removeMcpServerFromUi = function(id) { if (api.removeMcpServer) api.removeMcpServer(id).then(window.renderMcpManager); };
18870
20709
 
18871
20710
  window.filteredSkillMarket = function() {
18872
20711
  var market = state._skillMarketAll || [];
@@ -18887,7 +20726,7 @@ window.filteredSkillMarket = function() {
18887
20726
 
18888
20727
  window.renderSkillsMarketList = function() {
18889
20728
  var panel = document.getElementById('plugin-panel');
18890
- if (!panel) return;
20729
+ if (!panel || state.pluginActiveTab !== 'market' || !state.activeSubWindowView || state.activeSubWindowView.name !== 'plugins') return;
18891
20730
  var market = window.filteredSkillMarket();
18892
20731
  var total = (state._skillMarketAll || []).length;
18893
20732
  var query = state.skillMarketQuery || '';
@@ -19156,13 +20995,14 @@ window.renderLeftWsList = function() {
19156
20995
  var container = document.getElementById('left-ws-list');
19157
20996
  if (!container) return;
19158
20997
  var workspaces = state.workspaces || [];
20998
+ var hasActiveWorkspace = workspaces.some(function(item) { return workspaceIdentity(item) === String(state.currentWorkspaceId || ''); });
19159
20999
  var html = '';
19160
21000
  for (var i = 0; i < workspaces.length; i++) {
19161
21001
  var ws = workspaces[i];
19162
21002
  var identity = workspaceIdentity(ws);
19163
21003
  var active = identity === String(state.currentWorkspaceId || '');
19164
21004
  var runtimeStatus = workspaceRuntimeStatus(ws);
19165
- 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)">' +
19166
21006
  '<span class="ws-icon">' + esc((ws.name || '?')[0].toUpperCase()) + '</span>' +
19167
21007
  '<span class="ws-label">' + esc(ws.name || t('workspace.untitled')) + '</span>' +
19168
21008
  (runtimeStatus ? '<span class="ws-runtime-dot ' + escAttr(runtimeStatus) + '" title="' + escAttr(runtimeStatus) + '"></span>' : '') +
@@ -19175,6 +21015,24 @@ window.renderLeftWsList = function() {
19175
21015
  appendDomNodesInBatches(container, Array.from(holder.children), 'workspace-list', 24);
19176
21016
  };
19177
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
+
19178
21036
  window.toggleWorkspacePinned = function(identity) {
19179
21037
  if (!identity || !api.setWorkspacePinned) return;
19180
21038
  var nextPinned = false;
@@ -19279,7 +21137,7 @@ window.showNewWorkspaceDialog = function() {
19279
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>' +
19280
21138
  '</div>' +
19281
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>' +
19282
- '<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()">' +
19283
21141
  '<div id="ws-ext-path" style="display:none;margin-top:8px;">' +
19284
21142
  '<label style="display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;">' + esc(t('workspace.folderPath')) + '</label>' +
19285
21143
  '<div style="display:flex;gap:6px;">' +
@@ -19535,7 +21393,7 @@ window.openWorkspaceManager = async function() {
19535
21393
  var ws = workspaces[i];
19536
21394
  var identity = workspaceIdentity(ws);
19537
21395
  var isActive = identity === String(state.currentWorkspaceId || '');
19538
- 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)">' +
19539
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>' +
19540
21398
  '<span style="flex:1;font-size:12px;color:var(--text);">' + esc(ws.name || t('workspace.untitled')) + '</span>' +
19541
21399
  (isActive ? '<span style="font-size:10px;color:var(--accent2);">' + esc(t('workspace.current')) + '</span>' : '') +
@@ -19660,18 +21518,26 @@ window.saveWsSetting = function(key, value) {
19660
21518
  window.renderFlowSelector = function() {
19661
21519
  var flowSel = document.getElementById('flow-select');
19662
21520
  if (!flowSel) return;
21521
+ var flows = Array.isArray(state.flowWorks) ? state.flowWorks : [];
19663
21522
  var defaultFlow = state.defaultFlow || '';
21523
+ if (flows.length === 0) {
21524
+ state.defaultFlow = '';
21525
+ flowSel.disabled = true;
21526
+ flowSel.innerHTML = '<option value="">' + esc(t('flow.noAvailable')) + '</option>';
21527
+ if (window.syncNewmarkSelect) window.syncNewmarkSelect(flowSel);
21528
+ return;
21529
+ }
21530
+ flowSel.disabled = false;
19664
21531
  var html = '';
19665
- for (var fi = 0; fi < state.flowWorks.length; fi++) {
19666
- var selected = state.flowWorks[fi].name === defaultFlow ? ' selected' : '';
19667
- html += '<option value="' + esc(state.flowWorks[fi].name) + '"' + selected + '>' + esc(state.flowWorks[fi].name) + '</option>';
21532
+ for (var fi = 0; fi < flows.length; fi++) {
21533
+ var selected = flows[fi].name === defaultFlow ? ' selected' : '';
21534
+ html += '<option value="' + esc(flows[fi].name) + '"' + selected + '>' + esc(flows[fi].name) + '</option>';
19668
21535
  }
19669
21536
  flowSel.innerHTML = html;
19670
- if (state.flowWorks.length > 0) {
19671
- if (defaultFlow) flowSel.value = defaultFlow;
19672
- state.defaultFlow = flowSel.value;
19673
- }
21537
+ if (defaultFlow) flowSel.value = defaultFlow;
21538
+ state.defaultFlow = flowSel.value;
19674
21539
  flowSel.onchange = function() { state.defaultFlow = this.value; api.saveConfig({defaultFlow: state.defaultFlow}); };
21540
+ if (window.syncNewmarkSelect) window.syncNewmarkSelect(flowSel);
19675
21541
  };
19676
21542
 
19677
21543
  window.loadFlows = function(options) {
@@ -19764,6 +21630,549 @@ function stopMarqueeJS() {
19764
21630
  if (marqueeRAF) { cancelAnimationFrame(marqueeRAF); marqueeRAF = null; }
19765
21631
  }
19766
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
+
19767
22176
  function schedulePostStartupUiRendering() {
19768
22177
  if (state._postStartupUiRendering && state._postStartupUiRendering.cancel) state._postStartupUiRendering.cancel();
19769
22178
  var tasks = [
@@ -19812,8 +22221,33 @@ function schedulePostStartupUiRendering() {
19812
22221
  (function initResize() {
19813
22222
  var handles = document.querySelectorAll('.resize-handle');
19814
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
+ });
19815
22248
  handles[i].addEventListener('mousedown', function(e) {
19816
22249
  e.preventDefault();
22250
+ var handle = this;
19817
22251
  var target = this.getAttribute('data-target');
19818
22252
  var side = this.getAttribute('data-side');
19819
22253
  var el = target === 'left' ? els.left : (target === 'right' ? els.right : null);
@@ -19833,10 +22267,12 @@ function schedulePostStartupUiRendering() {
19833
22267
  var leftSize = Math.max(220, Math.min(460, newSize));
19834
22268
  setLeftWidthPx(leftSize);
19835
22269
  state.leftWidth = leftSize;
22270
+ handle.setAttribute('aria-valuenow', String(Math.round(leftSize)));
19836
22271
  } else if (side === 'left') {
19837
22272
  var newSize2 = startSize - dx;
19838
22273
  var rightSize = Math.max(340, Math.min(680, newSize2));
19839
22274
  window.setRightWidthPx(rightSize);
22275
+ handle.setAttribute('aria-valuenow', String(Math.round(rightSize)));
19840
22276
  } else if (side === 'top') {
19841
22277
  var newSize3 = startSize - dy;
19842
22278
  el.style.height = Math.max(0, Math.min(400, newSize3)) + 'px';
@@ -19935,6 +22371,10 @@ function schedulePostStartupUiRendering() {
19935
22371
  var startupHydrationError = null;
19936
22372
  cacheEls();
19937
22373
  setupAgentWorkEvents();
22374
+ if (api.onEditorCompletionDelta && !state._editorCompletionDeltaReady) {
22375
+ state._editorCompletionDeltaReady = true;
22376
+ api.onEditorCompletionDelta(function(payload) { window.applyEditorCompletionDelta(payload); });
22377
+ }
19938
22378
  if (api.onBrowserEnsureGuest) {
19939
22379
  api.onBrowserEnsureGuest(function(target) {
19940
22380
  cancelBrowserGuestIdleDestroy();
@@ -20047,6 +22487,7 @@ function schedulePostStartupUiRendering() {
20047
22487
  startupHydrationError = e;
20048
22488
  }
20049
22489
  applyUiAppearance();
22490
+ setupGuiKeyboard();
20050
22491
  window.renderContextWindow();
20051
22492
  if (api.onAutomationUpdated) {
20052
22493
  api.onAutomationUpdated(function() {
@@ -20071,19 +22512,69 @@ function schedulePostStartupUiRendering() {
20071
22512
  updateWorkspaceGate();
20072
22513
 
20073
22514
  // === Event Listeners ===
22515
+ function escapeBelongsToFocusedControl(event) {
22516
+ var target = event && event.target;
22517
+ if (!target || target === document || target === document.body || target === document.documentElement) return false;
22518
+ if (els.prompt && (target === els.prompt || (els.prompt.contains && els.prompt.contains(target)))) return false;
22519
+ if (target.closest && target.closest('.modal.active, .sub-win.open, .newmark-select-shell.open')) return true;
22520
+ return !!(target.matches && target.matches('input, textarea, select, [contenteditable="true"]'));
22521
+ }
22522
+ if (api.onWorkspaceChanged) {
22523
+ var workspaceRefreshTimer = null;
22524
+ api.onWorkspaceChanged(function() {
22525
+ if (workspaceRefreshTimer) clearTimeout(workspaceRefreshTimer);
22526
+ workspaceRefreshTimer = setTimeout(function() {
22527
+ workspaceRefreshTimer = null;
22528
+ if (window.refreshWorkspaceState) window.refreshWorkspaceState().catch(function(){});
22529
+ if (document.getElementById('right-archive-list')) window.refreshRightArchives();
22530
+ }, 120);
22531
+ });
22532
+ }
22533
+
22534
+ function stopRunningFromEscape(event) {
22535
+ if (!event || event.key !== 'Escape' || event.defaultPrevented || escapeBelongsToFocusedControl(event)) return false;
22536
+ if (currentFlowRunning() && flowTakeoverMatchesCurrent()) {
22537
+ event.preventDefault();
22538
+ window.stopFlowRun();
22539
+ return true;
22540
+ }
22541
+ if (isCurrentConversationRunning()) {
22542
+ event.preventDefault();
22543
+ window.stopCurrentConversation();
22544
+ return true;
22545
+ }
22546
+ return false;
22547
+ }
22548
+
22549
+ // The prompt handler below covers the normal focused-input path. Keep a
22550
+ // document-level fallback so a physical Escape still stops a running target
22551
+ // after focus moved to the chat, title bar, button, or another non-editor
22552
+ // surface. Modal/editor/select Escape behavior remains owned by that UI.
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
+ }
22561
+ if (stopRunningFromEscape(event)) event.stopPropagation();
22562
+ });
22563
+
20074
22564
  if (els.prompt) {
20075
22565
  els.prompt.addEventListener('keydown', function(e) {
20076
- 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()) {
20077
22568
  e.preventDefault();
20078
22569
  window.stopFlowRun();
20079
22570
  return;
20080
22571
  }
20081
- if (e.key === 'Escape' && isCurrentConversationRunning() && !promptHasText()) {
22572
+ if (e.key === 'Escape' && isCurrentConversationRunning()) {
20082
22573
  e.preventDefault();
20083
22574
  window.stopCurrentConversation();
20084
22575
  return;
20085
22576
  }
20086
- if (e.key === 'Enter' && e.ctrlKey) {
22577
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey) {
20087
22578
  e.preventDefault();
20088
22579
  if (state.mode === 'flow' || (currentFlowRunning() && flowTakeoverMatchesCurrent())) {
20089
22580
  window.submitCurrentAction();
@@ -20093,9 +22584,9 @@ function schedulePostStartupUiRendering() {
20093
22584
  window.sendMessage(opposite);
20094
22585
  return;
20095
22586
  }
20096
- if (e.key === 'Enter' && !e.shiftKey) {
22587
+ if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
20097
22588
  e.preventDefault();
20098
- window.submitCurrentAction();
22589
+ window.submitCurrentAction('enter');
20099
22590
  }
20100
22591
  });
20101
22592
  els.prompt.addEventListener('input', function() {
@@ -20190,10 +22681,10 @@ function schedulePostStartupUiRendering() {
20190
22681
  els['editor-textarea'].addEventListener('select', window.handleEditorCaretChange);
20191
22682
  els['editor-textarea'].addEventListener('keyup', window.handleEditorCaretChange);
20192
22683
  els['editor-textarea'].addEventListener('keydown', function(e) {
22684
+ if (e.isComposing || e.key === 'Process' || e.keyCode === 229) return;
20193
22685
  if (state.editorCompletionText && e.key === 'Tab') { e.preventDefault(); window.acceptEditorCompletion(); return; }
20194
22686
  if (e.key === 'Escape' && (state.editorCompletionText || (els['editor-completion'] && els['editor-completion'].classList.contains('open')))) { e.preventDefault(); window.dismissEditorCompletion(); return; }
20195
22687
  if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') { e.preventDefault(); window.saveEditor(); return; }
20196
- if ((e.ctrlKey && e.key === ' ') || (e.altKey && e.key === '\\')) { e.preventDefault(); window.requestEditorCompletion(); return; }
20197
22688
  if (state.editorVimEnabled && e.key === 'Escape') { state.editorVimMode = 'normal'; state.editorVimPending = ''; e.preventDefault(); window.renderNativeEditor(); return; }
20198
22689
  if (window.handleEditorVimKey(e)) return;
20199
22690
  });