dsh-code-server-app 0.3.21 → 0.3.23

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 (42) hide show
  1. package/README.en.md +85 -18
  2. package/README.md +74 -20
  3. package/assets/extensions/dshcs-editor-bridge/extension.js +94 -50
  4. package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +353 -174
  5. package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +30 -4
  6. package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
  7. package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +21 -0
  8. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
  9. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
  10. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
  11. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
  12. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
  13. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
  14. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
  15. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
  16. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
  17. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
  18. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
  19. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
  20. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
  21. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
  22. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
  23. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
  24. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
  25. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
  26. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
  27. package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
  28. package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +199 -0
  29. package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +61 -0
  30. package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +8 -0
  31. package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +346 -0
  32. package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +115 -0
  33. package/assets/extensions/dshcs-editor-bridge/webview/thread.css +1 -0
  34. package/assets/extensions/dshcs-editor-bridge/webview/thread.js +464 -0
  35. package/lib/bridge-approval.mjs +215 -0
  36. package/lib/bridge-session.mjs +3 -1
  37. package/lib/bridge-thread.mjs +374 -0
  38. package/lib/bridge.mjs +15 -6
  39. package/lib/index.js +142 -17
  40. package/package.json +29 -6
  41. package/vendor/VENDOR.json +1 -1
  42. package/lib/bridge-answer.mjs +0 -155
@@ -13,8 +13,13 @@
13
13
  // 就什么都不做。状态栏不显示任何东西,更不弹通知。
14
14
  // 3. **绝不覆盖未保存改动**:agent 改了文件而该文档是脏的就只告警 + 给 diff,由用户决定。
15
15
  //
16
- // 「问 DSH」面板(0.2.0):右键命令打开一个 webview,提问走 `/ask`(host 侧以**用户输入**进对话),
17
- // 回答随 `/sync` 的 `answers` 字段回到面板(纯逻辑在 lib/ask-panel.js,可单测)。
16
+ // 「问 DSH」面板(0.2.0 起;0.2.3 起正文走 DSH 官方渲染器):
17
+ // - 提问走 `/ask`(host 侧以**用户输入**进对话);
18
+ // - 回答**不再是另一条通道**:host 用 `sessionController.follow` 把该会话的**新内容**
19
+ // 投影成条目,随 `/sync` 的 `thread` 回来,面板用官方 markdown 渲染器显示
20
+ // (产物由 scripts/build-webview.mjs 打成 webview/thread.js + thread.css);
21
+ // - 授权(写工作区外文件 / 执行命令):host 的 `approvals` 字段回到面板,用户在面板上
22
+ // 「允许一次 / 拒绝」,扩展调 `/approve` 提交(桥里唯一的非只读路由)。
18
23
  //
19
24
  // 只依赖 `vscode` 与 Node 内置模块;纯逻辑在 lib/ 下且不 require('vscode'),便于单测。
20
25
 
@@ -22,6 +27,7 @@
22
27
 
23
28
  const vscode = require('vscode');
24
29
  const crypto = require('crypto');
30
+ const fs = require('fs');
25
31
  const path = require('path');
26
32
 
27
33
  const {
@@ -32,14 +38,12 @@ const {
32
38
  const { createProjector } = require('./lib/context-model.js');
33
39
  const { createDiffCache, describeChange } = require('./lib/diff-model.js');
34
40
  const {
35
- applyAnswers,
41
+ applySync,
36
42
  createPanelState,
37
- currentTurn,
38
- describeContext,
43
+ failPanel,
39
44
  panelPayload,
40
- pushQuestion,
45
+ pendingQuestion,
41
46
  renderPanelHtml,
42
- statusText,
43
47
  } = require('./lib/ask-panel.js');
44
48
 
45
49
  /** 状态栏项(仅在桥连通时显示)。 */
@@ -52,8 +56,10 @@ let client = null;
52
56
  let pollTimer = null;
53
57
  /** 上次成功轮询的时间(状态栏 tooltip 用)。 */
54
58
  let lastPollAt = 0;
55
- /** 「问 DSH」面板:null = 没开。`{panel, state}`(状态模型见 lib/ask-panel.js)。 */
59
+ /** 「问 DSH」面板:null = 没开。`{panel, state, mode}`(状态模型见 lib/ask-panel.js)。 */
56
60
  let askPanel = null;
61
+ /** 扩展根目录(activate 时记下;面板要按它取 webview 产物 URI)。 */
62
+ let extensionRoot = null;
57
63
  /** 是否已经确认过 host 端点可达(避免把"IDE 刚起、扩展先加载"误判为断线)。 */
58
64
  let connected = false;
59
65
  /** 诊断集合缓存:host 请求时现算,这里只做"有没有变化"的计数上报。 */
@@ -357,6 +363,10 @@ async function pollOnce() {
357
363
  }),
358
364
  diagnostics,
359
365
  at: Date.now(),
366
+ // 面板声明它要看哪个会话:host 据此对齐 `sessionController.follow` 订阅,
367
+ // 并用"有没有人在看"决定授权请求是先问面板还是直接交给 DSH 界面
368
+ // (见 host 侧 lib/bridge-thread.mjs / lib/bridge-approval.mjs)。
369
+ watch: askPanel !== null && askPanel.state.sessionId !== null ? [askPanel.state.sessionId] : [],
360
370
  };
361
371
  const result = await client.sync(payload);
362
372
  if (result.ok !== true) {
@@ -378,20 +388,9 @@ async function pollOnce() {
378
388
  await handleEvent(event);
379
389
  }
380
390
  if ((result.events ?? []).length > 0) client.persist();
381
- // 回答同步:host 把 agent 的正文放在 answers 里(见 lib/bridge-answer.mjs),
382
- // 面板每趟轮询刷新一次 —— 不需要第二个定时器,也不需要流式协议。
383
- if (askPanel !== null) {
384
- if (result.answers === undefined) {
385
- // 旧版 host(≤0.3.18)没有这个字段:此时面板会永远停在"正在回答…",必须说出原因。
386
- askPanel.stalePolls = (askPanel.stalePolls ?? 0) + 1;
387
- if (askPanel.stalePolls === 15 && askPanel.state.sessionId !== null && askPanel.state.status === 'thinking') {
388
- failAskPanel('宿主没有回复同步能力(插件版本过旧?):请重启 dsh web 让 host 侧升级到 0.3.19 以上');
389
- }
390
- } else {
391
- askPanel.stalePolls = 0;
392
- if (applyAnswers(askPanel.state, result.answers, askPanel.state.sessionId)) refreshAskPanel();
393
- }
394
- }
391
+ // 对话流 + 授权待决:host 每趟把被观看会话的**新内容**与待决授权一起回来
392
+ // (见 host 侧 lib/bridge-thread.mjs / lib/bridge-approval.mjs),面板有变化才重画。
393
+ if (askPanel !== null && applySync(askPanel.state, result)) refreshAskPanel();
395
394
  }
396
395
 
397
396
  function startPolling(context) {
@@ -447,33 +446,23 @@ function captureAskContext(mode) {
447
446
  /** 把状态推给面板(增量更新:输入框与滚动位置都不受影响)。 */
448
447
  function refreshAskPanel() {
449
448
  if (askPanel === null) return;
450
- const payload = panelPayload(askPanel.state);
451
- payload.contextText = askPanel.state.context === null
452
- ? '来自编辑器'
453
- : `来自编辑器:${describeContext(askPanel.state.context)}`;
454
- payload.statusText = askPanel.state.status === 'error'
455
- ? (askPanel.state.error ?? '出错了')
456
- : statusText(askPanel.state);
457
- void askPanel.panel.webview.postMessage(payload);
449
+ void askPanel.panel.webview.postMessage(panelPayload(askPanel.state));
458
450
  }
459
451
 
460
- /** 面板进入错误态:状态行 + 当前轮都标上原因(绝不静默)。 */
452
+ /** 面板进入错误态:状态行 + 最新一条本地提问都标上原因(绝不静默)。 */
461
453
  function failAskPanel(message) {
462
454
  if (askPanel === null) return;
463
- askPanel.state.status = 'error';
464
- askPanel.state.error = message;
465
- const turn = currentTurn(askPanel.state);
466
- if (turn !== null) turn.error = message;
455
+ failPanel(askPanel.state, message);
467
456
  log(message);
468
457
  refreshAskPanel();
469
458
  }
470
459
 
471
- /** 面板收到一条提问:投递到 DSH,并把这一轮放进状态里等回答。 */
460
+ /** 面板收到一条提问:投递到 DSH,**本地先乐观显示**,回答由 host 的对话流回填。 */
472
461
  async function sendAskFromPanel(text) {
473
462
  if (askPanel === null || client === null) return;
474
463
  // 用面板自己的意图(selection / file)取上下文:文件级提问永远不带行号。
475
464
  const context = captureAskContext(askPanel.mode) ?? askPanel.state.context;
476
- const turn = pushQuestion(askPanel.state, text, context);
465
+ pendingQuestion(askPanel.state, text, context);
477
466
  refreshAskPanel();
478
467
  try {
479
468
  const result = await client.ask({
@@ -489,21 +478,44 @@ async function sendAskFromPanel(text) {
489
478
  askPanel.state.status = 'thinking';
490
479
  log(`已投递编辑器消息(session=${result.sessionId ?? '?'})`);
491
480
  } else {
492
- askPanel.state.status = 'error';
493
- askPanel.state.error = `未投递:${result.error ?? '未知原因'}`;
494
- turn.error = askPanel.state.error;
481
+ failAskPanel(`未投递:${result.error ?? '未知原因'}`);
482
+ return;
495
483
  }
496
484
  } catch (error) {
497
485
  const status = error && error.status;
498
- const message = status === 409
486
+ failAskPanel(status === 409
499
487
  ? 'DSH 里没有可投递的会话:请先在 DSH 里打开或新建一个会话。'
500
488
  : (status === 401 || status === 503
501
489
  ? '编辑器桥尚未就绪,请稍后重试。'
502
- : `投递失败:${error && error.message ? error.message : error}`);
503
- // 桥没就绪时挂断会话:面板据此显示原因,轮询恢复后下一次发送即可。
504
- askPanel.state.status = 'error';
505
- askPanel.state.error = message;
506
- turn.error = message;
490
+ : `投递失败:${error && error.message ? error.message : error}`));
491
+ return;
492
+ }
493
+ refreshAskPanel();
494
+ }
495
+
496
+ /**
497
+ * 面板对一条授权请求的决策 → `POST /approve`。
498
+ *
499
+ * 只接受白名单的两个值(host 侧同样只认这两个);失败必须说出来:面板里点不动的时候,
500
+ * 用户唯一的线索就是这行状态/日志。
501
+ */
502
+ async function decideApproval(id, outcome) {
503
+ if (askPanel === null || client === null) return;
504
+ if (outcome !== 'allowed-once' && outcome !== 'rejected') return;
505
+ try {
506
+ await client.approve(id, outcome);
507
+ log(`面板授权决策已提交:${id} → ${outcome}`);
508
+ } catch (error) {
509
+ const status = error && error.status;
510
+ const message = status === 409
511
+ ? '这条授权请求已经过期或已被处理(DSH 界面里会弹同一张卡片)。'
512
+ : (status === 401 || status === 503
513
+ ? '编辑器桥尚未就绪,授权没有提交。'
514
+ : `授权提交失败:${error && error.message ? error.message : error}`);
515
+ // 卡片已经没意义了:本地撤掉,避免用户反复点。
516
+ askPanel.state.approvals = askPanel.state.approvals.filter((item) => item.id !== id);
517
+ failAskPanel(message);
518
+ return;
507
519
  }
508
520
  refreshAskPanel();
509
521
  }
@@ -515,16 +527,35 @@ async function sendAskFromPanel(text) {
515
527
  function openAskPanel(mode = 'selection') {
516
528
  const contextInfo = captureAskContext(mode);
517
529
  if (askPanel === null) {
530
+ // webview 产物是构建出来的(scripts/build-webview.mjs)。缺了就别开一个空白面板。
531
+ const webviewDir = vscode.Uri.joinPath(extensionRoot, 'webview');
532
+ const bundle = vscode.Uri.joinPath(webviewDir, 'thread.js');
533
+ const style = vscode.Uri.joinPath(webviewDir, 'thread.css');
534
+ if (!fs.existsSync(bundle.fsPath) || !fs.existsSync(style.fsPath)) {
535
+ const message = `面板产物缺失(${bundle.fsPath}):请重新安装插件,或在源码里跑 pnpm run build:webview`;
536
+ log(message);
537
+ void vscode.window.showErrorMessage(message);
538
+ return;
539
+ }
518
540
  const panel = vscode.window.createWebviewPanel(
519
541
  'dshAsk',
520
- 'DSH 提问',
521
- { viewColumn: vscode.ViewColumn.Beside, preserveFocus: false },
522
- { enableScripts: true, retainContextWhenHidden: true },
542
+ 'DSH 对话',
543
+ // 对话框形状(0.2.4):开在**当前编辑器组**,占满工作台宽度 —— 不再用 `Beside` 挤成一条侧栏。
544
+ // 面板内容自己居中限宽,看起来就是编辑器上的一扇对话窗;右上角的 ✕ 关掉它。
545
+ { viewColumn: vscode.ViewColumn.Active, preserveFocus: false },
546
+ {
547
+ enableScripts: true,
548
+ retainContextWhenHidden: true,
549
+ // 只允许加载扩展自己的 webview 目录(产物 + 字体),不给别的本地文件开口子。
550
+ localResourceRoots: [webviewDir],
551
+ },
523
552
  );
524
553
  askPanel = { panel, state: createPanelState(), mode };
525
554
  panel.webview.html = renderPanelHtml({
526
555
  cspSource: panel.webview.cspSource,
527
556
  nonce: crypto.randomBytes(16).toString('base64url'),
557
+ scriptUri: panel.webview.asWebviewUri(bundle).toString(),
558
+ styleUri: panel.webview.asWebviewUri(style).toString(),
528
559
  });
529
560
  panel.webview.onDidReceiveMessage((message) => {
530
561
  if (message === null || typeof message !== 'object') return;
@@ -535,6 +566,15 @@ function openAskPanel(mode = 'selection') {
535
566
  if (message.type === 'ask' && typeof message.text === 'string') {
536
567
  const text = message.text.trim();
537
568
  if (text !== '') void sendAskFromPanel(text);
569
+ return;
570
+ }
571
+ if (message.type === 'approve' && typeof message.id === 'string') {
572
+ void decideApproval(message.id, message.outcome);
573
+ return;
574
+ }
575
+ if (message.type === 'close') {
576
+ // 面板上的 ✕:关掉它(= 不再看这个会话 ⇒ 待决授权立刻交回 DSH 界面,不用干等窗口)。
577
+ panel.dispose();
538
578
  }
539
579
  });
540
580
  panel.onDidDispose(() => {
@@ -543,7 +583,8 @@ function openAskPanel(mode = 'selection') {
543
583
  }
544
584
  askPanel.state.context = contextInfo;
545
585
  askPanel.mode = mode;
546
- askPanel.panel.reveal(vscode.ViewColumn.Beside, false);
586
+ // 已经开着就提到前面(对话框不重复开)。
587
+ askPanel.panel.reveal(vscode.ViewColumn.Active, false);
547
588
  refreshAskPanel();
548
589
  }
549
590
 
@@ -580,6 +621,9 @@ function activate(context) {
580
621
  output = vscode.window.createOutputChannel('DSH Editor Bridge');
581
622
  context.subscriptions.push(output);
582
623
 
624
+ // 面板 webview 的产物按扩展根目录取 URI(0.2.3 起正文走官方渲染器,产物是打包出来的)。
625
+ extensionRoot = context.extensionUri;
626
+
583
627
  statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 90);
584
628
  context.subscriptions.push(statusBar);
585
629