dsh-code-server-app 0.3.23 → 0.3.24

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 +240 -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,147 @@ 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 };
782
+
783
+ /** 缓存里的活动编辑器 → 提问上下文(mode 决定带不带行号/选区,与扩展侧 0.3.21 的语义一致)。 */
784
+ function askContextFromCache(mode) {
785
+ const cached = bridgeContext.get();
786
+ const active = cached === null ? null : cached.context?.active ?? null;
787
+ if (active === null || active === undefined) return null;
788
+ const selection = active.selection ?? null;
789
+ const selectedText = typeof active.selectedText === 'string' ? active.selectedText : '';
790
+ const hasSelection = mode === 'selection' && selection !== null && selectedText !== '';
791
+ return {
792
+ file: typeof active.path === 'string' && active.path !== '' ? active.path : null,
793
+ lineStart: hasSelection ? selection.startLine ?? null : null,
794
+ lineEnd: hasSelection ? selection.endLine ?? null : null,
795
+ selection: hasSelection ? selectedText : null,
796
+ languageId: typeof active.language === 'string' && active.language !== '' ? active.language : null,
797
+ };
798
+ }
799
+
800
+ /** 对话框那一行上下文描述(标题栏用)。 */
801
+ function askContextLine(mode) {
802
+ const context = askContextFromCache(mode);
803
+ if (context === null || context.file === null) return '来自编辑器';
804
+ const base = context.file.replace(/^.*[\\/]/, '');
805
+ const where = Number.isSafeInteger(context.lineStart)
806
+ ? `${base}:${context.lineStart}${Number.isSafeInteger(context.lineEnd) && context.lineEnd !== context.lineStart ? `-${context.lineEnd}` : ''}`
807
+ : base;
808
+ return mode === 'file' ? `来自编辑器:${where} · 针对当前文件` : `来自编辑器:${where} · 针对选中内容`;
809
+ }
810
+
811
+ /**
812
+ * 对话框要的状态 —— **形状必须与面板载荷一致**(`lib/ask-panel.js` 的 `applyPayload`:
813
+ * 扁平的 entries / approvals / …),因为对话框里跑的就是同一个面板应用。
814
+ */
815
+ function askState() {
816
+ const watch = watchedSessions();
817
+ const snapshot = bridgeThread.supported()
818
+ ? bridgeThread.snapshot(watch)
819
+ : { available: false, sessionId: null, cursor: null, rev: 0, entries: [], error: '宿主没有 sessionController.follow(DSH 版本过旧)' };
820
+ return {
821
+ ok: true,
822
+ open: askDialog.open,
823
+ mode: askDialog.mode,
824
+ contextText: askContextLine(askDialog.mode),
825
+ entries: Array.isArray(snapshot.entries) ? snapshot.entries : [],
826
+ available: snapshot.available === true,
827
+ threadError: typeof snapshot.error === 'string' && snapshot.error !== '' ? snapshot.error : null,
828
+ approvals: bridgeApprovalBoard.snapshot(),
829
+ approvalHoldMs: DEFAULT_HOLD_MS,
830
+ status: 'idle',
831
+ statusText: '',
832
+ error: null,
833
+ sessionId: snapshot.sessionId ?? askDialog.sessionId ?? null,
834
+ uiVersion: uiVersion(),
835
+ dshVersion: dshVersion(),
836
+ };
837
+ }
838
+
839
+ /** 修订号:对话条目 / 待决授权 / 开关状态任何一处变了就变。 */
840
+ function askRevision() {
841
+ return bridgeThread.rev(watchedSessions()) * 8 + bridgeApprovalBoard.rev() * 8 + (askDialog.open ? 1 : 0) + (askDialog.mode === 'file' ? 2 : 0);
842
+ }
843
+
844
+ /** 对话框刷新它的"我在看"时间戳(每趟 /state 都算一次)。 */
845
+ function refreshDialogWatch() {
846
+ bridgeWatch.dialogIds = askDialog.open && askDialog.sessionId !== null ? [askDialog.sessionId] : [];
847
+ bridgeWatch.at = Date.now();
848
+ }
849
+
850
+ /**
851
+ * 回答一条待决授权 —— 桥的路由与对话框的路由共用同一套校验(见 lib/bridge-approval.mjs 的安全约束)。
852
+ * @returns {{status: number, body: object}}
853
+ */
854
+ function answerApproval(id, outcome) {
855
+ if (typeof id !== 'string' || id === '') return { status: 400, body: { ok: false, error: '需要 id 字段' } };
856
+ if (!PANEL_OUTCOMES.includes(outcome)) {
857
+ return { status: 400, body: { ok: false, error: `outcome 只能取 ${PANEL_OUTCOMES.join(' | ')}` } };
858
+ }
859
+ if (bridgeApprovalBoard.get(id) === null) return { status: 409, body: { ok: false, error: '未知或已处理的授权请求' } };
860
+ const ok = bridgeApprovalBoard.answer(id, outcome);
861
+ return { status: ok ? 200 : 409, body: { ok, outcome: ok ? outcome : null } };
862
+ }
863
+
864
+ /** 面板产物(thread.js / thread.css):对话框把它们注入 DSH 页面,所以由 host 读盘给文本。 */
865
+ let askBundleCache = null;
866
+ function askBundle() {
867
+ if (askBundleCache !== null) return askBundleCache;
868
+ const dir = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'assets', 'extensions', 'dshcs-editor-bridge', 'webview');
869
+ const read = (name) => {
870
+ try {
871
+ return fs.readFileSync(path.join(dir, name), 'utf8');
872
+ } catch {
873
+ return null;
874
+ }
875
+ };
876
+ const js = read('thread.js');
877
+ const css = read('thread.css');
878
+ // 产物缺失(打包漏了 build:webview)→ 明确报出来,不返回半个面板。
879
+ if (js === null || css === null) {
880
+ console.warn(`[code-server] 「问 DSH」对话框的产物缺失:${dir}(请在源码里跑 pnpm run build:webview)`);
881
+ return null;
882
+ }
883
+ askBundleCache = { js, css };
884
+ return askBundleCache;
885
+ }
886
+
887
+
756
888
  /** 本部署的 DSH 版本(诊断用;面板要的是下面那个"界面版本")。 */
757
889
  function dshVersion() {
758
890
  try {
@@ -1746,14 +1878,14 @@ export async function apply(ctx, config) {
1746
1878
  const watch = Array.isArray(body.watch) ? body.watch.filter((id) => typeof id === 'string' && id !== '') : [];
1747
1879
  bridgeWatch.ids = watch.slice(0, 4);
1748
1880
  bridgeWatch.at = Date.now();
1749
- bridgeThread.sync(bridgeWatch.ids);
1881
+ bridgeThread.sync(watchedSessions());
1750
1882
  // 对话流走独立字段:每个会话给有界条目(见 lib/bridge-thread.mjs),
1751
1883
  // 授权待决列表也一并回去(面板显示卡片)。
1752
1884
  return jsonResponse({
1753
1885
  ok: true,
1754
1886
  events,
1755
1887
  thread: bridgeThread.supported()
1756
- ? bridgeThread.snapshot(bridgeWatch.ids)
1888
+ ? bridgeThread.snapshot(watchedSessions())
1757
1889
  : { available: false, sessionId: null, cursor: null, entries: [], error: '宿主没有 sessionController.follow(DSH 版本过旧)' },
1758
1890
  approvals: bridgeApprovalBoard.snapshot(),
1759
1891
  // 授权卡片要在面板上倒计时,窗口长度由 host 给(面板不猜)。
@@ -1761,6 +1893,8 @@ export async function apply(ctx, config) {
1761
1893
  dshVersion: dshVersion(),
1762
1894
  // 界面版本:面板据此提示"内置渲染器与当前 DSH 界面版本不一致"。
1763
1895
  uiVersion: uiVersion(),
1896
+ // 能力探测(0.3.24):宿主支持 DSH 页面里的悬浮对话框 ⇒ 扩展改用 ask-open 事件而不是开 webview 面板。
1897
+ askDialog: true,
1764
1898
  });
1765
1899
  }
1766
1900
 
@@ -1821,17 +1955,93 @@ export async function apply(ctx, config) {
1821
1955
  } catch {
1822
1956
  return jsonResponse({ ok: false, error: '请求体不是合法 JSON' }, 400);
1823
1957
  }
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);
1958
+ const verdict = answerApproval(
1959
+ typeof body.id === 'string' ? body.id : '',
1960
+ typeof body.outcome === 'string' ? body.outcome : '',
1961
+ );
1962
+ return jsonResponse(verdict.body, verdict.status);
1963
+ }
1964
+
1965
+ // ---------------------------------------------------------------- 「问 DSH」对话框(client 半部)
1966
+
1967
+ /** 对话框每 900ms 问一次:给修订号,没变就只回一个数字(不重传几十 KB 的对话流)。 */
1968
+ async function handleAskState(request) {
1969
+ refreshDialogWatch();
1970
+ const current = askRevision();
1971
+ let rev = null;
1972
+ try {
1973
+ const raw = new URL(request.url).searchParams.get('rev');
1974
+ const parsed = raw === null ? Number.NaN : Number.parseInt(raw, 10);
1975
+ if (Number.isSafeInteger(parsed) && parsed >= 0) rev = parsed;
1976
+ } catch {
1977
+ rev = null;
1829
1978
  }
1830
- if (bridgeApprovalBoard.get(id) === null) {
1831
- return jsonResponse({ ok: false, error: '未知或已处理的授权请求' }, 409);
1979
+ if (rev !== null && rev === current) return jsonResponse({ ok: true, changed: false, rev: current });
1980
+ return jsonResponse({ ...askState(), changed: true, rev: current });
1981
+ }
1982
+
1983
+ /** 对话框发一条提问:上下文取宿主缓存里的编辑器状态(扩展每 600ms 上报)。 */
1984
+ async function handleAskSend(request) {
1985
+ let body;
1986
+ try {
1987
+ body = await readJsonBody(request);
1988
+ } catch {
1989
+ return jsonResponse({ ok: false, error: '请求体不是合法 JSON' }, 400);
1832
1990
  }
1833
- const ok = bridgeApprovalBoard.answer(id, outcome);
1834
- return jsonResponse({ ok, outcome: ok ? outcome : null }, ok ? 200 : 409);
1991
+ const text = typeof body.text === 'string' ? body.text.trim() : '';
1992
+ if (text === '') return jsonResponse({ ok: false, error: '需要 text 字段(要问 DSH 的话)' }, 400);
1993
+ const mode = body.mode === 'file' ? 'file' : askDialog.mode;
1994
+ const context = askContextFromCache(mode);
1995
+ const result = await deliverEditorPrompt(ctx, {
1996
+ text,
1997
+ file: context === null ? null : context.file,
1998
+ lineStart: context === null ? null : context.lineStart,
1999
+ lineEnd: context === null ? null : context.lineEnd,
2000
+ selection: context === null ? null : context.selection,
2001
+ languageId: context === null ? null : context.languageId,
2002
+ });
2003
+ if (result.ok === true && typeof result.sessionId === 'string' && result.sessionId !== '') {
2004
+ askDialog.sessionId = result.sessionId;
2005
+ askDialog.mode = mode;
2006
+ refreshDialogWatch();
2007
+ bridgeThread.watch(result.sessionId);
2008
+ bridgeApproval.intercept(result.agent, result.sessionId);
2009
+ }
2010
+ const { agent: _agent, ...payload } = result;
2011
+ void _agent;
2012
+ return jsonResponse(payload, result.ok === true ? 200 : (result.code === 'NO_AGENT' ? 409 : 200));
2013
+ }
2014
+
2015
+ /** 对话框回答授权(与桥的 /approve 同一套校验)。 */
2016
+ async function handleAskApprove(request) {
2017
+ let body;
2018
+ try {
2019
+ body = await readJsonBody(request);
2020
+ } catch {
2021
+ return jsonResponse({ ok: false, error: '请求体不是合法 JSON' }, 400);
2022
+ }
2023
+ const verdict = answerApproval(
2024
+ typeof body.id === 'string' ? body.id : '',
2025
+ typeof body.outcome === 'string' ? body.outcome : '',
2026
+ );
2027
+ return jsonResponse(verdict.body, verdict.status);
2028
+ }
2029
+
2030
+ /** 对话框关掉:不再看会话 ⇒ 待决授权按"没人看"交回官方链路(拦截器的轮询会立刻放行)。 */
2031
+ async function handleAskClose() {
2032
+ askDialog.open = false;
2033
+ askDialog.sessionId = null;
2034
+ refreshDialogWatch();
2035
+ return jsonResponse({ ok: true });
2036
+ }
2037
+
2038
+ /** 面板产物(thread.js / thread.css)给对话框注入用。 */
2039
+ async function handleAskBundle() {
2040
+ const bundle = askBundle();
2041
+ if (bundle === null) {
2042
+ return jsonResponse({ ok: false, error: '面板产物缺失:请在插件源码里跑 pnpm run build:webview,或重装插件' }, 503);
2043
+ }
2044
+ return jsonResponse({ ok: true, rev: askRevision(), ...bundle });
1835
2045
  }
1836
2046
 
1837
2047
  async function handleBridgeEvent(request) {
@@ -1840,6 +2050,15 @@ export async function apply(ctx, config) {
1840
2050
  const body = await readJsonBody(request);
1841
2051
  const kind = typeof body.kind === 'string' ? body.kind : '';
1842
2052
  if (kind === '') return jsonResponse({ ok: false, error: '需要 kind 字段' }, 400);
2053
+ // 右键「问 DSH」→ 扩展上报 ask-open:host 打开 DSH 页面里的对话框(client 半部轮询到就拿去显示)。
2054
+ if (kind === 'ask-open') {
2055
+ askDialog.open = true;
2056
+ askDialog.mode = body.mode === 'file' ? 'file' : 'selection';
2057
+ askDialog.at = Date.now();
2058
+ refreshDialogWatch();
2059
+ appendLog(`[bridge] ask-open mode=${askDialog.mode}\n`);
2060
+ return jsonResponse({ ok: true, mode: askDialog.mode, contextText: askContextLine(askDialog.mode) });
2061
+ }
1843
2062
  // 扩展上报的编辑器侧事件(打开/关闭文件等):目前只进日志尾,供诊断时回看。
1844
2063
  appendLog(`[bridge] ${kind}${typeof body.path === 'string' ? ` ${body.path}` : ''}\n`);
1845
2064
  return jsonResponse({ ok: true });
@@ -1854,6 +2073,13 @@ export async function apply(ctx, config) {
1854
2073
  { path: `${API_BASE}/setup`, methods: ['POST'], fetch: handleSetup },
1855
2074
  { path: `${API_BASE}/open-file`, methods: ['POST'], fetch: handleOpenFile },
1856
2075
  { path: `${API_BASE}/ui-mode`, methods: ['POST'], fetch: handleUiMode },
2076
+ // ---- 「问 DSH」对话框(0.3.24):DSH 页面里的 client 半部调这几条 ----
2077
+ // 这里**在 /api 下**是对的:调用方是 DSH 页面自己(同源 + cookie),与扩展宿主(必须走本机 IPC)不同。
2078
+ { path: `${API_BASE}/ask/state`, methods: ['GET'], fetch: handleAskState },
2079
+ { path: `${API_BASE}/ask/send`, methods: ['POST'], fetch: handleAskSend },
2080
+ { path: `${API_BASE}/ask/approve`, methods: ['POST'], fetch: handleAskApprove },
2081
+ { path: `${API_BASE}/ask/close`, methods: ['POST'], fetch: handleAskClose },
2082
+ { path: `${API_BASE}/ask/bundle`, methods: ['GET'], fetch: handleAskBundle },
1857
2083
  // ---- 编辑器桥的 4 条路由**不在 /api 下**(0.3.9 修正)----
1858
2084
  // 原因:Connection 给 `/api` 装了 cookie fence(无 cookie → 401),而扩展宿主是 Node 进程、
1859
2085
  // 永远拿不到浏览器 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.24",
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:15:46.539Z",
7
7
  "source": "registry",
8
8
  "node": "v24.21.0",
9
9
  "platform": "win32",