dsh-builtin-browser 0.1.23 → 0.3.0
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 +91 -3
- package/README.md +98 -4
- package/client.js +334 -0
- package/cordis.patch.yml +24 -5
- package/desktop-bridge/install.mjs +98 -0
- package/desktop-bridge/plugin-browser-bridge.js +385 -0
- package/docs/README.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/tool-reference.md +2 -1
- package/lib/browser/runtime.d.ts +18 -1
- package/lib/browser/runtime.js +19 -0
- package/lib/browser/types.d.ts +33 -0
- package/lib/browser-electron/bridge-connection.d.ts +58 -0
- package/lib/browser-electron/bridge-connection.js +133 -0
- package/lib/browser-electron/desktop-bridge-host.d.ts +124 -0
- package/lib/browser-electron/desktop-bridge-host.js +297 -0
- package/lib/browser-electron/entry.js +195 -11
- package/lib/browser-electron/history-store.d.ts +75 -0
- package/lib/browser-electron/history-store.js +162 -0
- package/lib/browser-electron/host-main.js +28 -1
- package/lib/browser-electron/provider.d.ts +122 -1
- package/lib/browser-electron/provider.js +233 -21
- package/lib/browser-electron/remote-host.d.ts +17 -1
- package/lib/browser-electron/remote-host.js +81 -9
- package/lib/browser-electron/settings-store.d.ts +92 -0
- package/lib/browser-electron/settings-store.js +183 -0
- package/lib/browser-electron/system-browser.d.ts +100 -0
- package/lib/browser-electron/system-browser.js +322 -0
- package/lib/browser-electron/virtual-cursor.d.ts +67 -0
- package/lib/browser-electron/virtual-cursor.js +141 -0
- package/lib/index.d.ts +16 -2
- package/lib/index.js +18 -2
- package/lib/tool-browser/index.js +122 -16
- package/package.json +14 -1
- package/src/browser/runtime.ts +22 -0
- package/src/browser/types.ts +31 -0
- package/src/browser-electron/bridge-connection.ts +145 -0
- package/src/browser-electron/desktop-bridge-host.ts +305 -0
- package/src/browser-electron/entry.ts +217 -11
- package/src/browser-electron/history-store.ts +171 -0
- package/src/browser-electron/host-main.ts +26 -1
- package/src/browser-electron/provider.ts +284 -21
- package/src/browser-electron/remote-host.ts +84 -7
- package/src/browser-electron/settings-store.ts +227 -0
- package/src/browser-electron/system-browser.ts +318 -0
- package/src/browser-electron/virtual-cursor.ts +152 -0
- package/src/index.ts +20 -2
- package/src/tool-browser/index.ts +121 -15
package/client.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser settings panel (client half): the switches that decide how the shared
|
|
3
|
+
* browser behaves — history retention, login-state persistence, presentation,
|
|
4
|
+
* vision strategy and credential access.
|
|
5
|
+
*
|
|
6
|
+
* The host half owns the document and serves it at `/dsh-builtin-browser/settings`;
|
|
7
|
+
* this panel only renders it and sends patches, so the same file stays the single
|
|
8
|
+
* source of truth (and hand-editable when no panel is reachable).
|
|
9
|
+
*
|
|
10
|
+
* Registered into the settings page's `settings.section` slot with an order above
|
|
11
|
+
* the host's own rows, so it appears under them. Plain ModuleLoader bundle: no
|
|
12
|
+
* build step, matching how the host loads plugin client halves.
|
|
13
|
+
*/
|
|
14
|
+
window.__ModuleLoader__.load({ id: "dsh-builtin-browser", factory: (require) => {
|
|
15
|
+
var module = { exports: {} }
|
|
16
|
+
var exports = module.exports
|
|
17
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
|
|
18
|
+
|
|
19
|
+
const react = require("react")
|
|
20
|
+
const h = react.createElement
|
|
21
|
+
const { useCallback, useEffect, useState } = react
|
|
22
|
+
|
|
23
|
+
const name = "dsh-builtin-browser"
|
|
24
|
+
const inject = ["slots", "locale"]
|
|
25
|
+
const NS = "settings.dsh-builtin-browser"
|
|
26
|
+
const ROUTE = "/dsh-builtin-browser/settings"
|
|
27
|
+
let translate = (key) => key
|
|
28
|
+
|
|
29
|
+
const zh = {
|
|
30
|
+
nav: "浏览器",
|
|
31
|
+
title: "浏览器(Browser Use)",
|
|
32
|
+
lead: "这些开关控制共享浏览器如何工作;改动立即生效,不需要重启 DSH。",
|
|
33
|
+
loading: "读取设置…",
|
|
34
|
+
failed: "读取设置失败:",
|
|
35
|
+
retry: "重试",
|
|
36
|
+
saved: "已保存",
|
|
37
|
+
path: "设置文件",
|
|
38
|
+
"history.title": "浏览历史",
|
|
39
|
+
"history.enabled": "保留浏览历史",
|
|
40
|
+
"history.hint": "记录访问过的页面;关闭浏览器后仍可查询并重新打开,关闭此开关后不再记录新访问。",
|
|
41
|
+
"cookies.title": "登录状态",
|
|
42
|
+
"cookies.persist": "保留 cookies",
|
|
43
|
+
"cookies.hint": "关闭后每次启动浏览器都是未登录状态。",
|
|
44
|
+
"ui.title": "展示",
|
|
45
|
+
"ui.autoExpandOnce": "侧栏自动展开(每轮一次)",
|
|
46
|
+
"ui.autoExpandOnce.hint": "Agent 首次操作时自动展开;你手动收起后,本轮不再打扰。",
|
|
47
|
+
"ui.closeWithSession": "会话结束时关闭浏览器",
|
|
48
|
+
"ui.closeWithSession.hint": "关闭会释放浏览器进程;浏览历史与登录状态会保留。",
|
|
49
|
+
"ui.virtualCursor": "显示可视化鼠标",
|
|
50
|
+
"ui.virtualCursor.hint": "让 Agent 正在操作的位置可见(出现即代表它在接管该标签页)。",
|
|
51
|
+
"vision.title": "视觉策略",
|
|
52
|
+
"vision.strategy": "默认策略",
|
|
53
|
+
"vision.auto": "Auto(坐标与语义都允许;适合能读图的模型)",
|
|
54
|
+
"vision.nonVisual": "纯非视觉(拒绝坐标点击,只用 DOM / 无障碍树定位)",
|
|
55
|
+
"vision.hint": "纯非视觉下,坐标点击会被明确拒绝并提示改用语义定位,避免靠猜坐标点错。两种策略下 DOM、无障碍树、文本提取与结构化抓取的能力完全一致 —— 不需要图像输入也能完整操作页面。",
|
|
56
|
+
"credentials.title": "凭据",
|
|
57
|
+
"credentials.allowRead": "允许 Agent 读取 cookies / 导出登录状态",
|
|
58
|
+
"browser.title": "浏览器载体",
|
|
59
|
+
"browser.channel": "使用哪个浏览器",
|
|
60
|
+
"browser.bundled": "内置 Electron(随插件提供,无需安装)",
|
|
61
|
+
"browser.chrome": "本机 Chrome",
|
|
62
|
+
"browser.edge": "本机 Edge",
|
|
63
|
+
"browser.auto": "自动(优先 Chrome,其次 Edge)",
|
|
64
|
+
"browser.hint": "选本机浏览器时会以**独立 profile** 启动它:不会打开、占用或修改你日常的窗口、书签与登录状态,关闭也不会关掉你自己的浏览器;代价是它看不到你日常浏览器里已登录的站点,需要时请在那个窗口里登录一次(登录态会留在插件自己的 profile 里)。桌面端默认由官方侧栏承载页面,此选项优先于侧栏。",
|
|
65
|
+
"credentials.hint": "关闭后 browser_auth 会被拒绝。桌面端请注意:Agent 驱动的是侧栏里那一个页面(人机同页),因此它与该页面共用同一个 partition —— 这也正是它能读到登录态的原因,属于有意接受的沙箱边界变化;关闭本项即拒绝读取。",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const en = {
|
|
69
|
+
nav: "Browser",
|
|
70
|
+
title: "Browser (Browser Use)",
|
|
71
|
+
lead: "These switches decide how the shared browser behaves; changes apply immediately, no DSH restart.",
|
|
72
|
+
loading: "Loading settings…",
|
|
73
|
+
failed: "Could not load settings:",
|
|
74
|
+
retry: "Retry",
|
|
75
|
+
saved: "Saved",
|
|
76
|
+
path: "Settings file",
|
|
77
|
+
"history.title": "Browsing history",
|
|
78
|
+
"history.enabled": "Keep browsing history",
|
|
79
|
+
"history.hint": "Records visited pages so they can be found and reopened after the browser is closed; off means no new visits are recorded.",
|
|
80
|
+
"cookies.title": "Login state",
|
|
81
|
+
"cookies.persist": "Keep cookies",
|
|
82
|
+
"cookies.hint": "Off makes every browser start a signed-out session.",
|
|
83
|
+
"ui.title": "Presentation",
|
|
84
|
+
"ui.autoExpandOnce": "Auto-expand the side panel (once per task)",
|
|
85
|
+
"ui.autoExpandOnce.hint": "Expands when the agent first operates; after you collapse it, this task stays quiet.",
|
|
86
|
+
"ui.closeWithSession": "Close the browser when the session ends",
|
|
87
|
+
"ui.closeWithSession.hint": "Closing releases the browser process; history and login state survive.",
|
|
88
|
+
"ui.virtualCursor": "Show the synthetic cursor",
|
|
89
|
+
"ui.virtualCursor.hint": "Makes the agent's operating position visible (its presence means the agent has taken over that tab).",
|
|
90
|
+
"vision.title": "Vision strategy",
|
|
91
|
+
"vision.strategy": "Default strategy",
|
|
92
|
+
"vision.auto": "Auto (coordinates and semantics both allowed; for models that read images)",
|
|
93
|
+
"vision.nonVisual": "Non-visual only (coordinate clicks refused; locate via DOM / a11y tree)",
|
|
94
|
+
"vision.hint": "Under non-visual, a coordinate click is refused outright and the error says to use a semantic target — so nothing is clicked on a guessed position. DOM lookup, the accessibility tree, text extraction and structured scraping behave identically in both strategies: the whole page stays operable without image input.",
|
|
95
|
+
"credentials.title": "Credentials",
|
|
96
|
+
"credentials.allowRead": "Allow the agent to read cookies / export login state",
|
|
97
|
+
"browser.title": "Browser",
|
|
98
|
+
"browser.channel": "Which browser to use",
|
|
99
|
+
"browser.bundled": "Bundled Electron (ships with the plugin, nothing to install)",
|
|
100
|
+
"browser.chrome": "Installed Chrome",
|
|
101
|
+
"browser.edge": "Installed Edge",
|
|
102
|
+
"browser.auto": "Automatic (Chrome first, then Edge)",
|
|
103
|
+
"browser.hint": "Choosing an installed browser launches it with a **separate profile**: your everyday windows, bookmarks and logins are never opened, locked or modified, and closing the plugin never closes your browser. The trade-off is that it does not see sites you are already signed into there — sign in once in that window and the session stays in the plugin's own profile. On the desktop the shell's sidebar normally carries the page; this setting outranks it.",
|
|
104
|
+
"credentials.allowRead": "Allow the agent to read cookies / export login state",
|
|
105
|
+
"credentials.hint": "Off makes browser_auth refuse. On the desktop, note that the agent drives the sidebar's own page (one page for both parties), so it shares that page's partition — which is exactly why it can reach the login state. That is a deliberate sandbox-boundary change; this switch is how you refuse it.",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Read or patch the host-owned settings document. */
|
|
109
|
+
async function callSettings(patch) {
|
|
110
|
+
const response = await fetch(ROUTE, patch === undefined
|
|
111
|
+
? { method: "GET", headers: { accept: "application/json" } }
|
|
112
|
+
: {
|
|
113
|
+
method: "PUT",
|
|
114
|
+
headers: { "content-type": "application/json" },
|
|
115
|
+
body: JSON.stringify(patch),
|
|
116
|
+
})
|
|
117
|
+
const data = await response.json().catch(() => undefined)
|
|
118
|
+
if (data === undefined || data.ok !== true) {
|
|
119
|
+
throw new Error((data && data.error) || `HTTP ${response.status}`)
|
|
120
|
+
}
|
|
121
|
+
return data
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const styles = {
|
|
125
|
+
wrap: { display: "flex", flexDirection: "column", gap: "14px", padding: "4px 2px", fontSize: "13px" },
|
|
126
|
+
lead: { opacity: 0.75, lineHeight: 1.6, margin: 0 },
|
|
127
|
+
group: { border: "1px solid rgba(128,128,128,.28)", borderRadius: "8px", padding: "10px 12px" },
|
|
128
|
+
groupTitle: { margin: "0 0 8px", fontSize: "13px", fontWeight: 600 },
|
|
129
|
+
row: { display: "flex", gap: "10px", alignItems: "flex-start", padding: "5px 0" },
|
|
130
|
+
box: { marginTop: "2px" },
|
|
131
|
+
label: { display: "flex", flexDirection: "column", gap: "2px", cursor: "pointer" },
|
|
132
|
+
hint: { opacity: 0.68, lineHeight: 1.5 },
|
|
133
|
+
select: { marginTop: "4px", padding: "4px 6px", borderRadius: "6px", background: "transparent", color: "inherit", border: "1px solid rgba(128,128,128,.4)" },
|
|
134
|
+
status: { opacity: 0.7, minHeight: "18px" },
|
|
135
|
+
error: { color: "#e5534b" },
|
|
136
|
+
pathLine: { opacity: 0.6, wordBreak: "break-all", lineHeight: 1.5 },
|
|
137
|
+
button: { padding: "3px 10px", borderRadius: "6px", border: "1px solid rgba(128,128,128,.4)", background: "transparent", color: "inherit", cursor: "pointer" },
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** One boolean switch bound to a settings path. */
|
|
141
|
+
function Toggle(props) {
|
|
142
|
+
const t = translate
|
|
143
|
+
return h("div", { style: styles.row }, [
|
|
144
|
+
h("input", {
|
|
145
|
+
key: "i",
|
|
146
|
+
type: "checkbox",
|
|
147
|
+
style: styles.box,
|
|
148
|
+
checked: props.checked,
|
|
149
|
+
disabled: props.busy,
|
|
150
|
+
onChange: event => props.onChange(event.target.checked),
|
|
151
|
+
}),
|
|
152
|
+
h("label", { key: "l", style: styles.label, onClick: () => props.onChange(!props.checked) }, [
|
|
153
|
+
h("span", { key: "t" }, t(props.labelKey)),
|
|
154
|
+
h("span", { key: "h", style: styles.hint }, t(props.hintKey)),
|
|
155
|
+
]),
|
|
156
|
+
])
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The settings page section. */
|
|
160
|
+
function SettingsRoot() {
|
|
161
|
+
const t = translate
|
|
162
|
+
const [settings, setSettings] = useState(null)
|
|
163
|
+
const [meta, setMeta] = useState({ path: "" })
|
|
164
|
+
const [error, setError] = useState("")
|
|
165
|
+
const [busy, setBusy] = useState(false)
|
|
166
|
+
const [saved, setSaved] = useState(false)
|
|
167
|
+
|
|
168
|
+
const load = useCallback(() => {
|
|
169
|
+
setError("")
|
|
170
|
+
callSettings().then(data => {
|
|
171
|
+
setSettings(data.settings)
|
|
172
|
+
setMeta({ path: data.path || "" })
|
|
173
|
+
}).catch(failure => setError(String(failure && failure.message ? failure.message : failure)))
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
useEffect(() => { load() }, [load])
|
|
177
|
+
|
|
178
|
+
const patch = useCallback(next => {
|
|
179
|
+
setBusy(true)
|
|
180
|
+
setError("")
|
|
181
|
+
callSettings(next).then(data => {
|
|
182
|
+
setSettings(data.settings)
|
|
183
|
+
setSaved(true)
|
|
184
|
+
setTimeout(() => setSaved(false), 1200)
|
|
185
|
+
}).catch(failure => setError(String(failure && failure.message ? failure.message : failure)))
|
|
186
|
+
.then(() => setBusy(false))
|
|
187
|
+
}, [])
|
|
188
|
+
|
|
189
|
+
if (settings === null) {
|
|
190
|
+
return h("div", { style: styles.wrap }, [
|
|
191
|
+
h("p", { key: "l", style: styles.lead }, t("loading")),
|
|
192
|
+
error !== "" ? h("p", { key: "e", style: styles.error }, `${t("failed")} ${error}`) : null,
|
|
193
|
+
])
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const section = (titleKey, children) => h("div", { key: titleKey, style: styles.group }, [
|
|
197
|
+
h("p", { key: "t", style: styles.groupTitle }, t(titleKey)),
|
|
198
|
+
...children,
|
|
199
|
+
])
|
|
200
|
+
|
|
201
|
+
return h("div", { style: styles.wrap }, [
|
|
202
|
+
h("p", { key: "lead", style: styles.lead }, t("lead")),
|
|
203
|
+
|
|
204
|
+
section("history.title", [
|
|
205
|
+
h(Toggle, {
|
|
206
|
+
key: "enabled",
|
|
207
|
+
labelKey: "history.enabled",
|
|
208
|
+
hintKey: "history.hint",
|
|
209
|
+
checked: settings.history.enabled,
|
|
210
|
+
busy,
|
|
211
|
+
onChange: value => patch({ history: { enabled: value } }),
|
|
212
|
+
}),
|
|
213
|
+
]),
|
|
214
|
+
|
|
215
|
+
section("cookies.title", [
|
|
216
|
+
h(Toggle, {
|
|
217
|
+
key: "persist",
|
|
218
|
+
labelKey: "cookies.persist",
|
|
219
|
+
hintKey: "cookies.hint",
|
|
220
|
+
checked: settings.cookies.persist,
|
|
221
|
+
busy,
|
|
222
|
+
onChange: value => patch({ cookies: { persist: value } }),
|
|
223
|
+
}),
|
|
224
|
+
]),
|
|
225
|
+
|
|
226
|
+
section("ui.title", [
|
|
227
|
+
h(Toggle, {
|
|
228
|
+
key: "autoExpandOnce",
|
|
229
|
+
labelKey: "ui.autoExpandOnce",
|
|
230
|
+
hintKey: "ui.autoExpandOnce.hint",
|
|
231
|
+
checked: settings.ui.autoExpandOnce,
|
|
232
|
+
busy,
|
|
233
|
+
onChange: value => patch({ ui: { autoExpandOnce: value } }),
|
|
234
|
+
}),
|
|
235
|
+
h(Toggle, {
|
|
236
|
+
key: "closeWithSession",
|
|
237
|
+
labelKey: "ui.closeWithSession",
|
|
238
|
+
hintKey: "ui.closeWithSession.hint",
|
|
239
|
+
checked: settings.ui.closeWithSession,
|
|
240
|
+
busy,
|
|
241
|
+
onChange: value => patch({ ui: { closeWithSession: value } }),
|
|
242
|
+
}),
|
|
243
|
+
h(Toggle, {
|
|
244
|
+
key: "virtualCursor",
|
|
245
|
+
labelKey: "ui.virtualCursor",
|
|
246
|
+
hintKey: "ui.virtualCursor.hint",
|
|
247
|
+
checked: settings.ui.virtualCursor,
|
|
248
|
+
busy,
|
|
249
|
+
onChange: value => patch({ ui: { virtualCursor: value } }),
|
|
250
|
+
}),
|
|
251
|
+
]),
|
|
252
|
+
|
|
253
|
+
section("vision.title", [
|
|
254
|
+
h("div", { key: "row", style: styles.row }, [
|
|
255
|
+
h("label", { key: "l", style: styles.label }, [
|
|
256
|
+
h("span", { key: "t" }, t("vision.strategy")),
|
|
257
|
+
h("select", {
|
|
258
|
+
key: "s",
|
|
259
|
+
style: styles.select,
|
|
260
|
+
value: settings.vision.strategy,
|
|
261
|
+
disabled: busy,
|
|
262
|
+
onChange: event => patch({ vision: { strategy: event.target.value } }),
|
|
263
|
+
}, [
|
|
264
|
+
h("option", { key: "auto", value: "auto" }, t("vision.auto")),
|
|
265
|
+
h("option", { key: "nonVisual", value: "nonVisual" }, t("vision.nonVisual")),
|
|
266
|
+
]),
|
|
267
|
+
h("span", { key: "h", style: styles.hint }, t("vision.hint")),
|
|
268
|
+
]),
|
|
269
|
+
]),
|
|
270
|
+
]),
|
|
271
|
+
|
|
272
|
+
section("browser.title", [
|
|
273
|
+
h("div", { key: "row", style: styles.row }, [
|
|
274
|
+
h("label", { key: "l", style: styles.label }, [
|
|
275
|
+
h("span", { key: "t" }, t("browser.channel")),
|
|
276
|
+
h("select", {
|
|
277
|
+
key: "s",
|
|
278
|
+
style: styles.select,
|
|
279
|
+
value: settings.browser.channel,
|
|
280
|
+
disabled: busy,
|
|
281
|
+
onChange: event => patch({ browser: { channel: event.target.value } }),
|
|
282
|
+
}, [
|
|
283
|
+
h("option", { key: "bundled", value: "bundled" }, t("browser.bundled")),
|
|
284
|
+
h("option", { key: "auto", value: "auto" }, t("browser.auto")),
|
|
285
|
+
h("option", { key: "chrome", value: "chrome" }, t("browser.chrome")),
|
|
286
|
+
h("option", { key: "edge", value: "edge" }, t("browser.edge")),
|
|
287
|
+
]),
|
|
288
|
+
h("span", { key: "h", style: styles.hint }, t("browser.hint")),
|
|
289
|
+
]),
|
|
290
|
+
]),
|
|
291
|
+
]),
|
|
292
|
+
|
|
293
|
+
section("credentials.title", [
|
|
294
|
+
h(Toggle, {
|
|
295
|
+
key: "allowRead",
|
|
296
|
+
labelKey: "credentials.allowRead",
|
|
297
|
+
hintKey: "credentials.hint",
|
|
298
|
+
checked: settings.credentials.allowRead,
|
|
299
|
+
busy,
|
|
300
|
+
onChange: value => patch({ credentials: { allowRead: value } }),
|
|
301
|
+
}),
|
|
302
|
+
]),
|
|
303
|
+
|
|
304
|
+
h("div", { key: "status", style: styles.status }, [
|
|
305
|
+
saved ? h("span", { key: "s" }, t("saved")) : null,
|
|
306
|
+
error !== "" ? h("span", { key: "e", style: styles.error }, ` ${t("failed")} ${error} `) : null,
|
|
307
|
+
error !== "" ? h("button", { key: "r", style: styles.button, onClick: load }, t("retry")) : null,
|
|
308
|
+
]),
|
|
309
|
+
|
|
310
|
+
meta.path !== "" ? h("div", { key: "path", style: styles.pathLine }, `${t("path")}: ${meta.path}`) : null,
|
|
311
|
+
])
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function apply(ctx) {
|
|
315
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-builtin-browser: dictionaries")
|
|
316
|
+
const t = ctx.locale.bind(NS)
|
|
317
|
+
translate = t
|
|
318
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
319
|
+
name: "settings.section",
|
|
320
|
+
id: "dsh-builtin-browser",
|
|
321
|
+
// Above the rows the host ships (its furthest sits at 40), so this panel
|
|
322
|
+
// lands beneath them in the settings page.
|
|
323
|
+
order: 60,
|
|
324
|
+
label: () => t("nav"),
|
|
325
|
+
locale: NS,
|
|
326
|
+
inject: () => ({ t }),
|
|
327
|
+
}, SettingsRoot))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
exports.name = name
|
|
331
|
+
exports.inject = inject
|
|
332
|
+
exports.apply = apply
|
|
333
|
+
return module.exports
|
|
334
|
+
}})
|
package/cordis.patch.yml
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
# dsh-builtin-browser bundle patch: mounts the shared-browser capability into a
|
|
2
|
-
# profile composition.
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
2
|
+
# profile composition.
|
|
3
|
+
#
|
|
4
|
+
# Row names matter beyond loading: the host's client-module scan resolves a row's
|
|
5
|
+
# specifier to a PACKAGE ROOT and reads `dsh.client` from there. A row named after
|
|
6
|
+
# a subpath (`dsh-builtin-browser/browser`) is not a package specifier and is
|
|
7
|
+
# skipped by design, which would leave the settings panel permanently absent even
|
|
8
|
+
# though every tool works — hence the inert root row under the bare package name.
|
|
9
|
+
#
|
|
10
|
+
# The browser seam (ctx.browser) is always mounted, and the Electron provider plus
|
|
11
|
+
# the browser_* tools are always registered: when a host provides an embedded view
|
|
12
|
+
# seam the provider drives that view, and otherwise it self-hosts its own Electron
|
|
13
|
+
# window, so installing the plugin is enough for the browser to work anywhere.
|
|
14
|
+
# DSH 0.2 no longer provides `electronViewHost` (the desktop shell's embedded
|
|
15
|
+
# view), so `ctx.get('electronViewHost')` currently resolves to undefined and
|
|
16
|
+
# every surface self-hosts. The row keeps reading it on purpose: a host that
|
|
17
|
+
# reintroduces such a seam is then picked up without a code change.
|
|
7
18
|
- insert:
|
|
19
|
+
# Root row. The host's client-module scan resolves a loader row's specifier
|
|
20
|
+
# to a PACKAGE ROOT; a row named after a subpath is skipped, and this package
|
|
21
|
+
# would then have no row to read its `dsh.client` declaration from — the
|
|
22
|
+
# browser would work while its settings panel never appeared. This row is
|
|
23
|
+
# inert (see src/index.ts) and exists only to name the package.
|
|
24
|
+
- id: dsh-builtin-browser
|
|
25
|
+
name: dsh-builtin-browser
|
|
26
|
+
|
|
8
27
|
- id: browser
|
|
9
28
|
name: dsh-builtin-browser/browser
|
|
10
29
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install the plugin browser bridge into an installed DSH Desktop.
|
|
3
|
+
*
|
|
4
|
+
* WHY A SCRIPT
|
|
5
|
+
* The bridge has to live inside the desktop app (it is the only process with
|
|
6
|
+
* Electron access), and the desktop app is somebody else's installed artifact:
|
|
7
|
+
* a desktop update replaces everything this script touches. So the change is
|
|
8
|
+
* expressed as something replayable rather than as a one-off edit — re-run this
|
|
9
|
+
* after every desktop upgrade.
|
|
10
|
+
*
|
|
11
|
+
* WHAT IT DOES (idempotent)
|
|
12
|
+
* 1. copies `plugin-browser-bridge.js` next to the app's main bundle;
|
|
13
|
+
* 2. appends a guarded import to `lib/main.js` that starts it after `whenReady`,
|
|
14
|
+
* keeping a pristine `main.js.before-bridge` backup the first time;
|
|
15
|
+
* 3. does nothing if the import is already present.
|
|
16
|
+
*
|
|
17
|
+
* USAGE
|
|
18
|
+
* node apps/desktop/bridge/install.mjs [path-to-DeepSeek-Harness-install]
|
|
19
|
+
* Default path: %LOCALAPPDATA%\Programs\DeepSeek Harness
|
|
20
|
+
* Pass --revert to restore the pre-bridge main.js instead.
|
|
21
|
+
*/
|
|
22
|
+
import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
23
|
+
import { join } from 'node:path'
|
|
24
|
+
|
|
25
|
+
const DEFAULT_INSTALL = join(process.env.LOCALAPPDATA ?? '', 'Programs', 'DeepSeek Harness')
|
|
26
|
+
const args = process.argv.slice(2)
|
|
27
|
+
const revert = args.includes('--revert')
|
|
28
|
+
const installRoot = args.find(arg => !arg.startsWith('--')) ?? DEFAULT_INSTALL
|
|
29
|
+
|
|
30
|
+
const appDir = join(installRoot, 'resources', 'app')
|
|
31
|
+
const mainPath = join(appDir, 'lib', 'main.js')
|
|
32
|
+
const bridgeTarget = join(appDir, 'lib', 'plugin-browser-bridge.js')
|
|
33
|
+
const bridgeSource = join(import.meta.dirname, 'plugin-browser-bridge.js')
|
|
34
|
+
const backupPath = `${mainPath}.before-bridge`
|
|
35
|
+
|
|
36
|
+
/** The exact block appended to main.js. Keep it recognisable: idempotence and
|
|
37
|
+
* reverting both key off this marker. */
|
|
38
|
+
const MARKER = '// dsh-builtin-browser: hand the plugin a way to drive the sidebar'
|
|
39
|
+
const SNIPPET = `
|
|
40
|
+
${MARKER} pages.
|
|
41
|
+
// The plugin runs inside the Node-mode host, where there is no Electron API, so the
|
|
42
|
+
// shell is the only process that can own a view. Exposing the existing webview
|
|
43
|
+
// guests (and their CDP) is what lets the agent work on the page the human is
|
|
44
|
+
// looking at, instead of spawning a second, parallel browser window.
|
|
45
|
+
// Installed by apps/desktop/bridge/install.mjs — re-run it after a desktop upgrade.
|
|
46
|
+
// A bridge failure is never fatal to the shell.
|
|
47
|
+
app.whenReady().then(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const bridge = await import("./plugin-browser-bridge.js");
|
|
50
|
+
bridge.start();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error("[dsh-browser-bridge] start failed:", error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
if (!existsSync(mainPath)) {
|
|
58
|
+
console.error(`not a DSH Desktop install (no ${mainPath})`)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (revert) {
|
|
63
|
+
if (!existsSync(backupPath)) {
|
|
64
|
+
console.error(`nothing to revert: ${backupPath} does not exist`)
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
copyFileSync(backupPath, mainPath)
|
|
68
|
+
console.log(`reverted ${mainPath} from ${backupPath}`)
|
|
69
|
+
process.exit(0)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const current = readFileSync(mainPath, 'utf8')
|
|
73
|
+
if (current.includes(MARKER)) {
|
|
74
|
+
console.log('bridge already installed (main.js carries the marker); refreshing the module copy')
|
|
75
|
+
copyFileSync(bridgeSource, bridgeTarget)
|
|
76
|
+
process.exit(0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!existsSync(backupPath)) {
|
|
80
|
+
copyFileSync(mainPath, backupPath)
|
|
81
|
+
console.log(`backed up main.js -> ${backupPath}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Append before the trailing `export {};` so the import stays inside the module.
|
|
85
|
+
const exportTail = 'export {};'
|
|
86
|
+
const insertAt = current.lastIndexOf(exportTail)
|
|
87
|
+
if (insertAt === -1) {
|
|
88
|
+
console.error('unexpected main.js shape: no trailing `export {};` to insert before')
|
|
89
|
+
process.exit(1)
|
|
90
|
+
}
|
|
91
|
+
const patched = `${current.slice(0, insertAt)}${SNIPPET}${current.slice(insertAt)}`
|
|
92
|
+
writeFileSync(mainPath, patched)
|
|
93
|
+
|
|
94
|
+
copyFileSync(bridgeSource, bridgeTarget)
|
|
95
|
+
console.log(`installed bridge into ${appDir}`)
|
|
96
|
+
console.log(` module: ${bridgeTarget}`)
|
|
97
|
+
console.log(` patch: ${mainPath} (+${SNIPPET.split('\n').length} lines)`)
|
|
98
|
+
console.log('restart DSH Desktop to activate it.')
|