dsh-code-server-app 0.3.23 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.en.md +8 -3
  2. package/README.md +6 -2
  3. package/assets/extensions/dshcs-editor-bridge/extension.js +21 -0
  4. package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +6 -2
  5. package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +13 -0
  6. package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
  7. package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +2 -2
  8. package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +25 -2
  9. package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +573 -7
  10. package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +45 -11
  11. package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +38 -2
  12. package/assets/extensions/dshcs-editor-bridge/webview/thread.css +1 -1
  13. package/assets/extensions/dshcs-editor-bridge/webview/thread.js +61 -60
  14. package/lib/bridge-approval.mjs +8 -0
  15. package/lib/bridge-session.mjs +40 -0
  16. package/lib/bridge-thread.mjs +33 -6
  17. package/lib/client.js +2 -2
  18. package/lib/index.js +268 -14
  19. package/package.json +1 -1
  20. package/vendor/VENDOR.json +1 -1
  21. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
  22. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
  23. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
  24. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
  25. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
  26. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
  27. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
  28. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
  29. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
  30. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
  31. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
  32. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
  33. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
  34. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
  35. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
  36. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
  37. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
  38. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
  39. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
  40. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
package/lib/index.js CHANGED
@@ -744,15 +744,159 @@ export async function apply(ctx, config) {
744
744
  });
745
745
  /** 面板的授权决策登记表 + agent 作用域拦截器(见 lib/bridge-approval.mjs)。 */
746
746
  const bridgeApprovalBoard = createApprovalBoard();
747
- /** 面板最近一次声明 watch 的时间/id:据此判断"面板是不是正在看"(关掉就不拦截授权)。 */
748
- const bridgeWatch = { ids: [], at: 0 };
747
+ /**
748
+ * 谁在"看着这个会话" —— 授权拦截据此决定要不要抢答(没人看就直接交给官方链路):
749
+ * - `ids`:编辑器扩展面板声明要看的会话(每趟 /sync 上报);
750
+ * - `dialogIds`:DSH 页面里的对话框(0.3.24)声明要看的会话;
751
+ * - `at`:最近一次声明的时间(超过 5s 视为没人看)。
752
+ */
753
+ const bridgeWatch = { ids: [], dialogIds: [], at: 0 };
754
+
755
+ /** 当前所有"在看"的会话(去重)。 */
756
+ function watchedSessions() {
757
+ const all = [...bridgeWatch.ids, ...bridgeWatch.dialogIds];
758
+ return [...new Set(all.filter((id) => typeof id === 'string' && id !== ''))].slice(0, 4);
759
+ }
760
+ /** 现在有没有人在看(授权拦截的唯一判据)。 */
761
+ function hasWatcher() {
762
+ return watchedSessions().length > 0 && Date.now() - bridgeWatch.at < 5000;
763
+ }
764
+
749
765
  const bridgeApproval = createApprovalInterceptor({
750
766
  board: bridgeApprovalBoard,
751
767
  holdMs: DEFAULT_HOLD_MS,
752
- hasPanel: () => bridgeWatch.ids.length > 0 && Date.now() - bridgeWatch.at < 5000,
768
+ hasPanel: hasWatcher,
753
769
  log: (message) => console.warn(`[code-server] 编辑器桥授权:${message}`),
754
770
  });
755
771
 
772
+ /**
773
+ * 「问 DSH」对话框的宿主侧状态(0.3.24)。
774
+ *
775
+ * 对话渲染从编辑器的 webview 面板搬到了 **DSH 页面里的浮动对话框**(client 半部,见 src/factory.js):
776
+ * 面板是编辑器里的一个 tab/一列,而用户要的是"像 DSH 那样的对话框"。host 这边只负责:
777
+ * - 打开/关闭状态(编辑器扩展右键 → 桥 `/event` 上报 `ask-open`);
778
+ * - 状态与动作的 HTTP 面(`/api/code-server/ask/*`,与 DSH 同源、吃它的 cookie/Origin 校验);
779
+ * - 把面板产物(thread.js / thread.css)交给对话框注入(Lib 版本与 DSH 界面同版本,见 build-webview.mjs)。
780
+ */
781
+ const askDialog = { open: false, mode: 'selection', at: 0, sessionId: null, polls: 0, lastPollAt: 0 };
782
+
783
+ /**
784
+ * 对话框"活着吗" —— **能力位必须以此为准**(0.3.25)。
785
+ *
786
+ * 0.3.24 的教训:宿主无条件报 `askDialog: true`,扩展于是不再开编辑器面板;而客户端半部只要没跑起来
787
+ * (浏览器还缓存着旧 bundle / 没硬刷新),就**没有任何一方在看这个会话** ⇒ 授权悄悄落到 DSH 界面的
788
+ * 官方卡片,用户在编辑器里什么都看不到。现在只有**真的收到过 `/ask/state` 轮询**(5 秒内)才算有
789
+ * 这个能力;否则扩展退回编辑器面板 —— 那是验证过、能看能点的 UI。
790
+ */
791
+ function askDialogLive() {
792
+ return askDialog.polls > 0 && Date.now() - askDialog.lastPollAt < 5000;
793
+ }
794
+
795
+ /** 缓存里的活动编辑器 → 提问上下文(mode 决定带不带行号/选区,与扩展侧 0.3.21 的语义一致)。 */
796
+ function askContextFromCache(mode) {
797
+ const cached = bridgeContext.get();
798
+ const active = cached === null ? null : cached.context?.active ?? null;
799
+ if (active === null || active === undefined) return null;
800
+ const selection = active.selection ?? null;
801
+ const selectedText = typeof active.selectedText === 'string' ? active.selectedText : '';
802
+ const hasSelection = mode === 'selection' && selection !== null && selectedText !== '';
803
+ return {
804
+ file: typeof active.path === 'string' && active.path !== '' ? active.path : null,
805
+ lineStart: hasSelection ? selection.startLine ?? null : null,
806
+ lineEnd: hasSelection ? selection.endLine ?? null : null,
807
+ selection: hasSelection ? selectedText : null,
808
+ languageId: typeof active.language === 'string' && active.language !== '' ? active.language : null,
809
+ };
810
+ }
811
+
812
+ /** 对话框那一行上下文描述(标题栏用)。 */
813
+ function askContextLine(mode) {
814
+ const context = askContextFromCache(mode);
815
+ if (context === null || context.file === null) return '来自编辑器';
816
+ const base = context.file.replace(/^.*[\\/]/, '');
817
+ const where = Number.isSafeInteger(context.lineStart)
818
+ ? `${base}:${context.lineStart}${Number.isSafeInteger(context.lineEnd) && context.lineEnd !== context.lineStart ? `-${context.lineEnd}` : ''}`
819
+ : base;
820
+ return mode === 'file' ? `来自编辑器:${where} · 针对当前文件` : `来自编辑器:${where} · 针对选中内容`;
821
+ }
822
+
823
+ /**
824
+ * 对话框要的状态 —— **形状必须与面板载荷一致**(`lib/ask-panel.js` 的 `applyPayload`:
825
+ * 扁平的 entries / approvals / …),因为对话框里跑的就是同一个面板应用。
826
+ */
827
+ function askState() {
828
+ const watch = watchedSessions();
829
+ const snapshot = bridgeThread.supported()
830
+ ? bridgeThread.snapshot(watch)
831
+ : { available: false, sessionId: null, cursor: null, rev: 0, entries: [], error: '宿主没有 sessionController.follow(DSH 版本过旧)' };
832
+ return {
833
+ ok: true,
834
+ open: askDialog.open,
835
+ mode: askDialog.mode,
836
+ contextText: askContextLine(askDialog.mode),
837
+ entries: Array.isArray(snapshot.entries) ? snapshot.entries : [],
838
+ available: snapshot.available === true,
839
+ threadError: typeof snapshot.error === 'string' && snapshot.error !== '' ? snapshot.error : null,
840
+ approvals: bridgeApprovalBoard.snapshot(),
841
+ approvalHoldMs: DEFAULT_HOLD_MS,
842
+ status: 'idle',
843
+ statusText: '',
844
+ error: null,
845
+ sessionId: snapshot.sessionId ?? askDialog.sessionId ?? null,
846
+ uiVersion: uiVersion(),
847
+ dshVersion: dshVersion(),
848
+ };
849
+ }
850
+
851
+ /** 修订号:对话条目 / 待决授权 / 开关状态任何一处变了就变。 */
852
+ function askRevision() {
853
+ return bridgeThread.rev(watchedSessions()) * 8 + bridgeApprovalBoard.rev() * 8 + (askDialog.open ? 1 : 0) + (askDialog.mode === 'file' ? 2 : 0);
854
+ }
855
+
856
+ /** 对话框刷新它的"我在看"时间戳(每趟 /state 都算一次)。 */
857
+ function refreshDialogWatch() {
858
+ bridgeWatch.dialogIds = askDialog.open && askDialog.sessionId !== null ? [askDialog.sessionId] : [];
859
+ bridgeWatch.at = Date.now();
860
+ }
861
+
862
+ /**
863
+ * 回答一条待决授权 —— 桥的路由与对话框的路由共用同一套校验(见 lib/bridge-approval.mjs 的安全约束)。
864
+ * @returns {{status: number, body: object}}
865
+ */
866
+ function answerApproval(id, outcome) {
867
+ if (typeof id !== 'string' || id === '') return { status: 400, body: { ok: false, error: '需要 id 字段' } };
868
+ if (!PANEL_OUTCOMES.includes(outcome)) {
869
+ return { status: 400, body: { ok: false, error: `outcome 只能取 ${PANEL_OUTCOMES.join(' | ')}` } };
870
+ }
871
+ if (bridgeApprovalBoard.get(id) === null) return { status: 409, body: { ok: false, error: '未知或已处理的授权请求' } };
872
+ const ok = bridgeApprovalBoard.answer(id, outcome);
873
+ return { status: ok ? 200 : 409, body: { ok, outcome: ok ? outcome : null } };
874
+ }
875
+
876
+ /** 面板产物(thread.js / thread.css):对话框把它们注入 DSH 页面,所以由 host 读盘给文本。 */
877
+ let askBundleCache = null;
878
+ function askBundle() {
879
+ if (askBundleCache !== null) return askBundleCache;
880
+ const dir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'assets', 'extensions', 'dshcs-editor-bridge', 'webview');
881
+ const read = (name) => {
882
+ try {
883
+ return fs.readFileSync(path.join(dir, name), 'utf8');
884
+ } catch {
885
+ return null;
886
+ }
887
+ };
888
+ const js = read('thread.js');
889
+ const css = read('thread.css');
890
+ // 产物缺失(打包漏了 build:webview)→ 明确报出来,不返回半个面板。
891
+ if (js === null || css === null) {
892
+ console.warn(`[code-server] 「问 DSH」对话框的产物缺失:${dir}(请在源码里跑 pnpm run build:webview)`);
893
+ return null;
894
+ }
895
+ askBundleCache = { js, css };
896
+ return askBundleCache;
897
+ }
898
+
899
+
756
900
  /** 本部署的 DSH 版本(诊断用;面板要的是下面那个"界面版本")。 */
757
901
  function dshVersion() {
758
902
  try {
@@ -1746,14 +1890,21 @@ export async function apply(ctx, config) {
1746
1890
  const watch = Array.isArray(body.watch) ? body.watch.filter((id) => typeof id === 'string' && id !== '') : [];
1747
1891
  bridgeWatch.ids = watch.slice(0, 4);
1748
1892
  bridgeWatch.at = Date.now();
1749
- bridgeThread.sync(bridgeWatch.ids);
1893
+ bridgeThread.sync(watchedSessions());
1894
+ // 请求过对话框、客户端半部却从没来问过 → 说清楚原因(多半是浏览器还缓存着旧的客户端 bundle)。
1895
+ if (askDialog.open && !askDialogLive() && askDialog.warned !== true) {
1896
+ askDialog.warned = true;
1897
+ console.warn('[code-server] 请求了「问 DSH」对话框,但客户端半部没有轮询 /ask/state:'
1898
+ + '浏览器可能还在用旧的客户端 bundle(硬刷新 / 清 Code Cache)。'
1899
+ + '此期间提问会退回编辑器面板,授权也在面板里批。');
1900
+ }
1750
1901
  // 对话流走独立字段:每个会话给有界条目(见 lib/bridge-thread.mjs),
1751
1902
  // 授权待决列表也一并回去(面板显示卡片)。
1752
1903
  return jsonResponse({
1753
1904
  ok: true,
1754
1905
  events,
1755
1906
  thread: bridgeThread.supported()
1756
- ? bridgeThread.snapshot(bridgeWatch.ids)
1907
+ ? bridgeThread.snapshot(watchedSessions())
1757
1908
  : { available: false, sessionId: null, cursor: null, entries: [], error: '宿主没有 sessionController.follow(DSH 版本过旧)' },
1758
1909
  approvals: bridgeApprovalBoard.snapshot(),
1759
1910
  // 授权卡片要在面板上倒计时,窗口长度由 host 给(面板不猜)。
@@ -1761,6 +1912,11 @@ export async function apply(ctx, config) {
1761
1912
  dshVersion: dshVersion(),
1762
1913
  // 界面版本:面板据此提示"内置渲染器与当前 DSH 界面版本不一致"。
1763
1914
  uiVersion: uiVersion(),
1915
+ // 能力探测(0.3.24;0.3.25 起以"客户端半部真的在轮询"为准):
1916
+ // 只有对话框活着的时候才让扩展走 ask-open;否则扩展开编辑器面板(那里也能批授权)。
1917
+ askDialog: askDialogLive(),
1918
+ // 诊断用:请求过对话框但客户端没响应(浏览器缓存旧 bundle?)。
1919
+ askDialogRequested: askDialog.open,
1764
1920
  });
1765
1921
  }
1766
1922
 
@@ -1821,17 +1977,99 @@ export async function apply(ctx, config) {
1821
1977
  } catch {
1822
1978
  return jsonResponse({ ok: false, error: '请求体不是合法 JSON' }, 400);
1823
1979
  }
1824
- const id = typeof body.id === 'string' ? body.id : '';
1825
- const outcome = typeof body.outcome === 'string' ? body.outcome : '';
1826
- if (id === '') return jsonResponse({ ok: false, error: '需要 id 字段' }, 400);
1827
- if (!PANEL_OUTCOMES.includes(outcome)) {
1828
- return jsonResponse({ ok: false, error: `outcome 只能取 ${PANEL_OUTCOMES.join(' | ')}` }, 400);
1980
+ const verdict = answerApproval(
1981
+ typeof body.id === 'string' ? body.id : '',
1982
+ typeof body.outcome === 'string' ? body.outcome : '',
1983
+ );
1984
+ return jsonResponse(verdict.body, verdict.status);
1985
+ }
1986
+
1987
+ // ---------------------------------------------------------------- 「问 DSH」对话框(client 半部)
1988
+
1989
+ /** 对话框每 900ms 问一次:给修订号,没变就只回一个数字(不重传几十 KB 的对话流)。 */
1990
+ async function handleAskState(request) {
1991
+ // 这一条就是"客户端半部活着"的证据(能力位 askDialog 以它为准,见 askDialogLive)。
1992
+ askDialog.polls += 1;
1993
+ askDialog.lastPollAt = Date.now();
1994
+ if (askDialog.polls === 1) {
1995
+ console.log('[code-server] 「问 DSH」对话框已连上客户端半部:/ask/state 开始轮询');
1829
1996
  }
1830
- if (bridgeApprovalBoard.get(id) === null) {
1831
- return jsonResponse({ ok: false, error: '未知或已处理的授权请求' }, 409);
1997
+ refreshDialogWatch();
1998
+ const current = askRevision();
1999
+ let rev = null;
2000
+ try {
2001
+ const raw = new URL(request.url).searchParams.get('rev');
2002
+ const parsed = raw === null ? Number.NaN : Number.parseInt(raw, 10);
2003
+ if (Number.isSafeInteger(parsed) && parsed >= 0) rev = parsed;
2004
+ } catch {
2005
+ rev = null;
1832
2006
  }
1833
- const ok = bridgeApprovalBoard.answer(id, outcome);
1834
- return jsonResponse({ ok, outcome: ok ? outcome : null }, ok ? 200 : 409);
2007
+ if (rev !== null && rev === current) return jsonResponse({ ok: true, changed: false, rev: current });
2008
+ return jsonResponse({ ...askState(), changed: true, rev: current });
2009
+ }
2010
+
2011
+ /** 对话框发一条提问:上下文取宿主缓存里的编辑器状态(扩展每 600ms 上报)。 */
2012
+ async function handleAskSend(request) {
2013
+ let body;
2014
+ try {
2015
+ body = await readJsonBody(request);
2016
+ } catch {
2017
+ return jsonResponse({ ok: false, error: '请求体不是合法 JSON' }, 400);
2018
+ }
2019
+ const text = typeof body.text === 'string' ? body.text.trim() : '';
2020
+ if (text === '') return jsonResponse({ ok: false, error: '需要 text 字段(要问 DSH 的话)' }, 400);
2021
+ const mode = body.mode === 'file' ? 'file' : askDialog.mode;
2022
+ const context = askContextFromCache(mode);
2023
+ const result = await deliverEditorPrompt(ctx, {
2024
+ text,
2025
+ file: context === null ? null : context.file,
2026
+ lineStart: context === null ? null : context.lineStart,
2027
+ lineEnd: context === null ? null : context.lineEnd,
2028
+ selection: context === null ? null : context.selection,
2029
+ languageId: context === null ? null : context.languageId,
2030
+ });
2031
+ if (result.ok === true && typeof result.sessionId === 'string' && result.sessionId !== '') {
2032
+ askDialog.sessionId = result.sessionId;
2033
+ askDialog.mode = mode;
2034
+ refreshDialogWatch();
2035
+ bridgeThread.watch(result.sessionId);
2036
+ bridgeApproval.intercept(result.agent, result.sessionId);
2037
+ }
2038
+ const { agent: _agent, ...payload } = result;
2039
+ void _agent;
2040
+ return jsonResponse(payload, result.ok === true ? 200 : (result.code === 'NO_AGENT' ? 409 : 200));
2041
+ }
2042
+
2043
+ /** 对话框回答授权(与桥的 /approve 同一套校验)。 */
2044
+ async function handleAskApprove(request) {
2045
+ let body;
2046
+ try {
2047
+ body = await readJsonBody(request);
2048
+ } catch {
2049
+ return jsonResponse({ ok: false, error: '请求体不是合法 JSON' }, 400);
2050
+ }
2051
+ const verdict = answerApproval(
2052
+ typeof body.id === 'string' ? body.id : '',
2053
+ typeof body.outcome === 'string' ? body.outcome : '',
2054
+ );
2055
+ return jsonResponse(verdict.body, verdict.status);
2056
+ }
2057
+
2058
+ /** 对话框关掉:不再看会话 ⇒ 待决授权按"没人看"交回官方链路(拦截器的轮询会立刻放行)。 */
2059
+ async function handleAskClose() {
2060
+ askDialog.open = false;
2061
+ askDialog.sessionId = null;
2062
+ refreshDialogWatch();
2063
+ return jsonResponse({ ok: true });
2064
+ }
2065
+
2066
+ /** 面板产物(thread.js / thread.css)给对话框注入用。 */
2067
+ async function handleAskBundle() {
2068
+ const bundle = askBundle();
2069
+ if (bundle === null) {
2070
+ return jsonResponse({ ok: false, error: '面板产物缺失:请在插件源码里跑 pnpm run build:webview,或重装插件' }, 503);
2071
+ }
2072
+ return jsonResponse({ ok: true, rev: askRevision(), ...bundle });
1835
2073
  }
1836
2074
 
1837
2075
  async function handleBridgeEvent(request) {
@@ -1840,6 +2078,15 @@ export async function apply(ctx, config) {
1840
2078
  const body = await readJsonBody(request);
1841
2079
  const kind = typeof body.kind === 'string' ? body.kind : '';
1842
2080
  if (kind === '') return jsonResponse({ ok: false, error: '需要 kind 字段' }, 400);
2081
+ // 右键「问 DSH」→ 扩展上报 ask-open:host 打开 DSH 页面里的对话框(client 半部轮询到就拿去显示)。
2082
+ if (kind === 'ask-open') {
2083
+ askDialog.open = true;
2084
+ askDialog.mode = body.mode === 'file' ? 'file' : 'selection';
2085
+ askDialog.at = Date.now();
2086
+ refreshDialogWatch();
2087
+ appendLog(`[bridge] ask-open mode=${askDialog.mode}\n`);
2088
+ return jsonResponse({ ok: true, mode: askDialog.mode, contextText: askContextLine(askDialog.mode) });
2089
+ }
1843
2090
  // 扩展上报的编辑器侧事件(打开/关闭文件等):目前只进日志尾,供诊断时回看。
1844
2091
  appendLog(`[bridge] ${kind}${typeof body.path === 'string' ? ` ${body.path}` : ''}\n`);
1845
2092
  return jsonResponse({ ok: true });
@@ -1854,6 +2101,13 @@ export async function apply(ctx, config) {
1854
2101
  { path: `${API_BASE}/setup`, methods: ['POST'], fetch: handleSetup },
1855
2102
  { path: `${API_BASE}/open-file`, methods: ['POST'], fetch: handleOpenFile },
1856
2103
  { path: `${API_BASE}/ui-mode`, methods: ['POST'], fetch: handleUiMode },
2104
+ // ---- 「问 DSH」对话框(0.3.24):DSH 页面里的 client 半部调这几条 ----
2105
+ // 这里**在 /api 下**是对的:调用方是 DSH 页面自己(同源 + cookie),与扩展宿主(必须走本机 IPC)不同。
2106
+ { path: `${API_BASE}/ask/state`, methods: ['GET'], fetch: handleAskState },
2107
+ { path: `${API_BASE}/ask/send`, methods: ['POST'], fetch: handleAskSend },
2108
+ { path: `${API_BASE}/ask/approve`, methods: ['POST'], fetch: handleAskApprove },
2109
+ { path: `${API_BASE}/ask/close`, methods: ['POST'], fetch: handleAskClose },
2110
+ { path: `${API_BASE}/ask/bundle`, methods: ['GET'], fetch: handleAskBundle },
1857
2111
  // ---- 编辑器桥的 4 条路由**不在 /api 下**(0.3.9 修正)----
1858
2112
  // 原因:Connection 给 `/api` 装了 cookie fence(无 cookie → 401),而扩展宿主是 Node 进程、
1859
2113
  // 永远拿不到浏览器 cookie ⇒ 挂在这里的路由根本到不了(实测:带令牌也被 401/405 挡回)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-server-app",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
4
4
  "description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs). Since 0.3.0 the bundle also ships an editor bridge (assets/extensions/dshcs-editor-bridge): a read-only channel between the in-tree VS Code extension host and DSH, giving the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and letting editor gestures drive the session. Since 0.3.22 the ask panel renders that session's new content with DSH's own Markdown renderer (pinned to the deployed DSH UI version); since 0.3.23 it is a full-width dialog with collapsible thinking rows and can answer approval requests in place — the bridge's only non-read-only route (POST /approve, constrained to pending in-process requests and the outcomes allowed-once/rejected; panel-first for 5 minutes, handed back to the official card the moment the panel closes). The tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so the product's own produced-file chips, delivered-file previews and inline prose mentions open in the workbench. The IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs no longer reloads it. Opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen). Following a workspace switch is lightweight: the workbench re-navigates with the new ?folder= and the IDE process is not restarted (since 0.2.12). Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions get a single upgrade notice on the settings page and no other UI. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). No code-server Node layer, no argon2, no C++ toolchain.",
5
5
  "homepage": "https://github.com/jinsiyu/dsh-code-server-app",
6
6
  "repository": {
@@ -3,7 +3,7 @@
3
3
  "vscodeVersion": "1.137.0",
4
4
  "productPath": "stable-b11dabdaca0d3369986975be285db92c8795cea5",
5
5
  "layout": "vscode-only",
6
- "preparedAt": "2026-09-13T08:52:39.438Z",
6
+ "preparedAt": "2026-09-13T09:25:27.979Z",
7
7
  "source": "registry",
8
8
  "node": "v24.21.0",
9
9
  "platform": "win32",