dsh-code-server-app 0.3.58 → 0.3.63
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/LICENSE-Apache-2.0.txt +201 -0
- package/README.en.md +215 -98
- package/README.md +186 -94
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/assets/extensions/dshcs-editor-bridge/extension.js +192 -222
- package/assets/extensions/dshcs-editor-bridge/lib/bridge-client.js +43 -39
- package/assets/extensions/dshcs-editor-bridge/lib/fim-completion.js +264 -0
- package/assets/extensions/dshcs-editor-bridge/package.json +2 -2
- package/lib/bridge-session.mjs +55 -5
- package/lib/bridge.mjs +14 -7
- package/lib/client.js +1061 -90
- package/lib/fim-adapter.mjs +603 -0
- package/lib/index.js +366 -205
- package/package.json +21 -41
- package/vendor/VENDOR.json +5 -5
- package/assets/extensions/dshcs-editor-bridge/lib/ask-panel.js +0 -416
- package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +0 -22
- package/assets/extensions/dshcs-editor-bridge/webview/src/app.jsx +0 -242
- package/assets/extensions/dshcs-editor-bridge/webview/src/approval.jsx +0 -61
- package/assets/extensions/dshcs-editor-bridge/webview/src/official-tokens.css +0 -576
- package/assets/extensions/dshcs-editor-bridge/webview/src/panel.css +0 -380
- package/assets/extensions/dshcs-editor-bridge/webview/src/thread.jsx +0 -160
- package/assets/extensions/dshcs-editor-bridge/webview/thread.css +0 -1
- package/assets/extensions/dshcs-editor-bridge/webview/thread.js +0 -465
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
// dshcs-editor-bridge / webview/src/app.jsx —— 「问 DSH」面板(0.2.3)
|
|
2
|
-
//
|
|
3
|
-
// 面板 = 外壳(上下文 / 状态 / 输入框 / 授权卡片)+ **DSH 官方渲染的对话内容**。
|
|
4
|
-
// 对话数据来自宿主 `/sync` 的 `thread` 字段:只有**新内容**(0.3.22 起,历史不重放),
|
|
5
|
-
// 助手正文逐条交给官方 markdown 渲染器(thread.jsx)。
|
|
6
|
-
//
|
|
7
|
-
// 与扩展侧的分工(消息协议,postMessage):
|
|
8
|
-
// 扩展 → 面板:`{type:'state', …}`(面板状态载荷,形状见 lib/ask-panel.js 的 panelPayload)
|
|
9
|
-
// 面板 → 扩展:`{type:'ready'}` / `{type:'ask', text}` / `{type:'approve', id, outcome}`
|
|
10
|
-
//
|
|
11
|
-
// 纯逻辑(状态合并 / 载荷解析)在 lib/ask-panel.js 里,与扩展侧同一份,可在 Node 里单测;
|
|
12
|
-
// 本文件只做"把状态画出来"。
|
|
13
|
-
|
|
14
|
-
import { useEffect, useRef, useState } from 'react';
|
|
15
|
-
import { createRoot } from 'react-dom/client';
|
|
16
|
-
|
|
17
|
-
import panel from '../../lib/ask-panel.js';
|
|
18
|
-
import { ThreadEntry } from './thread.jsx';
|
|
19
|
-
import { ApprovalCard } from './approval.jsx';
|
|
20
|
-
import './panel.css';
|
|
21
|
-
|
|
22
|
-
/** 打包时的官方渲染器版本(由 scripts/build-webview.mjs 注入)。 */
|
|
23
|
-
const RENDERER_VERSION = typeof __DSHCS_RENDERER_VERSION__ === 'string' ? __DSHCS_RENDERER_VERSION__ : '';
|
|
24
|
-
/** 打包时的官方 UI 包名(用于版本不一致时的提示文案)。 */
|
|
25
|
-
const RENDERER_PACKAGE = typeof __DSHCS_RENDERER_PACKAGE__ === 'string' ? __DSHCS_RENDERER_PACKAGE__ : '';
|
|
26
|
-
|
|
27
|
-
const vscode = acquireVsCodeApi();
|
|
28
|
-
|
|
29
|
-
/** 宿主形态(0.2.5):`'dsh'` = 跑在 DSH 页面里的浮动对话框里,否则是 VS Code webview 面板。 */
|
|
30
|
-
const HOST = typeof window.__DSHCS_HOST__ === 'string' ? window.__DSHCS_HOST__ : 'webview';
|
|
31
|
-
/** 挂载点:对话框把它的容器交进来;webview 里就是 #root。 */
|
|
32
|
-
const MOUNT = window.__DSHCS_MOUNT__ instanceof HTMLElement
|
|
33
|
-
? window.__DSHCS_MOUNT__
|
|
34
|
-
: document.getElementById('root');
|
|
35
|
-
|
|
36
|
-
/** 面板外壳的初始视图状态。 */
|
|
37
|
-
const INITIAL = panel.createViewState();
|
|
38
|
-
|
|
39
|
-
function App() {
|
|
40
|
-
const [view, setView] = useState(INITIAL);
|
|
41
|
-
const [now, setNow] = useState(() => Date.now());
|
|
42
|
-
const [draft, setDraft] = useState('');
|
|
43
|
-
const [sending, setSending] = useState(false);
|
|
44
|
-
const [scriptError, setScriptError] = useState(null);
|
|
45
|
-
const [decided, setDecided] = useState({});
|
|
46
|
-
const logRef = useRef(null);
|
|
47
|
-
const stickRef = useRef(true);
|
|
48
|
-
const boxRef = useRef(null);
|
|
49
|
-
|
|
50
|
-
// 对话框形态:壳子直接推状态(同一个 window,不走 postMessage —— DSH 页面里 postMessage 是公共广播)。
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (HOST !== 'dsh') return undefined;
|
|
53
|
-
window.__DSHCS_ASK_PUSH__ = (payload) => {
|
|
54
|
-
if (payload === null || typeof payload !== 'object') return;
|
|
55
|
-
setView((prev) => panel.applyPayload(prev, { ...payload, type: 'state' }));
|
|
56
|
-
};
|
|
57
|
-
return () => { delete window.__DSHCS_ASK_PUSH__; };
|
|
58
|
-
}, []);
|
|
59
|
-
|
|
60
|
-
// 扩展 → 面板的消息(webview 形态)。
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
const onMessage = (event) => {
|
|
63
|
-
const data = event.data;
|
|
64
|
-
if (data === null || typeof data !== 'object') return;
|
|
65
|
-
if (data.type === 'state') setView((prev) => panel.applyPayload(prev, data));
|
|
66
|
-
};
|
|
67
|
-
window.addEventListener('message', onMessage);
|
|
68
|
-
// 面板脚本自己出错也要说出来:静默空白是最难查的一种失败。
|
|
69
|
-
// **但"ResizeObserver loop completed with undelivered notifications"不是脚本错误**(0.3.41):
|
|
70
|
-
// 那是 Chromium 在布局收敛时投递的良性告警(官方渲染器的视口高亮/折叠行都会触发它),
|
|
71
|
-
// 浏览器自己会重试,重试后一帧内就恢复。把它当成"面板脚本出错"会误导用户去查一个不存在的问题。
|
|
72
|
-
const BENIGN = /ResizeObserver loop/i;
|
|
73
|
-
const onError = (event) => {
|
|
74
|
-
const message = event && event.message ? String(event.message) : '未知';
|
|
75
|
-
if (BENIGN.test(message)) return;
|
|
76
|
-
setScriptError(`面板脚本出错:${message}`);
|
|
77
|
-
};
|
|
78
|
-
const onRejection = (event) => {
|
|
79
|
-
const reason = event && event.reason ? String(event.reason) : '未知';
|
|
80
|
-
if (BENIGN.test(reason)) return;
|
|
81
|
-
setScriptError(`面板脚本出错:${reason}`);
|
|
82
|
-
};
|
|
83
|
-
window.addEventListener('error', onError);
|
|
84
|
-
window.addEventListener('unhandledrejection', onRejection);
|
|
85
|
-
vscode.postMessage({ type: 'ready' });
|
|
86
|
-
return () => {
|
|
87
|
-
window.removeEventListener('message', onMessage);
|
|
88
|
-
window.removeEventListener('error', onError);
|
|
89
|
-
window.removeEventListener('unhandledrejection', onRejection);
|
|
90
|
-
};
|
|
91
|
-
}, []);
|
|
92
|
-
|
|
93
|
-
// 暗色主题:官方令牌表用 body[data-ds-dark-theme] 切换调色板。
|
|
94
|
-
// **只在 webview 里做**:在 DSH 页面里那个属性属于 DSH 自己,面板不能去改它(会翻掉整个界面主题)。
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (HOST === 'dsh') return undefined;
|
|
97
|
-
const sync = () => {
|
|
98
|
-
const dark = document.body.classList.contains('vscode-dark')
|
|
99
|
-
|| document.body.classList.contains('vscode-high-contrast');
|
|
100
|
-
if (dark) document.body.setAttribute('data-ds-dark-theme', '');
|
|
101
|
-
else document.body.removeAttribute('data-ds-dark-theme');
|
|
102
|
-
};
|
|
103
|
-
sync();
|
|
104
|
-
const observer = new MutationObserver(sync);
|
|
105
|
-
observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
|
106
|
-
return () => observer.disconnect();
|
|
107
|
-
}, []);
|
|
108
|
-
|
|
109
|
-
// 授权倒计时:只在有卡片时跑。
|
|
110
|
-
useEffect(() => {
|
|
111
|
-
if (view.approvals.length === 0) return undefined;
|
|
112
|
-
const timer = window.setInterval(() => setNow(Date.now()), 250);
|
|
113
|
-
return () => window.clearInterval(timer);
|
|
114
|
-
}, [view.approvals.length]);
|
|
115
|
-
|
|
116
|
-
// 已提交的决策:卡片消失后清掉(不然 decided 会一直涨)。
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
setDecided((prev) => {
|
|
119
|
-
const ids = new Set(view.approvals.map((item) => item.id));
|
|
120
|
-
const next = {};
|
|
121
|
-
let changed = false;
|
|
122
|
-
for (const [id, value] of Object.entries(prev)) {
|
|
123
|
-
if (ids.has(id)) next[id] = value;
|
|
124
|
-
else changed = true;
|
|
125
|
-
}
|
|
126
|
-
return changed ? next : prev;
|
|
127
|
-
});
|
|
128
|
-
}, [view.approvals]);
|
|
129
|
-
|
|
130
|
-
// 贴底滚动:用户主动往上翻时不打扰。
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
const node = logRef.current;
|
|
133
|
-
if (node !== null && stickRef.current) node.scrollTop = node.scrollHeight;
|
|
134
|
-
}, [view.entries]);
|
|
135
|
-
|
|
136
|
-
// 任何一趟宿主状态回来都解除"发送中"的按钮锁(0.3.44)。
|
|
137
|
-
// 老写法盯着 `view.status` 变化 —— 而对话框形态下宿主回的 status **一直是 'idle'**,
|
|
138
|
-
// 于是那个 effect 永远不再触发:第一次发送之后按钮就永久变灰(实测"只能发送一次,没法追问")。
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
setSending(false);
|
|
141
|
-
}, [view]);
|
|
142
|
-
// 兜底:万一某一趟状态没回来(宿主卡住/请求失败),最多 15 秒也把锁解开。
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
if (sending !== true) return undefined;
|
|
145
|
-
const timer = window.setTimeout(() => setSending(false), 15000);
|
|
146
|
-
return () => window.clearTimeout(timer);
|
|
147
|
-
}, [sending]);
|
|
148
|
-
|
|
149
|
-
const onScroll = () => {
|
|
150
|
-
const node = logRef.current;
|
|
151
|
-
if (node === null) return;
|
|
152
|
-
stickRef.current = node.scrollHeight - node.scrollTop - node.clientHeight < 48;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const submit = () => {
|
|
156
|
-
const text = draft.trim();
|
|
157
|
-
if (text === '' || sending) return;
|
|
158
|
-
setSending(true);
|
|
159
|
-
setDraft('');
|
|
160
|
-
vscode.postMessage({ type: 'ask', text });
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const onKeyDown = (event) => {
|
|
164
|
-
if (event.key === 'Enter' && event.shiftKey === false) {
|
|
165
|
-
event.preventDefault();
|
|
166
|
-
submit();
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const onDecide = (id, outcome) => {
|
|
171
|
-
setDecided((prev) => ({ ...prev, [id]: outcome }));
|
|
172
|
-
vscode.postMessage({ type: 'approve', id, outcome });
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const status = scriptError === null ? view.statusText : scriptError;
|
|
176
|
-
const versionNote = view.uiVersion !== null && view.uiVersion !== undefined && RENDERER_VERSION !== ''
|
|
177
|
-
&& view.uiVersion !== RENDERER_VERSION
|
|
178
|
-
? `面板内置的渲染器是 ${RENDERER_PACKAGE}@${RENDERER_VERSION},而当前 DSH 界面是 ${view.uiVersion}:`
|
|
179
|
-
+ '排版可能有差异(重建面板:pnpm run build:webview)。'
|
|
180
|
-
: null;
|
|
181
|
-
|
|
182
|
-
return (
|
|
183
|
-
<div className="dshcs-app">
|
|
184
|
-
<header className="dshcs-header">
|
|
185
|
-
<span className="dshcs-title">DSH</span>
|
|
186
|
-
<span className="dshcs-where">{view.contextText}</span>
|
|
187
|
-
{RENDERER_VERSION === '' ? null : <span className="dshcs-version">渲染器 {RENDERER_VERSION}</span>}
|
|
188
|
-
<button
|
|
189
|
-
type="button"
|
|
190
|
-
className="dshcs-close"
|
|
191
|
-
title="关闭(Shift+Esc)"
|
|
192
|
-
aria-label="关闭"
|
|
193
|
-
onClick={() => vscode.postMessage({ type: 'close' })}
|
|
194
|
-
>
|
|
195
|
-
✕
|
|
196
|
-
</button>
|
|
197
|
-
</header>
|
|
198
|
-
|
|
199
|
-
{view.threadError === null ? null : <div className="dshcs-warn">{view.threadError}</div>}
|
|
200
|
-
{versionNote === null ? null : <div className="dshcs-warn">{versionNote}</div>}
|
|
201
|
-
|
|
202
|
-
<main className="dshcs-log" ref={logRef} onScroll={onScroll}>
|
|
203
|
-
{view.entries.length === 0
|
|
204
|
-
? <div className="dshcs-empty">在下面提问:DSH 的回答会像 DSH 界面那样显示在这里(思考折叠、正文按官方渲染)。</div>
|
|
205
|
-
: view.entries.map((entry, index) => (
|
|
206
|
-
<ThreadEntry key={`${entry.role}-${index}-${entry.callId ?? entry.approvalId ?? ''}`} entry={entry} />
|
|
207
|
-
))}
|
|
208
|
-
</main>
|
|
209
|
-
|
|
210
|
-
{view.approvals.map((item) => (
|
|
211
|
-
<ApprovalCard
|
|
212
|
-
key={item.id}
|
|
213
|
-
item={item}
|
|
214
|
-
holdMs={view.approvalHoldMs}
|
|
215
|
-
now={now}
|
|
216
|
-
decided={decided[item.id] ?? null}
|
|
217
|
-
onDecide={onDecide}
|
|
218
|
-
/>
|
|
219
|
-
))}
|
|
220
|
-
|
|
221
|
-
<div className="dshcs-status" data-status={view.status}>{status}</div>
|
|
222
|
-
|
|
223
|
-
<footer className="dshcs-footer">
|
|
224
|
-
<textarea
|
|
225
|
-
ref={boxRef}
|
|
226
|
-
value={draft}
|
|
227
|
-
placeholder="问 DSH…(Enter 发送,Shift+Enter 换行)"
|
|
228
|
-
onChange={(event) => setDraft(event.target.value)}
|
|
229
|
-
onKeyDown={onKeyDown}
|
|
230
|
-
autoFocus
|
|
231
|
-
/>
|
|
232
|
-
<button type="button" disabled={sending || draft.trim() === ''} onClick={submit}>发送</button>
|
|
233
|
-
</footer>
|
|
234
|
-
</div>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (MOUNT === null || MOUNT === undefined) {
|
|
239
|
-
console.error('[dshcs] 面板挂载点不存在(#root 或 window.__DSHCS_MOUNT__)');
|
|
240
|
-
} else {
|
|
241
|
-
createRoot(MOUNT).render(<App />);
|
|
242
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// dshcs-editor-bridge / webview/src/approval.jsx —— 待决授权卡片(0.2.3;0.2.4 修"失效")
|
|
2
|
-
//
|
|
3
|
-
// 背景(见 lib/bridge-approval.mjs):DSH 的敏感动作(写工作区外的文件、执行命令)会走
|
|
4
|
-
// `approval/request` 这条 agent 作用域的 waterfall。面板在旁边看的时候先接住它:
|
|
5
|
-
// - 用户在这张卡片上点「允许一次 / 拒绝」→ 立刻生效(官方卡片不再出现);
|
|
6
|
-
// - **面板关掉**(或等满宿主给的窗口,默认 5 分钟)→ 交回官方链路,DSH 界面里照旧弹同一张卡片。
|
|
7
|
-
//
|
|
8
|
-
// 0.2.3 的教训(用户实测:"授权框失效了"):卡片自己按 8 秒判过期 → 读完再点,按钮已经是灰的。
|
|
9
|
-
// 现在**不再由前端判过期**:只要卡片还在(宿主这边仍未决),按钮就可点;请求被交回官方链路时
|
|
10
|
-
// 它会从 `approvals` 里消失,面板改为显示审计行。倒计时只是"还有多久交回官方链路"的提示,
|
|
11
|
-
// 不再决定能不能点。
|
|
12
|
-
//
|
|
13
|
-
// 卡片只做两件事:**显示**这次请求要干什么(工具名 + 原因),**回答**它。
|
|
14
|
-
// 没有任何"记住这次选择""以后都允许"的入口 —— 授权只对这一次动作有效。
|
|
15
|
-
|
|
16
|
-
/** 剩余时间文案(超过一分钟就 mm:ss,避免秒数跳动带来的紧张感)。 */
|
|
17
|
-
function remainingText(item, holdMs, now) {
|
|
18
|
-
const at = Number.isSafeInteger(item.at) ? item.at : now;
|
|
19
|
-
const left = Math.max(0, at + holdMs - now);
|
|
20
|
-
if (left === 0) return '已交回 DSH 界面';
|
|
21
|
-
const totalSeconds = Math.ceil(left / 1000);
|
|
22
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
23
|
-
const seconds = totalSeconds % 60;
|
|
24
|
-
return minutes > 0
|
|
25
|
-
? `${minutes} 分 ${String(seconds).padStart(2, '0')} 秒后交回 DSH 界面`
|
|
26
|
-
: `${seconds} 秒后交回 DSH 界面`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function ApprovalCard({ item, holdMs, now, decided, onDecide }) {
|
|
30
|
-
const choice = decided ?? null;
|
|
31
|
-
// 已提交(点了但宿主还没确认):锁住按钮避免连点;这张卡片何时消失由宿主决定。
|
|
32
|
-
const locked = choice !== null;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className="dshcs-approval" data-decided={choice ?? undefined}>
|
|
36
|
-
<div className="dshcs-approval-head">
|
|
37
|
-
<span className="dshcs-approval-title">需要你的授权</span>
|
|
38
|
-
<span className="dshcs-approval-timer">{remainingText(item, holdMs, now)}</span>
|
|
39
|
-
</div>
|
|
40
|
-
<div className="dshcs-approval-body">
|
|
41
|
-
<span className="dshcs-approval-tool">{item.toolName}</span>
|
|
42
|
-
{item.reason === '' ? null : <div className="dshcs-approval-reason">{item.reason}</div>}
|
|
43
|
-
</div>
|
|
44
|
-
<div className="dshcs-approval-actions">
|
|
45
|
-
<button type="button" disabled={locked} onClick={() => onDecide(item.id, 'allowed-once')}>
|
|
46
|
-
允许一次
|
|
47
|
-
</button>
|
|
48
|
-
<button type="button" disabled={locked} onClick={() => onDecide(item.id, 'rejected')}>
|
|
49
|
-
拒绝
|
|
50
|
-
</button>
|
|
51
|
-
{choice === null
|
|
52
|
-
? <span className="dshcs-approval-note">只对这一次动作有效;关掉面板即交回 DSH 界面</span>
|
|
53
|
-
: (
|
|
54
|
-
<span className="dshcs-approval-note">
|
|
55
|
-
{choice === 'allowed-once' ? '已允许一次(DSH 继续执行)' : '已拒绝'}
|
|
56
|
-
</span>
|
|
57
|
-
)}
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|