dsh-vscode-mode 0.1.17 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({
29
29
  let react = require("react");
30
30
  react = __toESM(react, 1);
31
31
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/editor.css.mjs
32
- const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):\n 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */\n[data-edrv-view] { --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }\n[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: rgba(255,255,255,.12); color: var(--dsw-alias-label-primary, #eee); }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: #fff; font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-mn-add-line { background: rgba(15,157,88,.10); }\n[data-edrv-view] .edrv-mn-gutter-add { color: #0f9d58 !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone { background: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px rgba(217,83,79,.12); }\n[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }\n[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }\n[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }\n[data-edrv-view] .edrv-monaco-host { flex: 1; min-height: 0; position: relative; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 16px rgba(31,41,51,.12); overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }\n[data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }\n[data-edrv-view] .edrv-pill-ghost { background: #ffffff; color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: rgba(217,119,6,.5); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n";
32
+ const css$1 = "/* dsh-vscode-mode 编辑区样式(原 ensureEdrvStyles 字符串,落盘为真 .css,由 tsdown CSS-inline 注入) */\n/* 浅色主题(对齐参考图:#f8f8f8 近白底 + 薄荷绿药丸 + 青绿文字 + 三文鱼 undo + 浅灰边框):\n 在 [data-edrv-view] 作用域内重定义 --dsw-alias-*,级联命中组件内联样式与 CSS。 */\n[data-edrv-view] { box-sizing: border-box; --edrv-tab-h: 34px; --dsw-alias-bg-base: #f8f8f8; --dsw-alias-bg-layer-1: #ffffff; --dsw-alias-bg-layer-2: #f0f4f4; --dsw-alias-bg-overlay: #ffffff; --dsw-alias-label-primary: #1f2933; --dsw-alias-label-secondary: #52606d; --dsw-alias-label-tertiary: #9aa5b1; --dsw-alias-border-l1: #e0e6e8; --dsw-alias-border-l2: #cbd2d9; --dsw-alias-brand-primary: #0f9d58; --dsw-alias-state-success-primary: #0f9d58; --dsw-alias-state-error-primary: #d9534f; --dsw-alias-state-warn-primary: #d97706; --dsw-alias-interactive-bg-hover: rgba(15,157,88,.08); }\n\n[data-edrv-view] .edrv-tabs { display: flex; align-items: center; gap: 2px; padding: 2px 8px 0; background: var(--dsw-alias-bg-layer-1, transparent); overflow-x: auto; scrollbar-width: thin; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 8px 0 12px; border: none; border-radius: 6px 6px 0 0; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-tab.edrv-tab-active { background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); box-shadow: inset 0 -2px 0 var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-tab .edrv-tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4f8cff); flex-shrink: 0; }\n[data-edrv-view] .edrv-tab .edrv-tab-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 4px; color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; }\n[data-edrv-view] .edrv-tab .edrv-tab-x:hover { background: rgba(255,255,255,.12); color: var(--dsw-alias-label-primary, #eee); }\n[data-edrv-view] .edrv-tab-add { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-left: 2px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); font-size: 14px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-tab-add:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-pathbar { display: flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; background: var(--dsw-alias-bg-layer-1, #ffffff); border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); font-size: 12px; flex-shrink: 0; overflow: hidden; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-name { font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-full { flex: 1; min-width: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; text-align: left; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-meta { flex-shrink: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-variant-numeric: tabular-nums; }\n[data-edrv-view] .edrv-pathbar .edrv-pb-status { color: var(--dsw-alias-label-secondary, #52606d); }\n[data-edrv-view] .edrv-path-input { flex: 1; min-width: 140px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search-wrap { position: relative; flex-shrink: 0; display: flex; align-items: center; padding: 2px 8px 2px 4px; }\n[data-edrv-view] .edrv-search { width: 190px; height: 24px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 6px; background: var(--dsw-alias-bg-base, #111); color: var(--dsw-alias-label-primary, #eee); font-size: 12px; outline: none; }\n[data-edrv-view] .edrv-search:focus { border-color: var(--dsw-alias-brand-primary, #4f8cff); }\n[data-edrv-view] .edrv-search-pop { position: absolute; top: calc(100% + 4px); right: 8px; width: min(460px, calc(100vw - 24px)); max-height: 300px; overflow: auto; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); border-radius: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.4); z-index: 60; padding: 4px; }\n[data-edrv-view] .edrv-search-item { display: flex; flex-direction: column; gap: 1px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }\n[data-edrv-view] .edrv-search-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.07)); }\n[data-edrv-view] .edrv-search-item .n { color: var(--dsw-alias-label-primary, #eee); font-size: 12px; word-break: break-all; }\n[data-edrv-view] .edrv-search-item .d { color: var(--dsw-alias-label-tertiary, #888); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-search-empty { padding: 8px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }\n[data-edrv-view] .edrv-btn-mini { font-size: 11px; padding: 2px 9px; border: none; border-radius: 999px; cursor: pointer; color: #fff; font-weight: 600; white-space: nowrap; }\n[data-edrv-view] .edrv-btn-keep { background: var(--dsw-alias-state-success-primary, #2e9e44); }\n[data-edrv-view] .edrv-btn-undo { background: var(--dsw-alias-state-warn-primary, #d97706); }\n[data-edrv-view] .edrv-btn-mini:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-statusbar { display: flex; align-items: center; gap: 10px; padding: 3px 10px; border-top: 1px solid var(--dsw-alias-border-l1, #333); background: var(--dsw-alias-bg-layer-1, transparent); font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); flex-shrink: 0; white-space: nowrap; overflow: hidden; }\n[data-edrv-view] .edrv-statusbar .edrv-sp-path { flex: 1; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffchip { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-state-warn-primary, #b7791f); cursor: pointer; white-space: nowrap; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn { font-size: 12px; padding: 2px 6px; border: none; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; border-radius: 4px; flex-shrink: 0; }\n[data-edrv-view] .edrv-chip-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-mn-add-line { background: rgba(15,157,88,.10); }\n[data-edrv-view] .edrv-mn-gutter-add { color: #0f9d58 !important; font-weight: 700; display: flex !important; align-items: center; justify-content: center; }\n[data-edrv-view] .edrv-mn-gutter-add::before { content: \"+\"; }\n[data-edrv-view] .edrv-del-zone { background: #fdeaea; border-top: 1px solid rgba(217,83,79,.35); border-bottom: 1px solid rgba(217,83,79,.35); box-shadow: inset 0 0 0 1px rgba(217,83,79,.12); }\n[data-edrv-view] .edrv-del-row { white-space: pre; font-family: var(--ds-font-family-code, ui-monospace, monospace); font-size: 12.5px; line-height: 20px; color: #c0392b; }\n[data-edrv-view] .edrv-del-text { display: inline-block; white-space: pre; }\n[data-edrv-view] .edrv-minus-overlay { position: absolute; left: 0; top: 0; width: 100%; height: 100%; max-width: 100%; overflow: visible; pointer-events: none; z-index: 6; }\n[data-edrv-view] .edrv-minus-item { position: absolute; box-sizing: border-box; max-width: 100%; overflow: hidden; }\n[data-edrv-view] .edrv-minus-item::before { content: ''; position: absolute; left: 4px; top: 50%; width: 8px; height: 2px; transform: translateY(-50%); border-radius: 2px; background: #d9534f; }\n[data-edrv-view] .edrv-monaco-host { flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; overflow: hidden; }\n[data-edrv-view] .edrv-monaco-host > div { position: absolute; inset: 0; min-width: 0; min-height: 0; }\n[data-edrv-view] .edrv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; height: 100%; color: var(--dsw-alias-label-tertiary, #777); font-size: 13px; }\n[data-edrv-view] .edrv-diffbar { position: absolute; left: 0; right: 0; bottom: 10px; margin: 0 auto; z-index: 25; width: fit-content; max-width: calc(100% - 20px); border-radius: 12px; background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, #f0f4f4)); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); box-shadow: none; overflow: visible; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-editor-area > .edrv-diffbar { max-height: calc(100% - 20px); }\n[data-edrv-view] .edrv-diffbar-row { display: flex; align-items: center; gap: 6px; height: 36px; padding: 4px 5px 4px 12px; flex-wrap: nowrap; }\n[data-edrv-view] .edrv-diffbar-count { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); min-width: 42px; text-align: center; white-space: nowrap; cursor: pointer; }\n[data-edrv-view] .edrv-diffbar-count.edrv-count-file { min-width: 64px; cursor: default; }\n[data-edrv-view] .edrv-diffbar-file { flex: 1; min-width: 0; font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n[data-edrv-view] .edrv-pill { font-size: 11px; padding: 3px 10px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-weight: 600; }\n[data-edrv-view] .edrv-pill-keep { background: #e8f8e8; color: #0f7a45; border: 1px solid rgba(15,157,88,.4); }\n[data-edrv-view] .edrv-pill-undo { background: #fdeaea; color: #c0392b; border: 1px solid rgba(217,83,79,.4); }\n[data-edrv-view] .edrv-pill-ghost { background: #ffffff; color: var(--dsw-alias-label-secondary, #52606d); border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-view] .edrv-diffbar-menu { position: absolute; right: 8px; bottom: calc(100% + 6px); z-index: 45; min-width: 160px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 6px 20px rgba(31,41,51,.16); padding: 4px; display: flex; flex-direction: column; gap: 2px; }\n[data-edrv-view] .edrv-diffmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; }\n[data-edrv-view] .edrv-diffmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n/* 编辑区 / Tab 右键菜单(添加文件/选中内容到对话) */\n[data-edrv-view] .edrv-ctxmenu { position: fixed; z-index: 71; min-width: 200px; max-width: 320px; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 8px 26px rgba(31,41,51,.2); padding: 4px; display: flex; flex-direction: column; gap: 1px; }\n[data-edrv-view] .edrv-ctxmenu-item { display: flex; align-items: center; gap: 8px; font-size: 12px; line-height: 18px; padding: 7px 10px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-ctxmenu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-ctxmenu-item.edrv-ctxmenu-danger { color: var(--dsw-alias-state-error-primary, #d9534f); }\n[data-edrv-view] .edrv-ctxmenu-sep { height: 1px; margin: 3px 4px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffbar-body { max-height: 130px; max-width: min(560px, calc(100vw - 40px)); overflow: auto; padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 4px; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 6px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-diffrow-l { font-size: 11px; color: var(--dsw-alias-label-primary, #1f2933); min-width: 74px; font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-o { font-size: 11px; color: var(--dsw-alias-state-error-primary, #d9534f); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-n { font-size: 11px; color: var(--dsw-alias-state-success-primary, #0f9d58); font-family: var(--ds-font-family-code, ui-monospace, monospace); }\n[data-edrv-view] .edrv-diffrow-tag { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-diffrow-stale { border-color: rgba(217,119,6,.5); cursor: default; }\n[data-edrv-view] .edrv-diffbox-others { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 4px 2px 0; border-top: 1px solid var(--dsw-alias-border-l1, #e0e6e8); margin-top: 2px; }\n[data-edrv-view] .edrv-diffrow-file { font-size: 11px; padding: 2px 10px; border: 1px solid var(--dsw-alias-border-l1, #e0e6e8); border-radius: 999px; background: var(--dsw-alias-bg-layer-2, #f0f4f4); color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); color: var(--dsw-alias-label-primary, #1f2933); }\n/* 编辑区 hover 差异块的 Keep/Undo 浮层 */\n[data-edrv-view] .edrv-hoveract { position: absolute; z-index: 30; display: flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 8px; background: var(--dsw-alias-bg-overlay, #ffffff); border: 1px solid var(--dsw-alias-border-l2, #cbd2d9); box-shadow: 0 4px 14px rgba(31,41,51,.14); }\n[data-edrv-view] .edrv-launch { position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 40; border-radius: 10px; background: var(--dsw-alias-bg-overlay, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #555); box-shadow: 0 6px 24px rgba(0,0,0,.5); display: flex; flex-direction: column; max-height: 60%; overflow: hidden; }\n[data-edrv-view] .edrv-launch-head { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l1, #333); }\n[data-edrv-view] .edrv-launch-tab { font-size: 12px; padding: 3px 12px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #aaa); cursor: pointer; }\n[data-edrv-view] .edrv-launch-tab.on { background: var(--dsw-alias-bg-layer-2, transparent); color: var(--dsw-alias-label-primary, #eee); font-weight: 600; }\n[data-edrv-view] .edrv-launch-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 8px; display: flex; flex-direction: column; gap: 4px; }\n[data-edrv-view] .edrv-launch-row { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1, #333); border-radius: 6px; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-view] .edrv-launch-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(255,255,255,.06)); }\n[data-edrv-view] .edrv-launch-path { flex: 1; min-width: 0; font-size: 12px; color: var(--dsw-alias-label-primary, #eee); word-break: break-all; }\n[data-edrv-view] .edrv-launch-cnt { font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }\n[data-edrv-view] .edrv-launch-arch { display: flex; flex-direction: column; gap: 6px; }\n[data-edrv-view] .monaco-editor { overflow: hidden !important; }\n[data-edrv-view] .monaco-editor .sticky-widget { z-index: 30 !important; background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-content,\n[data-edrv-view] .monaco-editor .sticky-widget .sticky-line-number { background: var(--dsw-alias-bg-base, #f8f8f8) !important; }\n[data-edrv-view] .monaco-editor, [data-edrv-view] .monaco-editor .margin, [data-edrv-view] .monaco-editor-background { background: transparent !important; }\n[data-edrv-view] .monaco-editor .scrollbar .slider { background: rgba(128,128,128,.3) !important; }\n[data-edrv-view] .edrv-loading { gap: 8px; padding: 20px; }\n[data-edrv-view] .edrv-loading-title { color: var(--dsw-alias-label-primary, #1f2933); font-size: 13px; }\n[data-edrv-view] .edrv-progress { width: min(360px, 72%); height: 6px; overflow: hidden; border-radius: 999px; background: var(--dsw-alias-border-l1, #e0e6e8); }\n[data-edrv-view] .edrv-progress-value { height: 100%; border-radius: inherit; background: var(--dsw-alias-brand-primary, #0f9d58); transition: width .24s ease; }\n[data-edrv-view] .edrv-loading-percent { color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; font-variant-numeric: tabular-nums; }\n@media (prefers-reduced-motion: reduce) {\n [data-edrv-view] .edrv-progress-value { transition: none; }\n}\n\n/* ===== 唯一会话级差异 dock(遵循 DSH todo/queue 原生布局) ===== */\n[data-edrv-diff-dock-shell] {\n box-sizing: border-box;\n width: calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n max-width: calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));\n margin: 0 auto;\n padding: 0;\n flex: none;\n border: 1px solid var(--dsw-alias-border-l1, #e0e6e8);\n border-radius: 12px;\n background: var(--dsw-specific-tip, var(--dsw-alias-bg-layer-2, transparent));\n box-shadow: none;\n overflow: hidden;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock {\n box-sizing: border-box;\n position: relative;\n left: auto;\n right: auto;\n bottom: auto;\n z-index: auto;\n width: 100%;\n max-width: none;\n margin: 0;\n display: flex;\n flex-direction: column;\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n[data-edrv-diff-dock-shell] .edrv-diffbar-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-row { min-height: 36px; padding: 6px 12px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-body { max-height: 180px; overflow: auto; padding: 6px 12px 10px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button { box-sizing: border-box; display: flex; align-items: center; justify-content: center; width: 100%; height: 24px; border: none; border-radius: 8px; padding: 0 12px; background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; line-height: 24px; text-align: center; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-dock-button:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffbar-count { display: inline-flex; align-items: center; color: var(--dsw-alias-label-primary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 500; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-file { display: inline-flex; align-items: center; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-row { display: flex; align-items: center; height: 36px; min-height: 36px; padding: 4px 12px; overflow-x: auto; scrollbar-width: thin; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { display: flex; align-items: center; gap: 10px; width: 100%; min-width: max-content; min-height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-nav,\n[data-edrv-diff-dock-shell] .edrv-diffbar-files,\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { display: flex; align-items: center; gap: 6px; flex: none; height: 28px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-actions { margin-left: auto; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle { display: inline-flex; align-items: center; justify-content: center; width: 28px; min-width: 28px; padding: 0; font-size: 13px; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-details-toggle svg { width: 14px; height: 14px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files { min-width: 0; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { min-width: 72px; max-width: 240px; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-progress { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; font-variant-numeric: tabular-nums; }\n[data-edrv-diff-dock-shell] .edrv-diffbar-summary { min-width: 0; color: var(--dsw-alias-label-tertiary); font-family: Inter, var(--dsw-font-family); font-size: 13px; font-weight: 400; line-height: 24px; }\n[data-edrv-diff-dock-shell] .edrv-pill { box-sizing: border-box; height: 28px; padding: 0 9px; border: none; border-radius: 999px; cursor: pointer; white-space: nowrap; font-size: 11px; line-height: 26px; font-weight: 600; }\n[data-edrv-diff-dock-shell] .edrv-pill-keep { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); border: 1px solid var(--dsw-alias-state-success-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-undo { background: var(--dsw-alias-state-error-tertiary); color: var(--dsw-alias-state-error-primary); border: 1px solid var(--dsw-alias-state-error-secondary); }\n[data-edrv-diff-dock-shell] .edrv-pill-ghost { background: transparent; color: var(--dsw-alias-label-secondary); border: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-pill:hover:not(:disabled) { background-color: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-pill:focus-visible { outline: 2px solid var(--dsw-alias-label-tertiary); outline-offset: -2px; }\n[data-edrv-diff-dock-shell] .edrv-pill:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffrow { display: flex; align-items: center; gap: 8px; padding: 3px 6px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; cursor: pointer; flex-wrap: wrap; }\n[data-edrv-diff-dock-shell] .edrv-diffrow:hover { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-l { color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-o { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-n { color: var(--dsw-alias-state-success-primary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-tag { color: var(--dsw-alias-label-tertiary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-stale { border-color: var(--dsw-alias-state-warn-secondary); cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diffbox-others { border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file { border-color: var(--dsw-alias-border-l1); background: transparent; color: var(--dsw-alias-label-secondary); }\n[data-edrv-diff-dock-shell] .edrv-diffrow-file:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px 8px; border-top: 1px solid var(--dsw-alias-border-l1); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item { display: flex; align-items: center; width: 100%; min-height: 28px; gap: 8px; padding: 4px 8px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-primary); cursor: pointer; text-align: left; font-size: 12px; line-height: 20px; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item.danger { color: var(--dsw-alias-state-error-primary); }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-item:disabled { opacity: .45; cursor: default; }\n[data-edrv-diff-dock-shell] .edrv-diff-details .edrv-diffmenu-sep { height: 1px; margin: 2px 4px; background: var(--dsw-alias-border-l1); }\n@media (max-width: 620px) {\n [data-edrv-diff-dock-shell] .edrv-diffbar-editor-content { width: max-content; }\n [data-edrv-diff-dock-shell] .edrv-diffbar-files .edrv-diffbar-file { max-width: 160px; }\n}\n\n/* ===== 侧边栏(活动栏 + 面板区 + 文件树) ===== */\n[data-edrv-view] .edrv-editor-row { display: flex; flex: 1 1 auto; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col { box-sizing: border-box; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-main-col > .edrv-pathbar,\n[data-edrv-view] .edrv-main-col > .edrv-tabs { flex: 0 0 auto; }\n[data-edrv-view] .edrv-editor-area { position: relative; display: flex; flex: 1 1 auto; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; }\n[data-edrv-view] .edrv-sidebar { display: flex; flex-direction: row; flex-shrink: 0; min-width: 0; height: 100%; background: var(--dsw-alias-bg-layer-1, #ffffff); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); overflow: hidden; }\n[data-edrv-view] .edrv-rail { display: flex; flex-direction: column; align-items: center; width: 44px; flex-shrink: 0; padding: 6px 0; background: var(--dsw-alias-bg-layer-2, #f0f4f4); border-right: 1px solid var(--dsw-alias-border-l1, #e0e6e8); gap: 4px; }\n[data-edrv-view] .edrv-rail-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-rail-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-rail-btn.edrv-rail-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); box-shadow: inset 2px 0 0 var(--dsw-alias-brand-primary, #0f9d58); }\n[data-edrv-view] .edrv-rail-icon { font-size: 15px; line-height: 1; }\n[data-edrv-view] .edrv-rail-badge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--dsw-alias-brand-primary, #0f9d58); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; }\n[data-edrv-view] .edrv-side-body { flex: 1; min-width: 0; min-height: 0; display: flex; overflow: hidden; }\n[data-edrv-view] .edrv-side-panel { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }\n[data-edrv-view] .edrv-side-head { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 10px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l1, #e0e6e8); flex-shrink: 0; }\n[data-edrv-view] .edrv-side-title { font-size: 12px; font-weight: 600; color: var(--dsw-alias-label-primary, #1f2933); white-space: nowrap; }\n[data-edrv-view] .edrv-side-root { font-size: 11px; color: var(--dsw-alias-label-tertiary, #9aa5b1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-side-btn { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: none; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, #52606d); font-size: 12px; cursor: pointer; flex-shrink: 0; }\n[data-edrv-view] .edrv-side-btn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; font-size: 12px; }\n[data-edrv-view] .edrv-tree-row { display: flex; align-items: center; gap: 4px; height: 22px; padding-right: 6px; border-radius: 5px; color: var(--dsw-alias-label-primary, #1f2933); cursor: pointer; white-space: nowrap; }\n[data-edrv-view] .edrv-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.08)); }\n[data-edrv-view] .edrv-tree-row.edrv-tree-active { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.14)); }\n[data-edrv-view] .edrv-tree-chev { width: 12px; flex-shrink: 0; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 10px; text-align: center; }\n[data-edrv-view] .edrv-tree-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n[data-edrv-view] .edrv-tree-name.edrv-tree-dim { color: var(--dsw-alias-label-tertiary, #9aa5b1); }\n[data-edrv-view] .edrv-tree-badge { min-width: 14px; height: 14px; padding: 0 4px; border-radius: 999px; background: var(--dsw-alias-state-warn-primary, #d97706); color: #fff; font-size: 9px; font-weight: 600; line-height: 14px; text-align: center; box-sizing: border-box; flex-shrink: 0; }\n[data-edrv-view] .edrv-tree-loading { height: 22px; line-height: 22px; color: var(--dsw-alias-label-tertiary, #9aa5b1); font-size: 11px; }\n[data-edrv-view] .edrv-tree-error { display: flex; align-items: center; gap: 8px; margin: 4px 2px 6px; padding: 6px 8px; border: 1px solid rgba(217,83,79,.4); border-radius: 6px; background: #fdeaea; color: var(--dsw-alias-state-error-primary, #d9534f); font-size: 11px; }\n[data-edrv-view] .edrv-side-resize { width: 5px; flex-shrink: 0; cursor: col-resize; background: transparent; }\n[data-edrv-view] .edrv-side-resize:hover { background: var(--dsw-alias-brand-primary, #0f9d58); opacity: .35; }\n[data-edrv-view] .edrv-chip-btn.edrv-chip-on { background: var(--dsw-alias-interactive-bg-hover, rgba(15,157,88,.12)); color: var(--dsw-alias-label-primary, #1f2933); }\n\n";
33
33
  const tagId$1 = "dsh-vscode-mode/editor.css";
34
34
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
35
35
  const tag = document.createElement("style");
@@ -82,28 +82,53 @@ window.__ModuleLoader__.load({
82
82
  * 作者 ddj 2026-08-20
83
83
  */
84
84
  /**
85
- * 打开中央「文件编辑」页签:点击 shell 会话头部的视图页签(DOM 级,无需 store actions)。
86
- * @author ddj 2026年08月20号
87
- * @param path 要打开的路径(可空)
85
+ * 选择中央「文件编辑」页签(DOM 级,无需 store actions)。
86
+ * @author ddj 2026年08月26号
88
87
  */
89
- function openEditorView(path) {
88
+ function selectEditorTab() {
90
89
  const tabs = Array.from(document.querySelectorAll("div[role=\"tablist\"] button[role=\"tab\"]"));
91
- for (const b of tabs) if (b.textContent && b.textContent.includes("文件编辑")) {
92
- b.click();
90
+ for (const tab of tabs) if (tab.textContent && tab.textContent.includes("文件编辑")) {
91
+ tab.click();
93
92
  break;
94
93
  }
95
- const p = path ?? null;
94
+ }
95
+ /**
96
+ * 打开中央「文件编辑」页签。
97
+ * @author ddj 2026年08月26号
98
+ * @param path 要打开的路径(可空)
99
+ */
100
+ function openEditorView(path) {
101
+ selectEditorTab();
102
+ const target = path ?? null;
96
103
  setTimeout(() => {
97
- window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: { path: p } }));
104
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: { path: target } }));
105
+ }, 80);
106
+ }
107
+ /**
108
+ * 打开文件编辑页并聚焦指定文件的首个差异。
109
+ * @author ddj 2026年08月26号
110
+ * @param path 待聚焦的差异文件路径
111
+ */
112
+ function openDiffView(path) {
113
+ selectEditorTab();
114
+ setTimeout(() => {
115
+ window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
116
+ path,
117
+ focusDiff: true
118
+ } }));
98
119
  }, 80);
99
120
  }
100
121
  /** 差异状态变化:触发角标/DiffBox 重拉。 */
101
122
  function emitRefresh() {
102
123
  window.dispatchEvent(new CustomEvent("edrv:refresh"));
103
124
  }
104
- /** 拉起 DiffLauncher。 */
105
- function emitShowLauncher() {
106
- window.dispatchEvent(new CustomEvent("edrv:show-launcher"));
125
+ /**
126
+ * 拉起 DiffLauncher。
127
+ * @author ddj 2026年08月26号
128
+ * @param tab 可选的目标页签
129
+ */
130
+ function emitShowLauncher(tab) {
131
+ window.dispatchEvent(new CustomEvent("edrv:show-launcher", { detail: { tab } }));
107
132
  }
108
133
  //#endregion
109
134
  //#region src/client/monaco/loader.ts
@@ -863,199 +888,6 @@ window.__ModuleLoader__.load({
863
888
  lineHeight: 1.55
864
889
  };
865
890
  //#endregion
866
- //#region src/client/ui/DiffBox.ts
867
- /**
868
- * dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
869
- * 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
870
- * 作者 ddj 2026-08-20
871
- */
872
- /**
873
- * 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
874
- * (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
875
- */
876
- function DiffBox(props) {
877
- const { pendingRegions, staleRegions, onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles, allPendingCount, onRollback, onJump, otherFiles, onOpenOther, onOpenLauncher, onRefresh, activePath, diffIdx, diffTotal, fileIdx, fileTotal, onPrevDiff, onNextDiff, onPrevFile, onNextFile } = props;
878
- const [expanded, setExpanded] = react.default.useState(false);
879
- const [menuOpen, setMenuOpen] = react.default.useState(false);
880
- const canAct = pendingRegions.length > 0;
881
- const base = String(activePath || "").split(/[\\/]/).pop() || "";
882
- let bodyEl = null;
883
- if (expanded) {
884
- const rows = pendingRegions.map((r) => {
885
- const key = callIdAttr(r.callId, r.idx);
886
- return react.default.createElement("div", {
887
- key,
888
- className: "edrv-diffrow",
889
- "data-edrv-hunk": key,
890
- title: "跳转到 L" + (r.start ?? "?"),
891
- onClick: () => onJump(r)
892
- }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + "-" + (r.end ?? "?")), r.oldLines.length ? react.default.createElement("span", { className: "edrv-diffrow-o" }, "-" + r.oldLines.length) : null, r.newLines.length ? react.default.createElement("span", { className: "edrv-diffrow-n" }, "+" + r.newLines.length) : null, r.create ? react.default.createElement("span", { className: "edrv-diffrow-tag" }, "新建") : null, react.default.createElement("span", { style: { flex: 1 } }));
893
- });
894
- const stale = staleRegions.map((r) => react.default.createElement("div", {
895
- key: "stale" + callIdAttr(r.callId, r.idx),
896
- className: "edrv-diffrow edrv-diffrow-stale",
897
- title: "差异无法定位(文件可能已被手动修改)"
898
- }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + " 无法定位"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, "冲突/需刷新"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, badgeOf(r.status))));
899
- let othersEl = null;
900
- if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
901
- key: f.path,
902
- className: "edrv-diffrow-file",
903
- title: f.path,
904
- onClick: () => onOpenOther(f.path)
905
- }, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
906
- bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
907
- }
908
- const menu = menuOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
909
- style: {
910
- position: "fixed",
911
- inset: 0,
912
- zIndex: 44
913
- },
914
- onClick: () => setMenuOpen(false)
915
- }), react.default.createElement("div", { className: "edrv-diffbar-menu" }, react.default.createElement("button", {
916
- className: "edrv-diffmenu-item",
917
- disabled: !allPendingCount,
918
- onClick: () => {
919
- onAcceptAllFiles();
920
- setMenuOpen(false);
921
- }
922
- }, "✓ Keep All(全部文件采纳)"), react.default.createElement("button", {
923
- className: "edrv-diffmenu-item danger",
924
- disabled: !allPendingCount,
925
- onClick: () => {
926
- onUndoAllFiles();
927
- setMenuOpen(false);
928
- }
929
- }, "↩ Undo All(全部文件不采纳)"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
930
- className: "edrv-diffmenu-item",
931
- onClick: () => {
932
- onOpenLauncher("pending");
933
- setMenuOpen(false);
934
- }
935
- }, "🗂 差异总览"), react.default.createElement("button", {
936
- className: "edrv-diffmenu-item",
937
- onClick: () => {
938
- onOpenLauncher("archive");
939
- setMenuOpen(false);
940
- }
941
- }, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
942
- className: "edrv-diffmenu-item",
943
- disabled: !canAct,
944
- onClick: () => {
945
- onRollback();
946
- setMenuOpen(false);
947
- }
948
- }, "⟲ 回滚文件"), react.default.createElement("button", {
949
- className: "edrv-diffmenu-item",
950
- onClick: () => {
951
- onRefresh();
952
- setMenuOpen(false);
953
- }
954
- }, "⟳ 刷新"))) : null;
955
- return react.default.createElement("div", {
956
- className: "edrv-diffbar",
957
- "data-edrv-diffbox": "1"
958
- }, react.default.createElement("div", { className: "edrv-diffbar-row" }, react.default.createElement("button", {
959
- className: "edrv-pill edrv-pill-ghost",
960
- title: "上一个差异",
961
- disabled: !canAct,
962
- onClick: onPrevDiff
963
- }, "↑"), react.default.createElement("span", {
964
- className: "edrv-diffbar-count",
965
- title: "当前文件内差异位置(点击展开/收起差异列表)",
966
- onClick: () => setExpanded((v) => !v)
967
- }, diffTotal ? diffIdx + 1 + "/" + diffTotal : "0/0"), react.default.createElement("button", {
968
- className: "edrv-pill edrv-pill-ghost",
969
- title: "下一个差异",
970
- disabled: !canAct,
971
- onClick: onNextDiff
972
- }, "↓"), react.default.createElement("button", {
973
- className: "edrv-pill edrv-pill-ghost",
974
- title: "上一个差异文件",
975
- disabled: !fileTotal,
976
- onClick: onPrevFile
977
- }, "←"), react.default.createElement("span", {
978
- className: "edrv-diffbar-count edrv-count-file",
979
- title: "差异文件位置"
980
- }, fileTotal ? fileIdx + 1 + "/" + fileTotal + " 文件" : "0/0 文件"), react.default.createElement("button", {
981
- className: "edrv-pill edrv-pill-ghost",
982
- title: "下一个差异文件",
983
- disabled: !fileTotal,
984
- onClick: onNextFile
985
- }, "→"), react.default.createElement("span", {
986
- className: "edrv-diffbar-file",
987
- title: activePath || ""
988
- }, base), react.default.createElement("button", {
989
- className: "edrv-pill edrv-pill-keep",
990
- title: "采纳当前文件的全部差异",
991
- disabled: !canAct,
992
- onClick: onAcceptFile
993
- }, "✓ Keep"), react.default.createElement("button", {
994
- className: "edrv-pill edrv-pill-undo",
995
- title: "不采纳当前文件的全部差异(回滚)",
996
- disabled: !canAct,
997
- onClick: onUndoFile
998
- }, "↩ Undo"), react.default.createElement("button", {
999
- className: "edrv-pill edrv-pill-ghost",
1000
- title: "更多操作",
1001
- onClick: () => setMenuOpen((v) => !v)
1002
- }, "⋮")), bodyEl, menu);
1003
- }
1004
- //#endregion
1005
- //#region src/client/ui/DiffBarEmpty.ts
1006
- /**
1007
- * dsh-vscode-mode client — DiffBarEmpty:无文件打开 / 当前文件无差异但有差异文件时的空态浮窗。
1008
- * 迁移自原 src/client/index.ts 的 DiffBarEmpty,语义不改。
1009
- * 作者 ddj 2026-08-20
1010
- */
1011
- /**
1012
- * 空态差异条:提示当前状态 + 查看下一个差异文件 + ⋮(总览/归档/刷新)。
1013
- */
1014
- function DiffBarEmpty(props) {
1015
- const { sum, active, staleCount, onNextFile, onOpenLauncher, onRefresh } = props;
1016
- const [menuOpen, setMenuOpen] = react.default.useState(false);
1017
- const n = sum?.totalFiles ?? 0;
1018
- const tip = active ? staleCount > 0 ? "当前文件有 " + staleCount + " 处差异无法定位(可能已被后续修改影响)" : "当前文件无待处理差异" : "未打开文件";
1019
- const menu = menuOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
1020
- style: {
1021
- position: "fixed",
1022
- inset: 0,
1023
- zIndex: 44
1024
- },
1025
- onClick: () => setMenuOpen(false)
1026
- }), react.default.createElement("div", { className: "edrv-diffbar-menu" }, react.default.createElement("button", {
1027
- className: "edrv-diffmenu-item",
1028
- onClick: () => {
1029
- onOpenLauncher("pending");
1030
- setMenuOpen(false);
1031
- }
1032
- }, "🗂 差异总览"), react.default.createElement("button", {
1033
- className: "edrv-diffmenu-item",
1034
- onClick: () => {
1035
- onOpenLauncher("archive");
1036
- setMenuOpen(false);
1037
- }
1038
- }, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
1039
- className: "edrv-diffmenu-item",
1040
- onClick: () => {
1041
- onRefresh();
1042
- setMenuOpen(false);
1043
- }
1044
- }, "⟳ 刷新"))) : null;
1045
- return react.default.createElement("div", {
1046
- className: "edrv-diffbar",
1047
- "data-edrv-diffbox": "1"
1048
- }, react.default.createElement("div", { className: "edrv-diffbar-row" }, react.default.createElement("span", { className: "edrv-diffbar-count" }, "⚠ 差异 " + n + " 文件"), react.default.createElement("span", { className: "edrv-diffbar-file" }, tip), react.default.createElement("button", {
1049
- className: "edrv-pill edrv-pill-keep",
1050
- title: "打开并跳转到下一个差异文件",
1051
- onClick: onNextFile
1052
- }, "查看下一个差异文件"), react.default.createElement("button", {
1053
- className: "edrv-pill edrv-pill-ghost",
1054
- title: "更多操作",
1055
- onClick: () => setMenuOpen((v) => !v)
1056
- }, "⋮")), menu);
1057
- }
1058
- //#endregion
1059
891
  //#region src/client/ui/DiffLauncher.ts
1060
892
  /**
1061
893
  * dsh-vscode-mode client — DiffLauncher:全局差异总览 + 归档 圆角下拉。
@@ -1288,6 +1120,213 @@ window.__ModuleLoader__.load({
1288
1120
  }, "✕ 关闭")), react.default.createElement("div", { className: "edrv-launch-body" }, body));
1289
1121
  }
1290
1122
  //#endregion
1123
+ //#region src/client/sidebar/SidebarView.ts
1124
+ /**
1125
+ * dsh-vscode-mode client — 侧边栏容器(活动栏 + 面板区 + 拖拽调宽)。
1126
+ * 面板列表来自注册表(活动栏图标/徽标/激活态),渲染当前激活面板的 render(ctx);
1127
+ * 右侧手柄拖拽调宽(clamp 180–560),底部按钮收起侧边栏。
1128
+ * 作者 ddj 2026-08-26
1129
+ */
1130
+ const W_MIN = 180;
1131
+ const W_MAX = 560;
1132
+ /**
1133
+ * 侧边栏容器。
1134
+ * @param props.registry 面板注册表(list() 提供面板顺序)
1135
+ * @param props.ctx 面板共享上下文
1136
+ * @param props.activePanel 当前激活面板 id
1137
+ * @param props.onActive 激活面板变化回调
1138
+ * @param props.width 面板区宽度
1139
+ * @param props.onWidth 宽度变化回调
1140
+ * @param props.onHide 收起侧边栏回调
1141
+ */
1142
+ function SidebarView(props) {
1143
+ const panels = props.registry?.list?.() ?? [];
1144
+ const ctx = props.ctx;
1145
+ const activePanel = props.activePanel;
1146
+ const onActive = props.onActive;
1147
+ const width = props.width;
1148
+ const onWidth = props.onWidth;
1149
+ const onHide = props.onHide;
1150
+ const active = panels.find((p) => p.id === activePanel) || panels[0];
1151
+ const onPointerDown = (e) => {
1152
+ e.preventDefault();
1153
+ const startX = e.clientX;
1154
+ const startW = width;
1155
+ const onMove = (ev) => {
1156
+ const w = Math.max(W_MIN, Math.min(W_MAX, startW + (ev.clientX - startX)));
1157
+ onWidth(w);
1158
+ };
1159
+ const onUp = () => {
1160
+ window.removeEventListener("pointermove", onMove);
1161
+ window.removeEventListener("pointerup", onUp);
1162
+ };
1163
+ window.addEventListener("pointermove", onMove);
1164
+ window.addEventListener("pointerup", onUp);
1165
+ };
1166
+ const rail = react.default.createElement("div", { className: "edrv-rail" }, react.default.createElement("div", { style: {
1167
+ flex: 1,
1168
+ display: "flex",
1169
+ flexDirection: "column",
1170
+ gap: 2
1171
+ } }, panels.map((p) => {
1172
+ const count = typeof p.badge === "function" ? p.badge(ctx) : null;
1173
+ return react.default.createElement("button", {
1174
+ key: p.id,
1175
+ className: "edrv-rail-btn" + (active && p.id === active.id ? " edrv-rail-on" : ""),
1176
+ title: p.title,
1177
+ onClick: () => onActive(p.id)
1178
+ }, react.default.createElement("span", { className: "edrv-rail-icon" }, p.icon), count > 0 ? react.default.createElement("span", { className: "edrv-rail-badge" }, String(count)) : null);
1179
+ })), react.default.createElement("button", {
1180
+ className: "edrv-rail-btn",
1181
+ title: "隐藏侧边栏 (Ctrl+B)",
1182
+ onClick: onHide
1183
+ }, "◀"));
1184
+ return react.default.createElement("div", {
1185
+ className: "edrv-sidebar",
1186
+ style: { width }
1187
+ }, rail, react.default.createElement("div", { className: "edrv-side-body" }, active ? active.render(ctx) : null), react.default.createElement("div", {
1188
+ className: "edrv-side-resize",
1189
+ onPointerDown
1190
+ }));
1191
+ }
1192
+ //#endregion
1193
+ //#region src/client/diffDockStore.ts
1194
+ const values = /* @__PURE__ */ new Map();
1195
+ const sources = /* @__PURE__ */ new Map();
1196
+ const listeners = /* @__PURE__ */ new Map();
1197
+ /** 通知指定会话的 dock 订阅者。 */
1198
+ function notifyDiff(sessionId) {
1199
+ for (const listener of listeners.get(sessionId) ?? []) listener();
1200
+ }
1201
+ /**
1202
+ * 发布指定会话的最新编辑器差异上下文。
1203
+ * @author ddj 2026年08月26号
1204
+ * @param sessionId 会话 id
1205
+ * @param snapshot 完整 DiffBox 操作上下文
1206
+ * @param source 发布者令牌,用于卸载时防止误清理新实例
1207
+ */
1208
+ function publishDiffDock(sessionId, snapshot, source) {
1209
+ values.set(sessionId, snapshot);
1210
+ if (source) sources.set(sessionId, source);
1211
+ else sources.delete(sessionId);
1212
+ notifyDiff(sessionId);
1213
+ }
1214
+ /**
1215
+ * 清除指定会话的差异上下文。
1216
+ * @author ddj 2026年08月26号
1217
+ * @param sessionId 会话 id
1218
+ * @param source 可选发布者令牌;不匹配时忽略清理
1219
+ */
1220
+ function clearDiffDock(sessionId, source) {
1221
+ if (source && sources.get(sessionId) !== source) return;
1222
+ values.delete(sessionId);
1223
+ sources.delete(sessionId);
1224
+ notifyDiff(sessionId);
1225
+ }
1226
+ /**
1227
+ * 读取指定会话的最新上下文。
1228
+ * @author ddj 2026年08月26号
1229
+ * @param sessionId 会话 id
1230
+ * @returns 最新上下文或 null
1231
+ */
1232
+ function readDiffDock(sessionId) {
1233
+ if (!sessionId) return null;
1234
+ return values.get(sessionId) ?? null;
1235
+ }
1236
+ /**
1237
+ * 订阅指定会话的上下文变化。
1238
+ * @author ddj 2026年08月26号
1239
+ * @param sessionId 会话 id
1240
+ * @param listener 变化回调
1241
+ * @returns 取消订阅函数
1242
+ */
1243
+ function subscribeDiffDock(sessionId, listener) {
1244
+ let bucket = listeners.get(sessionId);
1245
+ if (!bucket) {
1246
+ bucket = /* @__PURE__ */ new Set();
1247
+ listeners.set(sessionId, bucket);
1248
+ }
1249
+ bucket.add(listener);
1250
+ return () => {
1251
+ bucket.delete(listener);
1252
+ if (!bucket.size) listeners.delete(sessionId);
1253
+ };
1254
+ }
1255
+ //#endregion
1256
+ //#region src/client/diffDock.ts
1257
+ /**
1258
+ * dsh-vscode-mode client — 对话差异 dock 纯函数。
1259
+ * 只负责差异文件轮转和文案,状态查询仍由组件复用现有 edrv.list/summarize。
1260
+ * 作者 ddj 2026-08-26
1261
+ */
1262
+ /**
1263
+ * 从稳定差异文件列表中取下一个路径,并返回下一次索引。
1264
+ * @author ddj 2026年08月26号
1265
+ * @param paths 待处理差异文件路径
1266
+ * @param index 当前轮转索引
1267
+ * @returns 下一个路径与归一化后的下一索引
1268
+ */
1269
+ function nextDiffPath(paths, index) {
1270
+ if (!paths.length) return {
1271
+ path: null,
1272
+ index: 0
1273
+ };
1274
+ const at = (Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0) % paths.length;
1275
+ return {
1276
+ path: paths[at] ?? null,
1277
+ index: (at + 1) % paths.length
1278
+ };
1279
+ }
1280
+ /**
1281
+ * 生成对话差异 dock 的单按钮文案。
1282
+ * @author ddj 2026年08月26号
1283
+ * @param count 待处理差异文件数量
1284
+ * @returns 展示文案
1285
+ */
1286
+ function diffDockText(count) {
1287
+ return "差异 " + Math.max(0, Math.floor(count)) + " 个文件 · 查看下一个";
1288
+ }
1289
+ /**
1290
+ * 根据活动文件决定编辑页 dock 形态,加载期间保持 editor 结构稳定。
1291
+ * @author ddj 2026年08月26号
1292
+ * @param activePath 当前活动文件路径
1293
+ * @returns 编辑态或无文件空态
1294
+ */
1295
+ function editorDockMode(activePath) {
1296
+ return activePath ? "editor" : "editor-empty";
1297
+ }
1298
+ /**
1299
+ * 获取切换文件期间稳定展示的文件内差异数量。
1300
+ * @author ddj 2026年08月26号
1301
+ * @param ready 当前文件内容是否已加载完成
1302
+ * @param actualTotal 内容就绪后的精确差异数
1303
+ * @param fallbackTotal 内容加载期间的文件摘要差异数
1304
+ * @returns 非负整数差异数
1305
+ */
1306
+ function displayDiffTotal(ready, actualTotal, fallbackTotal) {
1307
+ const value = ready ? actualTotal : fallbackTotal;
1308
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
1309
+ }
1310
+ //#endregion
1311
+ //#region src/client/editorLayout.ts
1312
+ /**
1313
+ * dsh-vscode-mode client — 编辑区与原生 composer 的几何边界计算。
1314
+ * @author ddj 2026年08月26号
1315
+ */
1316
+ /**
1317
+ * 计算编辑根节点在当前会话滚动区内、composer 上方可使用的高度。
1318
+ * @author ddj 2026年08月26号
1319
+ * @param rootTop 编辑根节点顶边
1320
+ * @param scrollBottom 会话滚动区可视底边
1321
+ * @param composerTop composer 顶边;缺失时回退滚动区底边
1322
+ * @returns 非负可用高度
1323
+ */
1324
+ function editorHeight(rootTop, scrollBottom, composerTop) {
1325
+ const root = Number.isFinite(rootTop) ? rootTop : 0;
1326
+ const scroll = Number.isFinite(scrollBottom) ? scrollBottom : root;
1327
+ return Math.max(0, Math.min(scroll, Number.isFinite(composerTop) ? composerTop : scroll) - root);
1328
+ }
1329
+ //#endregion
1291
1330
  //#region src/client/ui/EditorView.ts
1292
1331
  /**
1293
1332
  * dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
@@ -1304,6 +1343,7 @@ window.__ModuleLoader__.load({
1304
1343
  function EditorView(props) {
1305
1344
  const sessionId = props?.sessionId;
1306
1345
  const schedule = props.schedule;
1346
+ const addToConversation = props.addToConversation;
1307
1347
  const [monaco, setMonaco] = react.default.useState(null);
1308
1348
  const [monacoErr, setMonacoErr] = react.default.useState(null);
1309
1349
  const [records, setRecords] = react.default.useState({});
@@ -1311,6 +1351,7 @@ window.__ModuleLoader__.load({
1311
1351
  const [active, setActive] = react.default.useState(null);
1312
1352
  const [dirtyMap, setDirtyMap] = react.default.useState({});
1313
1353
  const [content, setContent] = react.default.useState(null);
1354
+ const [contentPath, setContentPath] = react.default.useState(null);
1314
1355
  const [status, setStatus] = react.default.useState("");
1315
1356
  const [error, setError] = react.default.useState(null);
1316
1357
  const [loadError, setLoadError] = react.default.useState(null);
@@ -1326,7 +1367,13 @@ window.__ModuleLoader__.load({
1326
1367
  const [hoverAct, setHoverAct] = react.default.useState(null);
1327
1368
  const [diffIdx, setDiffIdx] = react.default.useState(0);
1328
1369
  const [fileIdx, setFileIdx] = react.default.useState(0);
1370
+ const [tabMenu, setTabMenu] = react.default.useState(null);
1371
+ const [sidebarOn, setSidebarOn] = react.default.useState(true);
1372
+ const [sidebarW, setSidebarW] = react.default.useState(240);
1373
+ const [activePanel, setActivePanel] = react.default.useState("explorer");
1374
+ const [focusRequest, setFocusRequest] = react.default.useState(0);
1329
1375
  const editorRef = react.default.useRef(null);
1376
+ const viewRootRef = react.default.useRef(null);
1330
1377
  const monacoRef = react.default.useRef(null);
1331
1378
  const modelsRef = react.default.useRef(/* @__PURE__ */ new Map());
1332
1379
  const saveTimerRef = react.default.useRef(null);
@@ -1343,6 +1390,7 @@ window.__ModuleLoader__.load({
1343
1390
  const hoverEditorRef = react.default.useRef(false);
1344
1391
  const hoverPanelRef = react.default.useRef(false);
1345
1392
  const batchBusyRef = react.default.useRef(false);
1393
+ const menuHandlersRef = react.default.useRef(null);
1346
1394
  const diffRendererRef = react.default.useRef(null);
1347
1395
  if (!diffRendererRef.current) diffRendererRef.current = createDiffRenderer((sid, t) => dbg(sid, t));
1348
1396
  const currentRecords = react.default.useMemo(() => {
@@ -1350,7 +1398,13 @@ window.__ModuleLoader__.load({
1350
1398
  for (const rec of Object.values(records)) if (rec.path === active && rec.archived !== true) list.push(rec);
1351
1399
  return list;
1352
1400
  }, [records, active]);
1353
- const regions = react.default.useMemo(() => diffRegions(currentRecords, content).filter((r) => !r.superseded), [currentRecords, content]);
1401
+ const contentReady = content !== null && contentPath === active;
1402
+ const regions = react.default.useMemo(() => diffRegions(currentRecords, contentReady ? content : null).filter((r) => !r.superseded), [
1403
+ currentRecords,
1404
+ content,
1405
+ contentPath,
1406
+ active
1407
+ ]);
1354
1408
  const pendingRegions = react.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && !r.stale), [regions]);
1355
1409
  const staleRegions = react.default.useMemo(() => regions.filter((r) => r.status === ST.PENDING && r.stale), [regions]);
1356
1410
  hoverRegionsRef.current = pendingRegions;
@@ -1381,9 +1435,12 @@ window.__ModuleLoader__.load({
1381
1435
  return next;
1382
1436
  });
1383
1437
  };
1384
- const refreshRecords = () => {
1438
+ const refreshRecords = (skipStale) => {
1385
1439
  if (!sessionId) return;
1386
- rpc("edrv.list", { sessionId }).then((res) => {
1440
+ rpc("edrv.list", {
1441
+ sessionId,
1442
+ ...skipStale ? { skipStale: true } : {}
1443
+ }).then((res) => {
1387
1444
  if (!res || !res.ok || !Array.isArray(res.records)) return;
1388
1445
  const map = {};
1389
1446
  for (const r of res.records) {
@@ -1407,6 +1464,7 @@ window.__ModuleLoader__.load({
1407
1464
  if (seq !== loadSeqRef.current || path !== active) return;
1408
1465
  if (res && res.ok) {
1409
1466
  setContent(res.content);
1467
+ setContentPath(path);
1410
1468
  setLoadStage((prev) => ({
1411
1469
  progress: Math.max(84, prev.progress),
1412
1470
  message: "文件已读取,准备创建编辑器…"
@@ -1440,9 +1498,21 @@ window.__ModuleLoader__.load({
1440
1498
  react.default.useEffect(() => {
1441
1499
  const onOpen = (e) => {
1442
1500
  const p = e?.detail?.path;
1443
- if (p) addTab(p, true);
1501
+ if (!p) return;
1502
+ if (e?.detail?.focusDiff === true) {
1503
+ pendingFocusRef.current = {
1504
+ path: p,
1505
+ region: null
1506
+ };
1507
+ setFocusRequest((value) => value + 1);
1508
+ }
1509
+ addTab(p, true);
1510
+ };
1511
+ const onShowLauncher = (event) => {
1512
+ const tab = event?.detail?.tab;
1513
+ if (tab === "pending" || tab === "archive") setLauncherTab(tab);
1514
+ setLauncherOpen(true);
1444
1515
  };
1445
- const onShowLauncher = () => setLauncherOpen(true);
1446
1516
  window.addEventListener("edrv:open-editor", onOpen);
1447
1517
  window.addEventListener("edrv:show-launcher", onShowLauncher);
1448
1518
  return () => {
@@ -1450,6 +1520,58 @@ window.__ModuleLoader__.load({
1450
1520
  window.removeEventListener("edrv:show-launcher", onShowLauncher);
1451
1521
  };
1452
1522
  }, [sessionId]);
1523
+ const dockSourceRef = react.default.useRef({});
1524
+ react.default.useEffect(() => () => clearDiffDock(sessionId, dockSourceRef.current), [sessionId]);
1525
+ react.default.useLayoutEffect(() => {
1526
+ const root = viewRootRef.current;
1527
+ const scroll = root?.closest?.("[data-conversation-scroll]");
1528
+ if (!root || !scroll) return;
1529
+ let seat = null;
1530
+ let frame = null;
1531
+ let resizeObserver = null;
1532
+ const update = () => {
1533
+ frame = null;
1534
+ const rootRect = root.getBoundingClientRect();
1535
+ const scrollRect = scroll.getBoundingClientRect();
1536
+ const seatRect = seat?.getBoundingClientRect?.();
1537
+ const height = editorHeight(rootRect.top, scrollRect.bottom, seatRect?.top);
1538
+ root.style.setProperty("--edrv-editor-height", height + "px");
1539
+ };
1540
+ const scheduleUpdate = () => {
1541
+ if (frame !== null) return;
1542
+ frame = requestAnimationFrame(update);
1543
+ };
1544
+ const attachSeat = () => {
1545
+ const next = scroll.querySelector?.("[data-composer-seat]");
1546
+ if (next === seat) {
1547
+ scheduleUpdate();
1548
+ return;
1549
+ }
1550
+ resizeObserver?.disconnect();
1551
+ seat = next;
1552
+ resizeObserver = null;
1553
+ if (seat && typeof ResizeObserver === "function") {
1554
+ resizeObserver = new ResizeObserver(scheduleUpdate);
1555
+ resizeObserver.observe(seat);
1556
+ }
1557
+ scheduleUpdate();
1558
+ };
1559
+ attachSeat();
1560
+ const mutationObserver = typeof MutationObserver === "function" ? new MutationObserver(attachSeat) : null;
1561
+ mutationObserver?.observe(scroll, { childList: true });
1562
+ const scrollObserver = typeof ResizeObserver === "function" ? new ResizeObserver(scheduleUpdate) : null;
1563
+ scrollObserver?.observe(scroll);
1564
+ window.addEventListener("resize", scheduleUpdate);
1565
+ scheduleUpdate();
1566
+ return () => {
1567
+ if (frame !== null) cancelAnimationFrame(frame);
1568
+ resizeObserver?.disconnect();
1569
+ scrollObserver?.disconnect();
1570
+ mutationObserver?.disconnect();
1571
+ window.removeEventListener("resize", scheduleUpdate);
1572
+ root.style.removeProperty("--edrv-editor-height");
1573
+ };
1574
+ }, [sessionId]);
1453
1575
  react.default.useEffect(() => {
1454
1576
  if (bootRef.current || !sessionId) return;
1455
1577
  bootRef.current = true;
@@ -1477,9 +1599,53 @@ window.__ModuleLoader__.load({
1477
1599
  active,
1478
1600
  sessionId
1479
1601
  ]);
1602
+ react.default.useEffect(() => {
1603
+ if (!sessionId) return;
1604
+ try {
1605
+ const raw = localStorage.getItem("edrv.sidebar." + String(sessionId));
1606
+ if (raw) {
1607
+ const saved = JSON.parse(raw);
1608
+ if (typeof saved.on === "boolean") setSidebarOn(saved.on);
1609
+ if (typeof saved.width === "number") setSidebarW(Math.max(180, Math.min(560, saved.width)));
1610
+ if (typeof saved.panel === "string") setActivePanel(saved.panel);
1611
+ }
1612
+ } catch (e) {}
1613
+ }, [sessionId]);
1614
+ react.default.useEffect(() => {
1615
+ if (!sessionId) return;
1616
+ try {
1617
+ localStorage.setItem("edrv.sidebar." + String(sessionId), JSON.stringify({
1618
+ on: sidebarOn,
1619
+ width: sidebarW,
1620
+ panel: activePanel
1621
+ }));
1622
+ } catch (e) {}
1623
+ }, [
1624
+ sidebarOn,
1625
+ sidebarW,
1626
+ activePanel,
1627
+ sessionId
1628
+ ]);
1629
+ react.default.useEffect(() => {
1630
+ const onKey = (e) => {
1631
+ if ((e.ctrlKey || e.metaKey) && String(e.key).toLowerCase() === "b") {
1632
+ e.preventDefault();
1633
+ e.stopPropagation();
1634
+ setSidebarOn((v) => !v);
1635
+ }
1636
+ };
1637
+ window.addEventListener("keydown", onKey, true);
1638
+ return () => window.removeEventListener("keydown", onKey, true);
1639
+ }, []);
1640
+ const pendingByPath = react.default.useMemo(() => {
1641
+ const out = {};
1642
+ for (const f of sum.files) if (f.pending > 0) out[f.path] = f.pending;
1643
+ return out;
1644
+ }, [sum]);
1480
1645
  react.default.useEffect(() => {
1481
1646
  if (!active) return;
1482
1647
  setContent(null);
1648
+ setContentPath(null);
1483
1649
  setLoadError(null);
1484
1650
  setLoadStage({
1485
1651
  progress: 10,
@@ -1489,8 +1655,16 @@ window.__ModuleLoader__.load({
1489
1655
  loadContent(active, sessionId);
1490
1656
  }, [active, sessionId]);
1491
1657
  react.default.useEffect(() => {
1492
- if (monaco || monacoErr) return;
1493
- let alive = true;
1658
+ if (!tabMenu) return;
1659
+ const onKey = (e) => {
1660
+ if (e.key === "Escape") dismissMenus();
1661
+ };
1662
+ window.addEventListener("keydown", onKey, true);
1663
+ return () => window.removeEventListener("keydown", onKey, true);
1664
+ }, [tabMenu]);
1665
+ react.default.useEffect(() => {
1666
+ if (monaco || monacoErr) return;
1667
+ let alive = true;
1494
1668
  const onProgress = (stage) => {
1495
1669
  if (!alive) return;
1496
1670
  const progress = stage.phase === "ready" ? 70 : Math.round(10 + stage.progress * .6);
@@ -1544,6 +1718,7 @@ window.__ModuleLoader__.load({
1544
1718
  if (res && res.ok) {
1545
1719
  setStatus("已保存 " + (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8));
1546
1720
  setContent(text);
1721
+ setContentPath(active);
1547
1722
  setDirtyMap((d) => Object.assign({}, d, { [active]: false }));
1548
1723
  refreshRecords();
1549
1724
  emitRefresh();
@@ -1648,6 +1823,46 @@ window.__ModuleLoader__.load({
1648
1823
  ed.onDidChangeCursorPosition((e) => {
1649
1824
  setCursor("Ln " + e.position.lineNumber + ", Col " + e.position.column);
1650
1825
  });
1826
+ const menuHandlers = () => menuHandlersRef.current;
1827
+ const pathOf = (edx) => {
1828
+ const uriPath = (edx?.getModel?.())?.uri?.path;
1829
+ return uriPath ? decodeURIComponent(String(uriPath).replace(/^\//, "")) : null;
1830
+ };
1831
+ const selectionOf = (edx) => {
1832
+ const s = edx?.getSelection?.();
1833
+ if (!s) return null;
1834
+ if (s.startLineNumber === s.endLineNumber && s.startColumn === s.endColumn) return null;
1835
+ return {
1836
+ startLine: s.startLineNumber,
1837
+ endLine: s.endLineNumber
1838
+ };
1839
+ };
1840
+ const selKey = ed.createContextKey("edrvSelection", false);
1841
+ ed.onDidChangeCursorSelection((e) => {
1842
+ const s = e?.selection;
1843
+ selKey.set(!!(s && (s.startLineNumber !== s.endLineNumber || s.startColumn !== s.endColumn)));
1844
+ });
1845
+ ed.addAction({
1846
+ id: "edrv.addFileRef",
1847
+ label: "添加文件到对话",
1848
+ contextMenuGroupId: "1_edrv",
1849
+ precondition: "editorTextFocus",
1850
+ run: (edx) => menuHandlers()?.addRefToChat(pathOf(edx))
1851
+ });
1852
+ ed.addAction({
1853
+ id: "edrv.addSelectionRef",
1854
+ label: "添加选中内容为引用",
1855
+ contextMenuGroupId: "1_edrv",
1856
+ precondition: "edrvSelection",
1857
+ run: (edx) => {
1858
+ const s = selectionOf(edx);
1859
+ const p = pathOf(edx);
1860
+ if (s && p) menuHandlers()?.addRefToChat(p, {
1861
+ startLine: s.startLine,
1862
+ endLine: s.endLine
1863
+ });
1864
+ }
1865
+ });
1651
1866
  const hideSoon = () => {
1652
1867
  if (hoverEditorRef.current || hoverPanelRef.current || hideTimerRef.current) return;
1653
1868
  hideTimerRef.current = setTimeout(() => {
@@ -1710,7 +1925,8 @@ window.__ModuleLoader__.load({
1710
1925
  }, [
1711
1926
  active,
1712
1927
  content,
1713
- pendingRegions
1928
+ pendingRegions,
1929
+ focusRequest
1714
1930
  ]);
1715
1931
  const jumpTo = (region) => {
1716
1932
  if (editorRef.current) {
@@ -1747,10 +1963,15 @@ window.__ModuleLoader__.load({
1747
1963
  setFileIdx(next);
1748
1964
  openFile(sum.pendingFiles[next].path, true);
1749
1965
  };
1750
- const reloadFile = () => {
1966
+ const openNextFile = () => {
1967
+ if (!sum.pendingFiles.length) return;
1968
+ if (sum.pendingFiles.some((file) => file.path === active)) gotoFile(1);
1969
+ else openFile(sum.pendingFiles[0].path, true);
1970
+ };
1971
+ const reloadFile = (skipStale) => {
1751
1972
  if (!active) return;
1752
1973
  loadContent(active, sessionId);
1753
- refreshRecords();
1974
+ refreshRecords(skipStale === true);
1754
1975
  };
1755
1976
  /**
1756
1977
  * 关闭当前差异浮窗并清理 hover 锚点。
@@ -1798,11 +2019,77 @@ window.__ModuleLoader__.load({
1798
2019
  return false;
1799
2020
  });
1800
2021
  };
2022
+ /**
2023
+ * 批量采纳/不采纳:一次 RPC 处理多个差异(Keep 整文件 / Keep All / Undo All)。
2024
+ * 单次 setRecords 合并全部结果,避免逐条往返读写整个 sidecar。
2025
+ * @author ddj 2026年08月25号
2026
+ * @param items 决策项数组(callId/scope/hunkIndex/decision)
2027
+ * @returns Promise<{ok:number; fail:number}> 成功/失败计数
2028
+ */
2029
+ const actMany = (items) => {
2030
+ if (!items.length) return Promise.resolve({
2031
+ ok: 0,
2032
+ fail: 0
2033
+ });
2034
+ return rpc("edrv.decideBatch", {
2035
+ sessionId,
2036
+ items
2037
+ }).then((res) => {
2038
+ if (!res || !res.ok || !Array.isArray(res.results)) {
2039
+ setError(res?.error ? String(res.error) : "批量处理失败");
2040
+ return {
2041
+ ok: 0,
2042
+ fail: items.length
2043
+ };
2044
+ }
2045
+ let ok = 0;
2046
+ let fail = 0;
2047
+ const next = {};
2048
+ for (const item of res.results) if (item && item.ok) {
2049
+ ok++;
2050
+ if (item.record) next[item.callId] = item.record;
2051
+ } else fail++;
2052
+ setRecords((prev) => Object.assign({}, prev, next));
2053
+ return {
2054
+ ok,
2055
+ fail
2056
+ };
2057
+ }).catch((e) => {
2058
+ setError("批量处理异常:" + String(e));
2059
+ return {
2060
+ ok: 0,
2061
+ fail: items.length
2062
+ };
2063
+ });
2064
+ };
2065
+ /**
2066
+ * 决策项构造(与单条 actHunk 的 scope 语义一致:create 记录走 call 作用域)。
2067
+ * @author ddj 2026年08月25号
2068
+ * @param r 差异区域/待处理项(callId/idx/create)
2069
+ * @param reject true=不采纳
2070
+ * @returns decideBatch items 元素
2071
+ */
2072
+ const itemOf = (r, reject) => ({
2073
+ callId: r.callId,
2074
+ scope: r.create ? "call" : "hunk",
2075
+ hunkIndex: r.idx,
2076
+ decision: reject ? "rejected" : "accepted"
2077
+ });
1801
2078
  const acceptFile = () => {
1802
- for (const r of pendingRegions) actHunk(r, false);
2079
+ actMany(pendingRegions.map((r) => itemOf(r, false))).then(({ ok, fail }) => {
2080
+ reloadFile(true);
2081
+ emitRefresh();
2082
+ setStatus("已采纳 " + ok + " 处差异" + (fail ? "," + fail + " 处失败" : ""));
2083
+ if (fail) setError(fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
2084
+ });
1803
2085
  };
1804
2086
  const undoFile = () => {
1805
- for (const r of [...pendingRegions].reverse()) actHunk(r, true);
2087
+ actMany([...pendingRegions].reverse().map((r) => itemOf(r, true))).then(({ ok, fail }) => {
2088
+ reloadFile(true);
2089
+ emitRefresh();
2090
+ setStatus("已不采纳 " + ok + " 处差异" + (fail ? "," + fail + " 处失败" : ""));
2091
+ if (fail) setError(fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
2092
+ });
1806
2093
  };
1807
2094
  const allPending = react.default.useMemo(() => {
1808
2095
  const out = [];
@@ -1820,7 +2107,7 @@ window.__ModuleLoader__.load({
1820
2107
  return out;
1821
2108
  }, [records]);
1822
2109
  /**
1823
- * 批量处理所有差异文件:采纳全部 / 不采纳全部。不采纳按 at 降序(新改先回滚)串行执行。
2110
+ * 批量处理所有差异文件:采纳全部 / 不采纳全部。不采纳按 at 降序(新改先回滚)排序后一次 RPC。
1824
2111
  * @author ddj 2026年08月20号
1825
2112
  * @param reject true=全部不采纳(回滚),false=全部采纳
1826
2113
  */
@@ -1828,17 +2115,15 @@ window.__ModuleLoader__.load({
1828
2115
  if (batchBusyRef.current || !allPending.length) return;
1829
2116
  batchBusyRef.current = true;
1830
2117
  const list = reject ? [...allPending].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : b.idx - a.idx) : allPending;
1831
- let ok = 0, fail = 0;
1832
- const run = async () => {
1833
- for (const r of list) if (await actHunk(r, reject, true)) ok++;
1834
- else fail++;
2118
+ actMany(list.map((r) => itemOf(r, reject))).then(({ ok, fail }) => {
1835
2119
  batchBusyRef.current = false;
1836
- reloadFile();
2120
+ reloadFile(true);
1837
2121
  emitRefresh();
1838
2122
  setStatus((reject ? "已不采纳 " : "已采纳 ") + ok + " 处差异" + (fail ? "," + fail + " 处失败" : ""));
1839
2123
  if (fail) setError(fail + " 处差异处理失败(可能已被后续修改影响),可刷新后重试");
1840
- };
1841
- run();
2124
+ }).catch(() => {
2125
+ batchBusyRef.current = false;
2126
+ });
1842
2127
  };
1843
2128
  const acceptAllFiles = () => actAllPending(false);
1844
2129
  const undoAllFiles = () => actAllPending(true);
@@ -1856,6 +2141,33 @@ window.__ModuleLoader__.load({
1856
2141
  } else setError(res?.error ? String(res.error) : "回滚失败");
1857
2142
  }).catch((e) => setError("回滚异常:" + String(e)));
1858
2143
  };
2144
+ /**
2145
+ * 关闭 Tab 右键菜单。
2146
+ * @author ddj 2026年08月25号
2147
+ */
2148
+ const dismissMenus = () => {
2149
+ setTabMenu(null);
2150
+ };
2151
+ /** 把「添加到对话」返回状态映射为状态栏文案。
2152
+ * @author ddj 2026年08月25号 */
2153
+ const statusOfAdd = (outcome, okText) => {
2154
+ if (outcome === "ok") return okText;
2155
+ if (outcome === "busy") return okText + "(输入框忙,已降级纯文本)";
2156
+ return "无法添加到对话(无会话或输入框不可用)";
2157
+ };
2158
+ /** 添加文件/选中区引用到对话(异步,状态栏反馈)。 */
2159
+ const addRefToChat = (path, range) => {
2160
+ if (!path) {
2161
+ setStatus("无活动文件");
2162
+ return;
2163
+ }
2164
+ if (!addToConversation) {
2165
+ setStatus("添加到对话不可用");
2166
+ return;
2167
+ }
2168
+ addToConversation.appendReference(sessionId, path, range).then((o) => setStatus(statusOfAdd(o, "已添加文件引用")));
2169
+ };
2170
+ menuHandlersRef.current = { addRefToChat };
1859
2171
  const openFile = (path, focusDiff) => {
1860
2172
  if (!path) return;
1861
2173
  addTab(path, true);
@@ -1902,6 +2214,15 @@ window.__ModuleLoader__.load({
1902
2214
  flushSave();
1903
2215
  setActive(t.path);
1904
2216
  }
2217
+ },
2218
+ onContextMenu: (e) => {
2219
+ e.preventDefault();
2220
+ e.stopPropagation();
2221
+ setTabMenu({
2222
+ x: e.clientX,
2223
+ y: e.clientY,
2224
+ path: t.path
2225
+ });
1905
2226
  }
1906
2227
  }, react.default.createElement("span", { style: {
1907
2228
  overflow: "hidden",
@@ -1931,25 +2252,28 @@ window.__ModuleLoader__.load({
1931
2252
  const pathBar = react.default.createElement("div", {
1932
2253
  className: "edrv-pathbar",
1933
2254
  title: active || ""
1934
- }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (Ctrl+P) 打开文件"));
2255
+ }, react.default.createElement("span", { className: "edrv-pb-name" }, active ? String(active).split(/[\\/]/).pop() : "未打开文件"), react.default.createElement("span", { className: "edrv-pb-full" }, active || "使用右上搜索框 (Ctrl+P) 打开文件"), active && langOf(active) ? react.default.createElement("span", { className: "edrv-pb-meta" }, langOf(active)) : null, cursor ? react.default.createElement("span", { className: "edrv-pb-meta" }, cursor) : null, status ? react.default.createElement("span", { className: "edrv-pb-meta edrv-pb-status" }, status) : null);
1935
2256
  const tabRow = react.default.createElement("div", { style: {
1936
2257
  display: "flex",
1937
2258
  alignItems: "center",
1938
2259
  background: "var(--dsw-alias-bg-layer-1,transparent)",
1939
2260
  flexShrink: 0
1940
- } }, tabsEl, react.default.createElement(QuickOpen, {
1941
- sessionId,
1942
- onOpen: (p) => openFile(p, false)
1943
- }));
1944
- const statusBar = react.default.createElement("div", { className: "edrv-statusbar" }, react.default.createElement("span", null, "编辑"), react.default.createElement("span", null, active ? langOf(active) : ""), react.default.createElement("span", null, cursor), status ? react.default.createElement("span", null, status) : null, react.default.createElement("span", { style: { flex: 1 } }), sum.totalFiles > 0 ? react.default.createElement("button", {
2261
+ } }, tabsEl, sum.totalFiles > 0 ? react.default.createElement("button", {
1945
2262
  className: "edrv-diffchip",
1946
2263
  title: "打开差异总览/归档",
1947
2264
  onClick: () => setLauncherOpen((o) => !o)
1948
2265
  }, "⚠ 差异 " + sum.totalFiles + " 文件") : null, react.default.createElement("button", {
2266
+ className: "edrv-chip-btn" + (sidebarOn ? " edrv-chip-on" : ""),
2267
+ title: "切换侧边栏 (Ctrl+B)",
2268
+ onClick: () => setSidebarOn((v) => !v)
2269
+ }, "☰"), react.default.createElement("button", {
1949
2270
  className: "edrv-chip-btn",
1950
2271
  title: "刷新",
1951
2272
  onClick: reloadFile
1952
- }, "⟳"));
2273
+ }, "⟳"), react.default.createElement(QuickOpen, {
2274
+ sessionId,
2275
+ onOpen: (p) => openFile(p, false)
2276
+ }));
1953
2277
  const otherFiles = sum.pendingFiles.filter((f) => f.path !== active);
1954
2278
  /**
1955
2279
  * 渲染编辑器/文件加载进度面板。
@@ -1992,6 +2316,7 @@ window.__ModuleLoader__.load({
1992
2316
  else if (content === null && loadError) body = loadingBody("文件加载失败:" + loadError, 0, () => {
1993
2317
  setLoadError(null);
1994
2318
  setContent(null);
2319
+ setContentPath(null);
1995
2320
  setLoadStage({
1996
2321
  progress: 10,
1997
2322
  message: "重新读取文件…"
@@ -2044,6 +2369,40 @@ window.__ModuleLoader__.load({
2044
2369
  actHunk(hoverAct.region, true);
2045
2370
  }
2046
2371
  }, "↩ Undo")) : null;
2372
+ react.default.useEffect(() => {
2373
+ if (!sessionId) return;
2374
+ const mode = editorDockMode(active);
2375
+ publishDiffDock(sessionId, {
2376
+ mode,
2377
+ pendingRegions,
2378
+ staleRegions,
2379
+ onAct: actHunk,
2380
+ onAcceptFile: acceptFile,
2381
+ onUndoFile: undoFile,
2382
+ onAcceptAllFiles: acceptAllFiles,
2383
+ onUndoAllFiles: undoAllFiles,
2384
+ allPendingCount: allPending.length,
2385
+ onRollback: rollbackFile,
2386
+ onJump: jumpTo,
2387
+ otherFiles,
2388
+ onOpenOther: (path) => openFile(path, true),
2389
+ onOpenLauncher: (tab) => {
2390
+ setLauncherTab(tab || "pending");
2391
+ setLauncherOpen(true);
2392
+ },
2393
+ onRefresh: reloadFile,
2394
+ activePath: active,
2395
+ diffIdx: contentReady ? diffIdx : 0,
2396
+ diffTotal: displayDiffTotal(contentReady, pendingRegions.length, sum.files.find((file) => file.path === active)?.pending ?? 0),
2397
+ fileIdx,
2398
+ fileTotal: sum.pendingFiles.length,
2399
+ onPrevDiff: () => gotoDiff(-1),
2400
+ onNextDiff: () => gotoDiff(1),
2401
+ onPrevFile: () => gotoFile(-1),
2402
+ onNextFile: () => gotoFile(1),
2403
+ onOpenNextFile: openNextFile
2404
+ }, dockSourceRef.current);
2405
+ });
2047
2406
  const overlay = launcherOpen ? react.default.createElement(react.default.Fragment, null, react.default.createElement("div", {
2048
2407
  style: {
2049
2408
  position: "fixed",
@@ -2060,63 +2419,89 @@ window.__ModuleLoader__.load({
2060
2419
  openFile(p, true);
2061
2420
  setLauncherOpen(false);
2062
2421
  }
2063
- })) : pendingRegions.length > 0 ? react.default.createElement(DiffBox, {
2064
- pendingRegions,
2065
- staleRegions,
2066
- onAct: actHunk,
2067
- onAcceptFile: acceptFile,
2068
- onUndoFile: undoFile,
2069
- onAcceptAllFiles: acceptAllFiles,
2070
- onUndoAllFiles: undoAllFiles,
2071
- allPendingCount: allPending.length,
2072
- onRollback: rollbackFile,
2073
- onJump: jumpTo,
2074
- otherFiles,
2075
- onOpenOther: (p) => openFile(p, true),
2076
- onOpenLauncher: (tab) => {
2077
- setLauncherTab(tab || "pending");
2078
- setLauncherOpen(true);
2422
+ })) : null;
2423
+ /**
2424
+ * 计算右键菜单固定定位(clamp 防越出视口)。
2425
+ * @author ddj 2026年08月25号
2426
+ */
2427
+ const menuPos = (x, y) => ({
2428
+ left: Math.max(4, Math.min(x, (window.innerWidth || 800) - 224)),
2429
+ top: Math.max(4, Math.min(y, (window.innerHeight || 600) - 176))
2430
+ });
2431
+ const menuBackdrop = tabMenu ? react.default.createElement("div", {
2432
+ style: {
2433
+ position: "fixed",
2434
+ inset: 0,
2435
+ zIndex: 70
2079
2436
  },
2080
- onRefresh: reloadFile,
2437
+ onClick: dismissMenus,
2438
+ onContextMenu: (e) => {
2439
+ e.preventDefault();
2440
+ dismissMenus();
2441
+ }
2442
+ }) : null;
2443
+ const tabMenuEl = tabMenu ? react.default.createElement("div", {
2444
+ className: "edrv-ctxmenu",
2445
+ style: menuPos(tabMenu.x, tabMenu.y)
2446
+ }, react.default.createElement("button", {
2447
+ className: "edrv-ctxmenu-item",
2448
+ onClick: () => {
2449
+ addRefToChat(tabMenu.path);
2450
+ dismissMenus();
2451
+ }
2452
+ }, "添加文件到对话"), react.default.createElement("div", { className: "edrv-ctxmenu-sep" }), react.default.createElement("button", {
2453
+ className: "edrv-ctxmenu-item edrv-ctxmenu-danger",
2454
+ onClick: () => {
2455
+ closeTab(tabMenu.path);
2456
+ dismissMenus();
2457
+ }
2458
+ }, "关闭标签页")) : null;
2459
+ const sidebarPanels = props.sidebarPanels;
2460
+ const sidebarCtx = {
2461
+ sessionId,
2462
+ openFile: (p) => openFile(p, false),
2081
2463
  activePath: active,
2082
- diffIdx,
2083
- diffTotal: pendingRegions.length,
2084
- fileIdx,
2085
- fileTotal: sum.pendingFiles.length,
2086
- onPrevDiff: () => gotoDiff(-1),
2087
- onNextDiff: () => gotoDiff(1),
2088
- onPrevFile: () => gotoFile(-1),
2089
- onNextFile: () => gotoFile(1)
2090
- }) : sum.pendingFiles.length > 0 ? react.default.createElement(DiffBarEmpty, {
2091
- sum,
2092
- active,
2093
- staleCount: staleRegions.length,
2094
- onNextFile: () => {
2095
- if (sum.pendingFiles.length) openFile(sum.pendingFiles[0].path, true);
2096
- },
2097
- onOpenLauncher: (tab) => {
2098
- setLauncherTab(tab || "pending");
2099
- setLauncherOpen(true);
2464
+ pendingByPath,
2465
+ sum: {
2466
+ totalFiles: sum.totalFiles,
2467
+ files: sum.files
2100
2468
  },
2101
- onRefresh: reloadFile
2102
- }) : null;
2469
+ refreshRecords: () => refreshRecords()
2470
+ };
2471
+ const editorArea = react.default.createElement("div", { className: "edrv-editor-area" }, body, hoverEl, overlay);
2472
+ const mainCol = react.default.createElement("div", {
2473
+ className: "edrv-main-col",
2474
+ style: {
2475
+ flex: 1,
2476
+ minWidth: 0,
2477
+ minHeight: 0,
2478
+ display: "flex",
2479
+ flexDirection: "column",
2480
+ overflow: "hidden"
2481
+ }
2482
+ }, pathBar, tabRow, editorArea);
2483
+ const editorRow = react.default.createElement("div", { className: "edrv-editor-row" }, sidebarOn && sidebarPanels ? react.default.createElement(SidebarView, {
2484
+ registry: sidebarPanels,
2485
+ ctx: sidebarCtx,
2486
+ activePanel,
2487
+ onActive: setActivePanel,
2488
+ width: sidebarW,
2489
+ onWidth: setSidebarW,
2490
+ onHide: () => setSidebarOn(false)
2491
+ }) : null, mainCol);
2103
2492
  return react.default.createElement("div", {
2493
+ ref: viewRootRef,
2104
2494
  "data-edrv-view": "1",
2105
2495
  style: {
2106
- height: "100%",
2496
+ height: "var(--edrv-editor-height, 100%)",
2497
+ maxHeight: "var(--edrv-editor-height, 100%)",
2107
2498
  minHeight: 0,
2108
2499
  display: "flex",
2109
2500
  flexDirection: "column",
2110
2501
  background: "var(--dsw-alias-bg-base,transparent)",
2111
2502
  overflow: "hidden"
2112
2503
  }
2113
- }, pathBar, tabRow, react.default.createElement("div", { style: {
2114
- position: "relative",
2115
- flex: 1,
2116
- minHeight: 0,
2117
- display: "flex",
2118
- flexDirection: "column"
2119
- } }, body, hoverEl, overlay), statusBar);
2504
+ }, editorRow, menuBackdrop, tabMenuEl);
2120
2505
  }
2121
2506
  //#endregion
2122
2507
  //#region src/client/ui/DiffBadge.ts
@@ -2199,6 +2584,230 @@ window.__ModuleLoader__.load({
2199
2584
  } }, String(n)));
2200
2585
  }
2201
2586
  //#endregion
2587
+ //#region src/client/ui/DiffBox.ts
2588
+ /**
2589
+ * dsh-vscode-mode client — DiffBox:每个文件底部紧凑差异条(per-file hunk + ⋮ 二级菜单)。
2590
+ * 迁移自原 src/client/index.ts 的 DiffBox,语义不改。
2591
+ * 作者 ddj 2026-08-20
2592
+ */
2593
+ /**
2594
+ * 折叠控制使用任务栏同尺寸的 chevron 轮廓,避免依赖未声明的运行时图标包。
2595
+ * @author ddj 2026年08月25号
2596
+ * @param open 是否已展开
2597
+ * @returns 14×14 的装饰性 chevron SVG
2598
+ */
2599
+ function chevronOf(open) {
2600
+ const path = open ? "M3.25 5.25 7 9l3.75-3.75" : "M3.25 8.75 7 5l3.75 3.75";
2601
+ return react.default.createElement("svg", {
2602
+ width: 14,
2603
+ height: 14,
2604
+ viewBox: "0 0 14 14",
2605
+ fill: "none",
2606
+ "aria-hidden": true,
2607
+ focusable: false
2608
+ }, react.default.createElement("path", {
2609
+ d: path,
2610
+ stroke: "currentColor",
2611
+ strokeWidth: 1.2,
2612
+ strokeLinecap: "round",
2613
+ strokeLinejoin: "round"
2614
+ }));
2615
+ }
2616
+ /**
2617
+ * 差异条:当前文件内 diff 导航(↑↓←→)、Keep/Undo、展开 hunk 列表、⋮ 二级菜单
2618
+ * (Keep All / Undo All / 差异总览 / 归档 / 回滚 / 刷新)+ 其他差异文件小节。
2619
+ */
2620
+ function DiffBox(props) {
2621
+ const { pendingRegions = [], staleRegions = [], onAct, onAcceptFile, onUndoFile, onAcceptAllFiles, onUndoAllFiles, allPendingCount = 0, onRollback, onJump, otherFiles = [], onOpenOther, onOpenLauncher, onRefresh, activePath, diffIdx = 0, diffTotal = 0, fileIdx = 0, fileTotal = 0, onPrevDiff, onNextDiff, onPrevFile, onNextFile, mode = "editor", onOpenNextFile, dock = false } = props;
2622
+ const [expanded, setExpanded] = react.default.useState(false);
2623
+ const [detailsOpen, setDetailsOpen] = react.default.useState(false);
2624
+ const detailsId = react.default.useId();
2625
+ const canAct = pendingRegions.length > 0;
2626
+ const base = String(activePath || "").split(/[\\/]/).pop() || "";
2627
+ if (mode === "chat" || mode === "editor-empty") return react.default.createElement("div", {
2628
+ className: "edrv-diffbar edrv-diffbar-dock",
2629
+ "data-edrv-diffbox": "1",
2630
+ "data-edrv-diff-mode": mode
2631
+ }, react.default.createElement("div", { className: "edrv-diffbar-row edrv-diffbar-dock-row" }, react.default.createElement("button", {
2632
+ type: "button",
2633
+ className: "edrv-diffbar-dock-button",
2634
+ title: "切换到下一个差异文件",
2635
+ "aria-label": diffDockText(fileTotal),
2636
+ onClick: onOpenNextFile
2637
+ }, diffDockText(fileTotal))));
2638
+ let bodyEl = null;
2639
+ if (expanded) {
2640
+ const rows = pendingRegions.map((r) => {
2641
+ const key = callIdAttr(r.callId, r.idx);
2642
+ return react.default.createElement("div", {
2643
+ key,
2644
+ className: "edrv-diffrow",
2645
+ "data-edrv-hunk": key,
2646
+ title: "跳转到 L" + (r.start ?? "?"),
2647
+ onClick: () => onJump(r)
2648
+ }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + "-" + (r.end ?? "?")), r.oldLines.length ? react.default.createElement("span", { className: "edrv-diffrow-o" }, "-" + r.oldLines.length) : null, r.newLines.length ? react.default.createElement("span", { className: "edrv-diffrow-n" }, "+" + r.newLines.length) : null, r.create ? react.default.createElement("span", { className: "edrv-diffrow-tag" }, "新建") : null, react.default.createElement("span", { style: { flex: 1 } }));
2649
+ });
2650
+ const stale = staleRegions.map((r) => react.default.createElement("div", {
2651
+ key: "stale" + callIdAttr(r.callId, r.idx),
2652
+ className: "edrv-diffrow edrv-diffrow-stale",
2653
+ title: "差异无法定位(文件可能已被手动修改)"
2654
+ }, react.default.createElement("span", { className: "edrv-diffrow-l" }, "L" + (r.start ?? "?") + " 无法定位"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, "冲突/需刷新"), react.default.createElement("span", { className: "edrv-diffrow-tag" }, badgeOf(r.status))));
2655
+ let othersEl = null;
2656
+ if (otherFiles.length) othersEl = react.default.createElement("div", { className: "edrv-diffbox-others" }, react.default.createElement("span", { className: "edrv-diffrow-tag" }, "其他差异文件 (" + otherFiles.length + "):"), otherFiles.map((f) => react.default.createElement("button", {
2657
+ key: f.path,
2658
+ className: "edrv-diffrow-file",
2659
+ title: f.path,
2660
+ onClick: () => onOpenOther(f.path)
2661
+ }, String(f.path).split(/[\\/]/).pop() + " (" + f.pending + ")")));
2662
+ bodyEl = react.default.createElement("div", { className: "edrv-diffbar-body" }, ...rows, ...stale, othersEl);
2663
+ }
2664
+ const details = detailsOpen ? react.default.createElement("div", {
2665
+ className: "edrv-diff-details",
2666
+ id: detailsId
2667
+ }, react.default.createElement("button", {
2668
+ className: "edrv-diffmenu-item",
2669
+ disabled: !allPendingCount,
2670
+ onClick: onAcceptAllFiles
2671
+ }, "✓ Keep All(全部文件采纳)"), react.default.createElement("button", {
2672
+ className: "edrv-diffmenu-item danger",
2673
+ disabled: !allPendingCount,
2674
+ onClick: onUndoAllFiles
2675
+ }, "↩ Undo All(全部文件不采纳)"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
2676
+ className: "edrv-diffmenu-item",
2677
+ onClick: () => onOpenLauncher("pending")
2678
+ }, "🗂 差异总览"), react.default.createElement("button", {
2679
+ className: "edrv-diffmenu-item",
2680
+ onClick: () => onOpenLauncher("archive")
2681
+ }, "📁 归档"), react.default.createElement("div", { className: "edrv-diffmenu-sep" }), react.default.createElement("button", {
2682
+ className: "edrv-diffmenu-item",
2683
+ disabled: !canAct,
2684
+ onClick: onRollback
2685
+ }, "⟲ 回滚文件"), react.default.createElement("button", {
2686
+ className: "edrv-diffmenu-item",
2687
+ onClick: onRefresh
2688
+ }, "⟳ 刷新")) : null;
2689
+ return react.default.createElement("div", {
2690
+ className: "edrv-diffbar edrv-diffbar-dock",
2691
+ "data-edrv-diffbox": "1",
2692
+ "data-edrv-diff-mode": "editor"
2693
+ }, react.default.createElement("div", { className: "edrv-diffbar-row edrv-diffbar-editor-row" }, react.default.createElement("div", { className: "edrv-diffbar-editor-content" }, react.default.createElement("div", { className: "edrv-diffbar-nav" }, react.default.createElement("button", {
2694
+ className: "edrv-pill edrv-pill-ghost",
2695
+ title: "上一个差异",
2696
+ disabled: !canAct,
2697
+ onClick: onPrevDiff
2698
+ }, "↑"), react.default.createElement("span", {
2699
+ className: "edrv-diffbar-count edrv-diffbar-progress",
2700
+ title: "当前文件内差异位置(点击展开/收起差异列表)",
2701
+ onClick: () => setExpanded((v) => !v)
2702
+ }, diffTotal ? diffIdx + 1 + "/" + diffTotal : "0/0"), react.default.createElement("button", {
2703
+ className: "edrv-pill edrv-pill-ghost",
2704
+ title: "下一个差异",
2705
+ disabled: !canAct,
2706
+ onClick: onNextDiff
2707
+ }, "↓")), react.default.createElement("div", { className: "edrv-diffbar-files" }, react.default.createElement("button", {
2708
+ className: "edrv-pill edrv-pill-ghost",
2709
+ title: "上一个差异文件",
2710
+ disabled: !fileTotal,
2711
+ onClick: onPrevFile
2712
+ }, "←"), react.default.createElement("span", {
2713
+ className: "edrv-diffbar-count edrv-count-file edrv-diffbar-progress",
2714
+ title: "差异文件位置"
2715
+ }, fileTotal ? fileIdx + 1 + "/" + fileTotal + " 文件" : "0/0 文件"), react.default.createElement("button", {
2716
+ className: "edrv-pill edrv-pill-ghost",
2717
+ title: "下一个差异文件",
2718
+ disabled: !fileTotal,
2719
+ onClick: onNextFile
2720
+ }, "→"), react.default.createElement("span", {
2721
+ className: "edrv-diffbar-file edrv-diffbar-summary",
2722
+ title: activePath || ""
2723
+ }, base)), react.default.createElement("div", { className: "edrv-diffbar-actions" }, react.default.createElement("button", {
2724
+ className: "edrv-pill edrv-pill-keep",
2725
+ title: "采纳当前文件的全部差异",
2726
+ disabled: !canAct,
2727
+ onClick: onAcceptFile
2728
+ }, "✓ Keep"), react.default.createElement("button", {
2729
+ className: "edrv-pill edrv-pill-undo",
2730
+ title: "不采纳当前文件的全部差异(回滚)",
2731
+ disabled: !canAct,
2732
+ onClick: onUndoFile
2733
+ }, "↩ Undo"), react.default.createElement("button", {
2734
+ className: "edrv-pill edrv-pill-ghost edrv-diff-details-toggle",
2735
+ title: detailsOpen ? "收起差异操作" : "展开差异操作",
2736
+ "aria-label": detailsOpen ? "收起差异操作" : "展开差异操作",
2737
+ "aria-expanded": detailsOpen,
2738
+ "aria-controls": detailsId,
2739
+ onClick: () => setDetailsOpen((value) => !value)
2740
+ }, chevronOf(detailsOpen))))), bodyEl, details);
2741
+ }
2742
+ //#endregion
2743
+ //#region src/client/ui/ConversationDiffDock.ts
2744
+ /**
2745
+ * dsh-vscode-mode client — 会话级差异 dock。
2746
+ * conversation.input.dock 是唯一实例:普通对话显示摘要,文件编辑页显示完整操作条。
2747
+ * 作者 ddj 2026-08-26
2748
+ */
2749
+ const nextIndexBySession = /* @__PURE__ */ new Map();
2750
+ /**
2751
+ * 对话输入框上方的唯一差异 dock。
2752
+ * @author ddj 2026年08月26号
2753
+ * @param props DSH 注入的会话属性
2754
+ * @returns 差异 dock React 元素或 null
2755
+ */
2756
+ function ConversationDiffDock(props) {
2757
+ const sessionId = props?.sessionId;
2758
+ const [summary, setSummary] = react.default.useState(null);
2759
+ const [, setStoreRevision] = react.default.useState(0);
2760
+ const seq = react.default.useRef(0);
2761
+ const load = react.default.useCallback(() => {
2762
+ if (!sessionId) return;
2763
+ const current = ++seq.current;
2764
+ rpc("edrv.list", { sessionId }).then((res) => {
2765
+ if (current !== seq.current || !res || !res.ok || !Array.isArray(res.records)) return;
2766
+ setSummary(summarize(res.records));
2767
+ }).catch(() => {});
2768
+ }, [sessionId]);
2769
+ react.default.useEffect(() => {
2770
+ load();
2771
+ const timer = setInterval(load, 5e3);
2772
+ const onRefresh = () => load();
2773
+ window.addEventListener("edrv:refresh", onRefresh);
2774
+ return () => {
2775
+ clearInterval(timer);
2776
+ window.removeEventListener("edrv:refresh", onRefresh);
2777
+ };
2778
+ }, [load]);
2779
+ react.default.useEffect(() => {
2780
+ if (!sessionId) return void 0;
2781
+ const sync = () => setStoreRevision((value) => value + 1);
2782
+ sync();
2783
+ return subscribeDiffDock(sessionId, sync);
2784
+ }, [sessionId]);
2785
+ const renderDock = (content, key) => react.default.createElement("div", {
2786
+ key,
2787
+ className: "edrv-diff-dock-shell",
2788
+ "data-edrv-diff-dock-shell": "1"
2789
+ }, content);
2790
+ const editorSnapshot = readDiffDock(sessionId);
2791
+ if (editorSnapshot) {
2792
+ if (editorSnapshot.mode === "editor-empty" && !editorSnapshot.fileTotal) return null;
2793
+ return renderDock(react.default.createElement(DiffBox, Object.assign({}, editorSnapshot, { dock: true })), sessionId);
2794
+ }
2795
+ if (!sessionId || !summary?.pendingFiles?.length) return null;
2796
+ const paths = summary.pendingFiles.map((file) => file.path);
2797
+ const click = () => {
2798
+ const current = nextIndexBySession.get(sessionId) ?? 0;
2799
+ const next = nextDiffPath(paths, current);
2800
+ nextIndexBySession.set(sessionId, next.index);
2801
+ if (next.path) openDiffView(next.path);
2802
+ };
2803
+ return renderDock(react.default.createElement(DiffBox, {
2804
+ mode: "chat",
2805
+ dock: true,
2806
+ fileTotal: paths.length,
2807
+ onOpenNextFile: click
2808
+ }), sessionId || "chat");
2809
+ }
2810
+ //#endregion
2202
2811
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
2203
2812
  const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* 子 Tab:我的 MCP / MCP 市场 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:10px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0 0 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n";
2204
2813
  const tagId = "dsh-vscode-mode/mcp.css";
@@ -2919,10 +3528,349 @@ window.__ModuleLoader__.load({
2919
3528
  return typeof value === "string" && value.trim() ? value.trim() : AUTO_OPEN_TOOL;
2920
3529
  }
2921
3530
  //#endregion
3531
+ //#region src/client/addToConversation.ts
3532
+ /** DSH reference source 名(dsh-client-ui-reference 注册的 @file/@session 统一源)。 */
3533
+ const REF_SOURCE = "reference";
3534
+ /**
3535
+ * 生成 DSH @file 语法引用串:cwd 相对化、\ → /、含空白时按 @"path" 语法加引号。
3536
+ * cwd 外/无法相对化的路径回退原路径。
3537
+ * @author ddj 2026年08月25号
3538
+ * @param path 打开的文件路径(相对 cwd 或绝对)
3539
+ * @param cwd 会话工作区目录(可选)
3540
+ * @returns 引用串(如 @src/index.ts 或 @"a b.ts")
3541
+ */
3542
+ function mentionOf(path, cwd) {
3543
+ const raw = String(path);
3544
+ let rel = raw;
3545
+ if (cwd) {
3546
+ const base = String(cwd).replace(/[\\/]+$/, "");
3547
+ const up = raw.replace(/\\/g, "/");
3548
+ const baseUp = base.replace(/\\/g, "/");
3549
+ if (up.startsWith(baseUp + "/")) rel = up.slice(baseUp.length + 1);
3550
+ else if (up === baseUp) rel = "";
3551
+ }
3552
+ rel = rel.replace(/\\/g, "/").replace(/^\.\//, "");
3553
+ if (!rel) rel = raw.replace(/\\/g, "/");
3554
+ if (/[\u0000-\u001f\u007f-\u009f"]/u.test(rel)) return "@" + raw;
3555
+ return /\s/u.test(rel) ? `@"${rel}"` : `@${rel}`;
3556
+ }
3557
+ /** 引用标签:文件名 + 可选行区间(chip 内展示,不带 @ 前缀)。
3558
+ * @author ddj 2026年08月25号 */
3559
+ function labelOf(path, range) {
3560
+ const base = String(path).split(/[\\/]/).pop() || String(path);
3561
+ if (!range || range.endLine < range.startLine) return base;
3562
+ return range.startLine === range.endLine ? `${base} L${range.startLine}` : `${base} L${range.startLine}-${range.endLine}`;
3563
+ }
3564
+ /** 引用 ref:引用串 + 可选行区间(序列化直出,agent 据此读取对应片段)。
3565
+ * @author ddj 2026年08月25号 */
3566
+ function refOf(mention, range) {
3567
+ if (!range || range.endLine < range.startLine) return mention;
3568
+ return range.startLine === range.endLine ? `${mention} L${range.startLine}` : `${mention} L${range.startLine}-${range.endLine}`;
3569
+ }
3570
+ /** 构造 DSH 文件引用插入载荷(source=reference,发送序列化为 mention(+range) 文本)。 */
3571
+ function buildFileRef(path, cwd, range) {
3572
+ const mention = mentionOf(path, cwd);
3573
+ return {
3574
+ mention,
3575
+ reference: {
3576
+ source: REF_SOURCE,
3577
+ ref: refOf(mention, range),
3578
+ label: labelOf(path, range),
3579
+ appearance: "file",
3580
+ clipboardText: mention
3581
+ }
3582
+ };
3583
+ }
3584
+ /**
3585
+ * 解析会话输入门面;缺失时返回 undefined(动作侧守卫降级)。
3586
+ * @author ddj 2026年08月25号
3587
+ * @param ctx 客户端服务上下文
3588
+ * @param sessionId 会话 id
3589
+ * @returns SessionInput 或 undefined
3590
+ */
3591
+ function inputFor(ctx, sessionId) {
3592
+ if (!sessionId) return void 0;
3593
+ try {
3594
+ const sessions = ctx.get("sessions");
3595
+ const conversation = ctx.get("conversation");
3596
+ const actx = sessions?.scope?.(sessionId);
3597
+ const shell = actx && conversation?.input?.for?.(actx);
3598
+ return shell && typeof shell.setDraft === "function" ? shell : void 0;
3599
+ } catch {
3600
+ return;
3601
+ }
3602
+ }
3603
+ /** 取当前草稿长度与版本;无输入门面时返回 null。
3604
+ * @author ddj 2026年08月25号 */
3605
+ function draftCursor(input) {
3606
+ try {
3607
+ const s = input?.state?.getSnapshot?.();
3608
+ if (!s) return null;
3609
+ return {
3610
+ draft: String(s.draft ?? ""),
3611
+ draftRev: Number(s.draftRev ?? 0)
3612
+ };
3613
+ } catch {
3614
+ return null;
3615
+ }
3616
+ }
3617
+ /** 从 sessions 列表快照读会话工作区目录(与 index.ts 既有读取方式一致)。
3618
+ * @author ddj 2026年08月25号 */
3619
+ function cwdOf(ctx, sessionId) {
3620
+ if (!sessionId) return void 0;
3621
+ try {
3622
+ return ctx.get("sessions")?.list?.getSnapshot?.()?.byId?.[sessionId]?.cwd;
3623
+ } catch {
3624
+ return;
3625
+ }
3626
+ }
3627
+ /** 写入成功后的 composer 内联提示(门面可用时;失败静默)。
3628
+ * @author ddj 2026年08月25号 */
3629
+ function notify(input, text) {
3630
+ try {
3631
+ input.notify?.("info", text);
3632
+ } catch {}
3633
+ }
3634
+ /** 输入门面插入引用(拦截异常视作未应用,走降级)。
3635
+ * @author ddj 2026年08月25号 */
3636
+ function safeInsert(input, reference, span) {
3637
+ try {
3638
+ return input.insertReference(reference, span) === true;
3639
+ } catch {
3640
+ return false;
3641
+ }
3642
+ }
3643
+ /**
3644
+ * 创建「添加到对话」动作集(apply 阶段构建一次,随 props 传给 EditorView)。
3645
+ * @author ddj 2026年08月25号
3646
+ * @param ctx 客户端服务上下文(sessions + conversation)
3647
+ * @returns 动作集
3648
+ */
3649
+ function createAddToConversation(ctx) {
3650
+ const appendReference = async (sessionId, path, range) => {
3651
+ const input = inputFor(ctx, sessionId);
3652
+ if (!input) return "unavailable";
3653
+ const cur = draftCursor(input);
3654
+ if (!cur) return "unavailable";
3655
+ const { reference, mention } = buildFileRef(path, cwdOf(ctx, sessionId), range);
3656
+ if (!safeInsert(input, reference, {
3657
+ start: cur.draft.length,
3658
+ end: cur.draft.length,
3659
+ draftRev: cur.draftRev
3660
+ })) {
3661
+ const gap = cur.draft.length > 0 && !/\s$/.test(cur.draft) ? " " : "";
3662
+ input.setDraft(cur.draft + gap + mention + " ");
3663
+ notify(input, "已添加文件引用 " + mention);
3664
+ return "busy";
3665
+ }
3666
+ notify(input, "已添加文件引用 " + mention);
3667
+ return "ok";
3668
+ };
3669
+ return { appendReference };
3670
+ }
3671
+ //#endregion
3672
+ //#region src/client/sidebar/registry.ts
3673
+ /** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
3674
+ function panelRegister(entries, notify, panel) {
3675
+ if (!panel.id || typeof panel.render !== "function") throw new TypeError("侧边栏面板必须提供 id 和 render");
3676
+ entries.set(panel.id, panel);
3677
+ notify();
3678
+ }
3679
+ /**
3680
+ * 创建生命周期独立的面板注册表。
3681
+ * @author ddj 2026年08月26号
3682
+ * @returns 面板注册表
3683
+ */
3684
+ function createSidebarPanelRegistry() {
3685
+ const entries = /* @__PURE__ */ new Map();
3686
+ const listeners = /* @__PURE__ */ new Set();
3687
+ const notify = () => {
3688
+ for (const listener of listeners) listener();
3689
+ };
3690
+ const list = () => [...entries.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
3691
+ return {
3692
+ register(panel) {
3693
+ panelRegister(entries, notify, panel);
3694
+ return () => {
3695
+ if (entries.get(panel.id) !== panel) return;
3696
+ entries.delete(panel.id);
3697
+ notify();
3698
+ };
3699
+ },
3700
+ list,
3701
+ subscribe(listener) {
3702
+ listeners.add(listener);
3703
+ return () => listeners.delete(listener);
3704
+ },
3705
+ get: (id) => entries.get(id)
3706
+ };
3707
+ }
3708
+ //#endregion
3709
+ //#region src/client/sidebar/panels/FileExplorer.ts
3710
+ /**
3711
+ * dsh-vscode-mode client — 侧边栏「文件管理」面板(懒加载目录树)。
3712
+ * 点目录展开/收起(首次经 edrv.listDir 拉取并缓存),点文件经 ctx.openFile 打开;
3713
+ * 差异角标取 pendingByPath,活动文件高亮;edrv:refresh 事件与手动刷新重载树。
3714
+ * 作者 ddj 2026-08-26
3715
+ */
3716
+ const DIR_CAP = 4e3;
3717
+ /**
3718
+ * 目录树面板主体。
3719
+ * @param props.ctx 面板共享上下文(sessionId/openFile/activePath/pendingByPath)
3720
+ */
3721
+ function FileExplorer(props) {
3722
+ const ctx = props?.ctx;
3723
+ const sessionId = ctx?.sessionId;
3724
+ const openFile = ctx?.openFile;
3725
+ const activePath = ctx?.activePath ?? null;
3726
+ const pendingByPath = ctx?.pendingByPath ?? {};
3727
+ const [root, setRoot] = react.default.useState(null);
3728
+ const [dirs, setDirs] = react.default.useState({});
3729
+ const [expanded, setExpanded] = react.default.useState({});
3730
+ const [loading, setLoading] = react.default.useState({});
3731
+ const [error, setError] = react.default.useState(null);
3732
+ const tokensRef = react.default.useRef({});
3733
+ const expandedRef = react.default.useRef({});
3734
+ expandedRef.current = expanded;
3735
+ const loadDir = (rel) => {
3736
+ const token = (tokensRef.current[rel] || 0) + 1;
3737
+ tokensRef.current[rel] = token;
3738
+ setLoading((prev) => Object.assign({}, prev, { [rel]: true }));
3739
+ setError(null);
3740
+ rpc("edrv.listDir", {
3741
+ sessionId,
3742
+ path: rel
3743
+ }).then((res) => {
3744
+ if (tokensRef.current[rel] !== token) return;
3745
+ if (res && res.ok && Array.isArray(res.entries)) {
3746
+ setDirs((prev) => Object.assign({}, prev, { [rel]: res.entries }));
3747
+ if (res.root && !root) setRoot(res.root);
3748
+ } else setError(res?.error ? String(res.error) : "读取目录失败");
3749
+ }).catch((e) => {
3750
+ if (tokensRef.current[rel] !== token) return;
3751
+ setError("读取目录异常:" + String(e));
3752
+ }).finally(() => {
3753
+ if (tokensRef.current[rel] !== token) return;
3754
+ setLoading((prev) => {
3755
+ const next = Object.assign({}, prev);
3756
+ delete next[rel];
3757
+ return next;
3758
+ });
3759
+ });
3760
+ };
3761
+ const toggle = (rel) => {
3762
+ if (expanded[rel] === true) {
3763
+ setExpanded((prev) => Object.assign({}, prev, { [rel]: false }));
3764
+ return;
3765
+ }
3766
+ if (!dirs[rel]) loadDir(rel);
3767
+ setExpanded((prev) => Object.assign({}, prev, { [rel]: true }));
3768
+ };
3769
+ const refresh = () => {
3770
+ const keep = Object.keys(expandedRef.current).filter((k) => expandedRef.current[k] === true);
3771
+ tokensRef.current = {};
3772
+ setDirs({});
3773
+ setLoading({});
3774
+ setError(null);
3775
+ if (keep.length) {
3776
+ const next = {};
3777
+ for (const rel of keep) next[rel] = true;
3778
+ setExpanded(next);
3779
+ for (const rel of keep) loadDir(rel);
3780
+ } else setExpanded({});
3781
+ loadDir("");
3782
+ };
3783
+ react.default.useEffect(() => {
3784
+ tokensRef.current = {};
3785
+ setDirs({});
3786
+ setExpanded({});
3787
+ setLoading({});
3788
+ setError(null);
3789
+ setRoot(null);
3790
+ loadDir("");
3791
+ }, [sessionId]);
3792
+ react.default.useEffect(() => {
3793
+ const onRefresh = () => refresh();
3794
+ window.addEventListener("edrv:refresh", onRefresh);
3795
+ return () => window.removeEventListener("edrv:refresh", onRefresh);
3796
+ }, []);
3797
+ const rootName = root ? String(root).split(/[\\/]/).pop() || root : "";
3798
+ const rowEl = (e, depth, isDir, isOpen) => {
3799
+ const pending = isDir ? 0 : pendingByPath[e.path] ?? 0;
3800
+ const active = !isDir && e.path === activePath;
3801
+ const dim = e.type === "other";
3802
+ return react.default.createElement("div", {
3803
+ key: e.path,
3804
+ className: "edrv-tree-row" + (active ? " edrv-tree-active" : ""),
3805
+ title: e.path,
3806
+ style: { paddingLeft: 6 + depth * 14 },
3807
+ onClick: () => {
3808
+ if (isDir) toggle(e.path);
3809
+ else openFile(e.path);
3810
+ }
3811
+ }, react.default.createElement("span", { className: "edrv-tree-chev" }, isDir ? isOpen ? "▾" : "▸" : ""), react.default.createElement("span", { className: "edrv-tree-name" + (dim ? " edrv-tree-dim" : "") }, isDir ? isOpen ? "📂" : "📁" : "📄", " ", e.name), pending > 0 ? react.default.createElement("span", { className: "edrv-tree-badge" }, String(pending)) : null);
3812
+ };
3813
+ const rowsOf = (rel, depth) => {
3814
+ const entries = dirs[rel];
3815
+ if (!entries) return [react.default.createElement("div", {
3816
+ key: rel + ":loading",
3817
+ className: "edrv-tree-loading",
3818
+ style: { paddingLeft: 6 + depth * 14 }
3819
+ }, loading[rel] ? "加载中…" : "无法加载")];
3820
+ const rows = [];
3821
+ const cap = entries.length;
3822
+ for (let i = 0; i < cap; i++) {
3823
+ const e = entries[i];
3824
+ const isDir = e.type === "directory";
3825
+ const isOpen = expanded[e.path] === true;
3826
+ rows.push(rowEl(e, depth, isDir, isOpen));
3827
+ if (isDir && isOpen) rows.push(...rowsOf(e.path, depth + 1));
3828
+ }
3829
+ if (cap >= DIR_CAP) rows.push(react.default.createElement("div", {
3830
+ key: rel + ":cap",
3831
+ className: "edrv-tree-loading",
3832
+ style: { paddingLeft: 6 + depth * 14 }
3833
+ }, "目录条目过多,仅显示前 4000 项"));
3834
+ return rows;
3835
+ };
3836
+ return react.default.createElement("div", { className: "edrv-side-panel" }, react.default.createElement("div", { className: "edrv-side-head" }, react.default.createElement("span", { className: "edrv-side-title" }, "资源管理器"), react.default.createElement("span", {
3837
+ className: "edrv-side-root",
3838
+ title: root || ""
3839
+ }, rootName), react.default.createElement("span", { style: { flex: 1 } }), react.default.createElement("button", {
3840
+ className: "edrv-side-btn",
3841
+ title: "刷新目录树",
3842
+ onClick: refresh
3843
+ }, "⟳")), react.default.createElement("div", { className: "edrv-tree" }, error ? react.default.createElement("div", { className: "edrv-tree-error" }, react.default.createElement("span", null, String(error)), react.default.createElement("button", {
3844
+ className: "edrv-side-btn",
3845
+ onClick: () => refresh()
3846
+ }, "重试")) : null, rowsOf("", 0)));
3847
+ }
3848
+ //#endregion
3849
+ //#region src/client/sidebar/panels/index.ts
3850
+ /**
3851
+ * dsh-vscode-mode client — 「文件管理」面板定义(注册表一项)。
3852
+ * 作者 ddj 2026-08-26
3853
+ */
3854
+ /**
3855
+ * 构造文件管理面板定义。
3856
+ * @author ddj 2026年08月26号
3857
+ * @returns 面板定义(含活动栏徽标 = 差异文件总数)
3858
+ */
3859
+ function createFilePanel() {
3860
+ return {
3861
+ id: "explorer",
3862
+ title: "文件管理",
3863
+ icon: "📁",
3864
+ order: 10,
3865
+ badge: (ctx) => ctx.sum && ctx.sum.totalFiles > 0 ? ctx.sum.totalFiles : null,
3866
+ render: (ctx) => react.default.createElement(FileExplorer, { ctx })
3867
+ };
3868
+ }
3869
+ //#endregion
2922
3870
  //#region src/client/index.ts
2923
3871
  /**
2924
3872
  * dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
2925
- * 挂点:conversation.view「文件编辑」页签(中央 Monaco 编辑器)+ header 差异角标。
3873
+ * 挂点:conversation.view「文件编辑」页签(中央 Monaco 编辑器)+ conversation.input.dock 差异条 + header 差异角标。
2926
3874
  * 与 Host 通信:同源 fetch('/edrv/rpc')(shared/rpc 契约)。
2927
3875
  *
2928
3876
  * ⚠️ 跨版本 slot 装配(2026-08-21):新版 DSH 的 slots 系统要求 slot 必须由父 entry
@@ -2930,7 +3878,8 @@ window.__ModuleLoader__.load({
2930
3878
  * 声明未就绪时抛 `slot ... is not declared (a parent entry's children table must declare it)`。
2931
3879
  * 正确写法 = `ctx.slots.inject(name, () => ctx.slots.register(...))`:声明存在时同步
2932
3880
  * 执行,否则等待声明(官方 ui-conversation 自身即此模式);旧版(rc.8 及更早)同样支持,
2933
- * 故跨版本兼容。slot 名未变:conversation.view / conversation.session.header.utilities。
3881
+ * 故跨版本兼容。slot 名:conversation.view / conversation.session.header.utilities /
3882
+ * conversation.input.dock。
2934
3883
  * 作者 ddj 2026-08-20
2935
3884
  */
2936
3885
  const inject = [
@@ -2941,6 +3890,7 @@ window.__ModuleLoader__.load({
2941
3890
  "remote",
2942
3891
  "workspaces",
2943
3892
  "sessions",
3893
+ "conversation",
2944
3894
  "settingsScope",
2945
3895
  "webUiSettings"
2946
3896
  ];
@@ -2954,10 +3904,27 @@ window.__ModuleLoader__.load({
2954
3904
  const registry = createFileOpenerRegistry();
2955
3905
  const workspaces = ctx.get("workspaces");
2956
3906
  const sessions = ctx.get("sessions");
3907
+ const monacoList = sessions?.list;
3908
+ if (typeof window !== "undefined" && monacoList && typeof monacoList.subscribe === "function") {
3909
+ const list = monacoList;
3910
+ const schedulePreload = () => {
3911
+ const preload = () => loadMonaco(() => {}).catch(() => {});
3912
+ if (typeof window.requestIdleCallback === "function") window.requestIdleCallback(preload, { timeout: 2e3 });
3913
+ else schedule(preload, 300);
3914
+ };
3915
+ const onSessionEnter = () => {
3916
+ if (!list.getSnapshot()?.current) return;
3917
+ schedulePreload();
3918
+ };
3919
+ ctx.effect(() => list.subscribe(onSessionEnter), "vscode-mode: monaco session trigger");
3920
+ onSessionEnter();
3921
+ }
2957
3922
  const originalOpenPath = workspaces?.openPath;
2958
3923
  const binder = pickSettingsBinder(ctx);
2959
3924
  const settings = binder.scope;
2960
3925
  let selected = autoValue("auto");
3926
+ /** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
3927
+ const addToConversation = createAddToConversation(ctx);
2961
3928
  /** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
2962
3929
  const compatSummary = () => [
2963
3930
  {
@@ -2978,6 +3945,9 @@ window.__ModuleLoader__.load({
2978
3945
  ];
2979
3946
  ctx.provide("fileOpeners", registry);
2980
3947
  ctx.effect(() => registry.register(vscodeOpener()), "vscode-mode: file opener");
3948
+ const sidebarPanels = createSidebarPanelRegistry();
3949
+ ctx.provide("edrvSidebarPanels", sidebarPanels);
3950
+ ctx.effect(() => sidebarPanels.register(createFilePanel()), "vscode-mode: sidebar panel");
2981
3951
  ctx.effect(() => registry.register({
2982
3952
  id: "system",
2983
3953
  label: "系统默认应用",
@@ -3019,7 +3989,18 @@ window.__ModuleLoader__.load({
3019
3989
  order: 5,
3020
3990
  label: "文件编辑",
3021
3991
  inject: (sessionId) => ({ sessionId })
3022
- }, (props) => react.default.createElement(EditorView, Object.assign({}, props, { schedule })));
3992
+ }, (props) => react.default.createElement(EditorView, Object.assign({}, props, {
3993
+ schedule,
3994
+ addToConversation,
3995
+ sidebarPanels
3996
+ })));
3997
+ registerSlotSafely(ctx, {
3998
+ name: "conversation.input.dock",
3999
+ id: "edrv-diff-dock",
4000
+ order: 30,
4001
+ label: "差异",
4002
+ inject: (sessionId) => ({ sessionId })
4003
+ }, (props) => react.default.createElement(ConversationDiffDock, Object.assign({}, props)));
3023
4004
  registerSlotSafely(ctx, {
3024
4005
  name: "conversation.session.header.utilities",
3025
4006
  id: "edrv-diff-badge",