dsh-live-browser 0.4.0 → 0.5.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/lib/skills.js CHANGED
@@ -31,6 +31,7 @@ const BODY = `# 用内嵌浏览器完成任务
31
31
  - **敏感操作先问**:下单、付款、删除、发送消息、修改账号等不可撤销的动作,把 \`sensitive\` 设为 \`true\`,插件会请用户确认;用户拒绝时返回 \`executed:false\`,不要重试。
32
32
  - **登录由用户完成**:遇到扫码、短信验证码,让用户直接在面板里操作,你继续等待页面变化即可。
33
33
  - **面板没开时**:\`browser_status\` 会告诉你 \`attached:false\`,可调 \`browser_open_panel\`,或请用户点右侧栏的「浏览器」标签页。
34
+ - **iframe 浏览器**:如果 \`browser_status\` 返回 \`mode: "iframe"\`,或页面对象的 \`kind\` 是 \`iframe\`,说明当前是 DSH Web 版的 iframe 浏览器,跨域限制导致插件无法注入脚本。不要反复重试点击/输入;直接把返回的 \`hint\` 告诉用户,并指导用户改用 DSH 桌面版的 webview 浏览器(关闭浏览器里的 DSH 页面 → 启动 DeepSeek Harness 桌面客户端 → 打开同一会话 → 点右侧栏「浏览器」)。
34
35
 
35
36
  ## 常见任务写法
36
37
 
@@ -54,7 +55,16 @@ export function registerSkills(ctx) {
54
55
  if (registry === undefined || typeof registry.register !== 'function') return
55
56
 
56
57
  ctx.effect(
57
- () => registry.register({ name: NAME, description: DESCRIPTION, content: BODY }),
58
+ () =>
59
+ registry.register({
60
+ name: NAME,
61
+ description: DESCRIPTION,
62
+ // DSH 0.2.2's loader checks `source` when it reads a runtime skill; a
63
+ // definition with only `content` registers but fails on load with
64
+ // "source must be a string".
65
+ source: BODY,
66
+ content: BODY,
67
+ }),
58
68
  'dsh-live-browser: skill',
59
69
  )
60
70
  }
package/lib/tools.js CHANGED
@@ -31,6 +31,19 @@ const SENSITIVE_PARAM = {
31
31
  },
32
32
  }
33
33
 
34
+ /**
35
+ * User-facing guidance when the sidebar is the Web build's iframe browser.
36
+ *
37
+ * A cross-origin iframe cannot be scripted from the parent page. The Desktop
38
+ * build renders the same tab as an Electron <webview>, which this plugin can
39
+ * drive completely, so the correct answer is to send the user to Desktop.
40
+ */
41
+ const IFRAME_HINT =
42
+ '当前右侧栏打开的是 Web 版 iframe 浏览器,页面与 DSH 跨域,浏览器同源策略禁止插件注入脚本,点击/输入/读取/执行脚本都无法工作。' +
43
+ '请改用 DSH 桌面版的 webview 浏览器:1) 关闭浏览器里的 DSH 页面(地址形如 http://127.0.0.1:54249);' +
44
+ '2) 启动 DeepSeek Harness 桌面客户端(Windows 开始菜单里的 DeepSeek Harness,或 D:\\软件\\deepseekHARNESS\\DeepSeek Harness.exe);' +
45
+ '3) 在桌面客户端打开同一个会话,点右侧栏「浏览器」标签页。桌面版浏览器是 Electron <webview>,browser_* 工具即可正常操作。'
46
+
34
47
  /**
35
48
  * Turn what a model or human typed into a navigable URL.
36
49
  *
@@ -153,7 +166,7 @@ export function registerBrowserTools(ctx, rawConfig, bridge, panelControl = {})
153
166
  register({
154
167
  name: 'browser_status',
155
168
  description:
156
- '查看内嵌浏览器状态:右侧栏的浏览器面板是否打开、页面是否可操作。开始网页任务前先调用它确认环境。',
169
+ '查看内嵌浏览器状态:右侧栏的浏览器面板是否打开、页面是否可操作。开始网页任务前先调用它确认环境。如果返回 mode:"iframe",说明当前是 Web 版 iframe 浏览器,插件无法注入脚本,请把 hint 原样告诉用户并指导其改用桌面版 webview 浏览器。',
157
170
  parameters: { type: 'object', properties: {}, additionalProperties: false },
158
171
  async execute(args, exec) {
159
172
  // `page` must stay JSON-clean: returning `undefined` makes DSH reject the
@@ -175,8 +188,21 @@ export function registerBrowserTools(ctx, rawConfig, bridge, panelControl = {})
175
188
  }
176
189
  const attached = page !== null || attachedFor(exec)
177
190
  if (!attached) pageError = undefined
191
+ // A Web-build iframe is attached and visible, but nothing can be injected
192
+ // into it. Say so up front instead of telling the caller the panel is
193
+ // ready and letting the next click fail with a generic browser error.
194
+ if (page?.kind === 'iframe') {
195
+ return {
196
+ attached: true,
197
+ mode: 'iframe',
198
+ actionRequired: 'switch-to-desktop-webview',
199
+ page,
200
+ hint: page.hint ?? IFRAME_HINT,
201
+ }
202
+ }
178
203
  return {
179
204
  attached,
205
+ ...(page?.kind === 'webview' ? { mode: 'webview' } : {}),
180
206
  page,
181
207
  ...(pageError === undefined ? {} : { pageError }),
182
208
  hint: attached
@@ -205,14 +231,51 @@ export function registerBrowserTools(ctx, rawConfig, bridge, panelControl = {})
205
231
  // panel may still be finishing a poll started for the previous chat.
206
232
  let attached = attachedFor(exec)
207
233
  let hasPage = false
234
+ let tabList = []
208
235
  try {
209
236
  const tabs = await run('tabs', {}, { panelWaitMs: attached ? 0 : 800, timeoutMs: 3000 }, exec)
210
237
  attached = true
211
- hasPage = Array.isArray(tabs?.tabs) && tabs.tabs.length > 0
238
+ tabList = Array.isArray(tabs?.tabs) ? tabs.tabs : []
239
+ hasPage = tabList.length > 0
212
240
  } catch {
213
241
  // No panel, or it cannot answer: fall through to the opener below.
214
242
  }
215
- if (hasPage) return { opened: false, reason: '面板已经打开' }
243
+ if (hasPage) {
244
+ const first = tabList[0]
245
+ if (first?.kind === 'iframe') {
246
+ return {
247
+ opened: false,
248
+ mode: 'iframe',
249
+ actionRequired: 'switch-to-desktop-webview',
250
+ reason: '当前右侧栏是 iframe 浏览器,插件无法注入脚本',
251
+ hint: first.hint ?? IFRAME_HINT,
252
+ }
253
+ }
254
+ return { opened: false, reason: '面板已经打开' }
255
+ }
256
+
257
+ // Preferred path: the client owns the sidebar and can open a tab in the
258
+ // requesting conversation even when the user is looking at another chat.
259
+ try {
260
+ const result = await run(
261
+ 'openPanel',
262
+ { url: args.url },
263
+ { panelWaitMs: attached ? 0 : 800, timeoutMs: cfg.openPanelWaitMs + 3000 },
264
+ exec,
265
+ )
266
+ const ready = attachedFor(exec)
267
+ if (result?.opened === true && result?.hasPage === true) {
268
+ return {
269
+ opened: true,
270
+ attached: ready,
271
+ ...(ready ? {} : { hint: '侧边栏标签页已创建,但面板还没连上;请确认右侧栏可见后重试。' }),
272
+ }
273
+ }
274
+ // The client accepted the command but did not produce a page (for
275
+ // example an older client); fall through to the host opener/hint.
276
+ } catch {
277
+ // No client worker yet: fall through to the host opener / hint.
278
+ }
216
279
 
217
280
  // The sidebar service lives on the client, not here: `sidebarRight` is
218
281
  // declared with dsh.client.platform = web, so ctx.get() on the host side
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-live-browser",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "让 agent 操作 DSH 自带的侧边栏浏览器:真实 webview 渲染,注入命令层,可点击、输入、读取和滚动用户眼前那张页面;每个对话各自一页,并补齐标签页右键批量关闭。",
5
5
  "type": "module",
6
6
  "license": "MIT",