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.
- package/README.en.md +85 -18
- package/README.md +74 -20
- package/assets/extensions/dshcs-editor-bridge/extension.js +94 -50
- package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +353 -174
- package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +30 -4
- package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
- package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +21 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/fonts/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +199 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +61 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +8 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +346 -0
- package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +115 -0
- package/assets/extensions/dshcs-editor-bridge/webview/thread.css +1 -0
- package/assets/extensions/dshcs-editor-bridge/webview/thread.js +464 -0
- package/lib/bridge-approval.mjs +215 -0
- package/lib/bridge-session.mjs +3 -1
- package/lib/bridge-thread.mjs +374 -0
- package/lib/bridge.mjs +15 -6
- package/lib/index.js +142 -17
- package/package.json +29 -6
- package/vendor/VENDOR.json +1 -1
- 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
|
|
17
|
-
//
|
|
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
|
-
|
|
41
|
+
applySync,
|
|
36
42
|
createPanelState,
|
|
37
|
-
|
|
38
|
-
describeContext,
|
|
43
|
+
failPanel,
|
|
39
44
|
panelPayload,
|
|
40
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
522
|
-
|
|
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
|
-
|
|
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
|
|