dsh-x-core 0.1.21 → 0.1.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 (50) hide show
  1. package/assets/dsh-preview.png +0 -0
  2. package/assets/dsh.ico +0 -0
  3. package/assets/icon.ico +0 -0
  4. package/assets/tray.ico +0 -0
  5. package/assets/tray.png +0 -0
  6. package/core-npm.js +201 -183
  7. package/desktop-plugins.js +86 -0
  8. package/desktop.js +200 -0
  9. package/filemanager-base.js +7 -8
  10. package/filemanager-linux.js +124 -0
  11. package/filemanager.js +4 -7
  12. package/native-notify.js +74 -0
  13. package/node/node +2 -1
  14. package/package.json +1 -1
  15. package/packs/dsh-x-workbench/README.md +61 -0
  16. package/packs/dsh-x-workbench/dspack.json +4 -0
  17. package/packs/dsh-x-workbench/home/skills/agent-browser/LICENSE-APACHE-2.0.txt +202 -0
  18. package/packs/dsh-x-workbench/home/skills/agent-browser/NOTICE.md +6 -0
  19. package/packs/dsh-x-workbench/home/skills/agent-browser/SKILL.md +697 -0
  20. package/packs/dsh-x-workbench/home/skills/agent-browser/references/authentication.md +313 -0
  21. package/packs/dsh-x-workbench/home/skills/agent-browser/references/commands.md +300 -0
  22. package/packs/dsh-x-workbench/home/skills/agent-browser/references/profiling.md +120 -0
  23. package/packs/dsh-x-workbench/home/skills/agent-browser/references/proxy-support.md +194 -0
  24. package/packs/dsh-x-workbench/home/skills/agent-browser/references/session-management.md +201 -0
  25. package/packs/dsh-x-workbench/home/skills/agent-browser/references/snapshot-refs.md +229 -0
  26. package/packs/dsh-x-workbench/home/skills/agent-browser/references/video-recording.md +173 -0
  27. package/packs/dsh-x-workbench/home/skills/agent-browser/templates/authenticated-session.sh +105 -0
  28. package/packs/dsh-x-workbench/home/skills/agent-browser/templates/capture-workflow.sh +69 -0
  29. package/packs/dsh-x-workbench/home/skills/agent-browser/templates/form-automation.sh +62 -0
  30. package/packs/dsh-x-workbench/home/skills/electron/LICENSE-APACHE-2.0.txt +202 -0
  31. package/packs/dsh-x-workbench/home/skills/electron/NOTICE.md +6 -0
  32. package/packs/dsh-x-workbench/home/skills/electron/SKILL.md +243 -0
  33. package/packs/dsh-x-workbench/manifest.json +35 -0
  34. package/perf/patch-hooks.mjs +14 -1
  35. package/perf/plugin-profile.mjs +204 -0
  36. package/perf/register.mjs +4 -0
  37. package/platform.js +2 -1
  38. package/public/booting.html +475 -0
  39. package/public/favicon.ico +0 -0
  40. package/public/favicon.png +0 -0
  41. package/public/icon.png +0 -0
  42. package/public/index.html +9277 -8173
  43. package/public/internal-tabs.js +45 -41
  44. package/public/launcher.css +285 -98
  45. package/public/mascot.js +1 -1
  46. package/public/theme.css +33 -6
  47. package/public/water.js +1 -1
  48. package/registry.js +38 -3
  49. package/server.js +1060 -119
  50. package/settings.js +78 -1
@@ -9,16 +9,10 @@
9
9
  const send = (message) => window.ipc?.postMessage(typeof message === 'string' ? message : JSON.stringify(message))
10
10
  const closeIcon = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="m7 7 10 10M7 17 17 7"/></svg>'
11
11
  const items = new Map()
12
- const indicator = document.createElement('div')
13
- indicator.className = 'internal-tab-indicator'
14
- indicator.setAttribute('aria-hidden', 'true')
15
- list.prepend(indicator)
16
12
  let lastHeight = 0
17
13
  let selected = ''
18
14
  let presentation = 0
19
15
  let submittedPresentation = 0
20
- let indicatorFrame = 0
21
- let instantFrame = 0
22
16
  let nextRequest = Date.now()
23
17
  let pendingSelection = null
24
18
  let presentationFrame = 0
@@ -29,7 +23,7 @@
29
23
  let suppressedClick = null
30
24
  const moves = new Map()
31
25
  const reducedMotion = () => root.classList.contains('reduce-motion') || matchMedia('(prefers-reduced-motion: reduce)').matches
32
- const tabNodes = () => [...list.children].filter((item) => item !== indicator)
26
+ const tabNodes = () => [...list.children]
33
27
  const order = () => tabNodes().map((item) => item.dataset.id)
34
28
  const glide = (item, from) => {
35
29
  moves.get(item)?.cancel()
@@ -39,21 +33,28 @@
39
33
  moves.set(item, animation)
40
34
  animation.finished.catch(() => {}).then(() => { if (moves.get(item) === animation) moves.delete(item) })
41
35
  }
42
- const arrange = (ids, animate = false) => {
36
+ /** 新标签淡入并轻微放大到原尺寸。用独立的 scale 属性,不和 glide 的 translateX 抢 transform。 */
37
+ const enter = (item) => {
38
+ if (reducedMotion()) return
39
+ item.animate([{ opacity: 0, scale: '0.94' }, { opacity: 1, scale: '1' }], { duration: 180, easing: 'cubic-bezier(.2, .8, .2, 1)' })
40
+ }
41
+ const arrange = (ids, animate = false, snapshot = null) => {
43
42
  const current = order()
44
43
  const desired = [...ids.filter((id) => items.has(id)), ...current.filter((id) => !ids.includes(id))]
45
44
  // 原生回执和标题轮询经常不改变顺序,不能因此提前结束正在播放的让位动画。
46
- if (desired.join('\0') === current.join('\0')) { positionIndicator(); return }
45
+ if (desired.join('\0') === current.join('\0')) return
47
46
  const focus = list.contains(document.activeElement) ? document.activeElement : null
48
- const before = new Map(tabNodes().map((item) => [item, item.getBoundingClientRect().left]))
47
+ // snapshot 是调用方在删掉节点之前量好的位置(关标签时用),拿不到才现量。
48
+ const before = snapshot || new Map(tabNodes().map((item) => [item, item.getBoundingClientRect().left]))
49
49
  for (const animation of moves.values()) animation.cancel()
50
50
  moves.clear()
51
- let previous = indicator
51
+ let previous = null
52
52
  // 只移动节点,保留点击焦点;拖动期间新出现的标签继续排在末尾。
53
53
  for (const id of desired) {
54
54
  const item = items.get(id)
55
55
  if (!item) continue
56
- if (previous.nextSibling !== item) previous.after(item)
56
+ if (!previous) list.append(item)
57
+ else if (previous.nextSibling !== item) previous.after(item)
57
58
  previous = item
58
59
  }
59
60
  if (focus?.isConnected && document.activeElement !== focus) focus.focus({ preventScroll: true })
@@ -62,34 +63,13 @@
62
63
  if (animate) for (const item of tabNodes()) {
63
64
  if (item !== drag?.item && before.has(item)) glide(item, before.get(item) - item.getBoundingClientRect().left)
64
65
  }
65
- positionIndicator()
66
66
  }
67
67
  const commitOrder = () => {
68
68
  pendingOrder = { ids: order(), request: ++nextRequest }
69
69
  send({ action: 'reorder', ...pendingOrder })
70
70
  }
71
71
 
72
- const positionIndicator = ({ instant = false } = {}) => {
73
- const item = items.get(selected)
74
- if (!item || area.hidden) { indicator.classList.remove('visible'); return }
75
- const first = instant || !indicator.classList.contains('visible')
76
- cancelAnimationFrame(instantFrame)
77
- if (first) indicator.classList.add('instant')
78
- else indicator.classList.remove('instant')
79
- indicator.style.width = `${item.offsetWidth}px`
80
- indicator.style.transform = `translate3d(${item.offsetLeft + (drag?.item === item ? drag.offset || 0 : 0)}px, ${item.offsetTop}px, 0)`
81
- indicator.classList.add('visible')
82
- if (first) {
83
- void indicator.offsetWidth
84
- instantFrame = requestAnimationFrame(() => indicator.classList.remove('instant'))
85
- }
86
- }
87
- const scheduleIndicator = () => {
88
- cancelAnimationFrame(indicatorFrame)
89
- indicatorFrame = requestAnimationFrame(() => positionIndicator({ instant: true }))
90
- }
91
72
  const selectVisual = (id) => {
92
- cancelAnimationFrame(indicatorFrame)
93
73
  const changed = id !== selected
94
74
  selected = id
95
75
  for (const [key, item] of items) {
@@ -97,7 +77,6 @@
97
77
  item.querySelector('.internal-tab-label').setAttribute('aria-selected', String(key === id))
98
78
  }
99
79
  if (changed) items.get(id)?.scrollIntoView({ block: 'nearest', inline: 'nearest' })
100
- positionIndicator()
101
80
  }
102
81
  const selectTab = (id) => {
103
82
  // 点击即开始滑动,不等待跨 WebView 的回传;请求编号防止快速连点被旧状态拉回。
@@ -129,7 +108,6 @@
129
108
  if (ids.join('\0') !== order().join('\0')) arrange(ids, true)
130
109
  drag.offset = left - item.offsetLeft
131
110
  item.style.transform = `translateX(${drag.offset}px)`
132
- positionIndicator({ instant: true })
133
111
  }
134
112
  const scrollDrag = () => {
135
113
  if (!drag?.started) return
@@ -154,7 +132,6 @@
154
132
  glide(ended.item, ended.offset)
155
133
  if (order().join('\0') !== ended.original.join('\0')) commitOrder()
156
134
  }
157
- positionIndicator()
158
135
  }
159
136
  }
160
137
  list.addEventListener('pointerdown', (event) => {
@@ -210,7 +187,15 @@
210
187
  const active = String(state.active || '')
211
188
  const en = state.lang === 'en'
212
189
  root.classList.toggle('internal-tabs', enabled)
190
+ // 顶栏形态由「有没有标签」决定:一个标签都没有时还是原来那条高顶栏,浏览器式扁条只在真有标签时出现。
191
+ root.classList.toggle('has-tabs', enabled && (state.tabs || []).length > 0)
213
192
  root.classList.toggle('internal-dsh-active', enabled && !!active)
193
+ // 选中标签的底色跟选中那块页面走(外壳采样来的):同色才看不出接缝,采样不到就退回主题色。
194
+ if (state.surface) root.style.setProperty('--tab-surface', state.surface)
195
+ else root.style.removeProperty('--tab-surface')
196
+ // 标签上的字跟着采到的底色走(亮底给深字):外壳算好放在 state.ink 里
197
+ if (state.ink) root.style.setProperty('--tab-ink', state.ink)
198
+ else root.style.removeProperty('--tab-ink')
214
199
  area.hidden = !enabled
215
200
  // 隐藏的管理内容不能被键盘 Tab 聚焦;顶部设置、Star 与窗口控制仍保留原来的交互。
216
201
  const main = document.querySelector('main')
@@ -225,13 +210,18 @@
225
210
  if (drag && (!enabled || !ids.has(drag.item.dataset.id))) finishDrag(true)
226
211
  if (pendingOrder && state.orderRequest === pendingOrder.request) pendingOrder = null
227
212
  if (pendingSelection && (state.request === pendingSelection.request || (pendingSelection.id && !ids.has(pendingSelection.id)))) pendingSelection = null
228
- for (const [id, item] of items) {
229
- if (!ids.has(id)) { item.remove(); items.delete(id) }
213
+ // 关标签:先量一遍现在的位置,删掉节点后让剩下的从老位置滑过去,别硬跳一格。
214
+ const closing = [...items].filter(([id]) => !ids.has(id))
215
+ const closingBefore = closing.length ? new Map(tabNodes().map((item) => [item, item.getBoundingClientRect().left])) : null
216
+ for (const [id, item] of closing) {
217
+ item.remove()
218
+ items.delete(id)
230
219
  }
231
220
  const localOrder = drag || pendingOrder ? order() : null
232
221
  for (const tab of state.tabs || []) {
233
222
  let item = items.get(tab.id)
234
- if (!item) {
223
+ const created = !item
224
+ if (created) {
235
225
  item = document.createElement('div')
236
226
  item.className = 'internal-tab'
237
227
  item.dataset.id = tab.id
@@ -245,6 +235,8 @@
245
235
  }
246
236
  // 复用节点让选中态连续过渡,也保留键盘焦点;后台标题变化不能重建整排标签。
247
237
  if (!item.parentNode) list.append(item)
238
+ // 入场动画要等节点进了文档再起,否则(未挂载的元素不动画)什么都看不到。
239
+ if (created) enter(item)
248
240
  item.classList.toggle('stopped', !!tab.stopped)
249
241
  const label = item.querySelector('.internal-tab-label')
250
242
  label.title = [tab.title, tab.pageTitle].filter(Boolean).join(' — ')
@@ -254,7 +246,7 @@
254
246
  close.title = en ? 'Close tab' : '关闭标签'
255
247
  close.setAttribute('aria-label', close.title)
256
248
  }
257
- arrange(localOrder || (state.tabs || []).map((tab) => tab.id))
249
+ arrange(localOrder || (state.tabs || []).map((tab) => tab.id), !!closingBefore, closingBefore)
258
250
  if (drag?.started) moveDrag()
259
251
  selectVisual(pendingSelection?.id ?? active)
260
252
  reportHeight()
@@ -285,7 +277,19 @@
285
277
  document.getElementById('settingsEntry')?.addEventListener('click', () => {
286
278
  if (root.classList.contains('internal-dsh-active')) selectTab('')
287
279
  }, true)
288
- new ResizeObserver(() => { reportHeight(); scheduleIndicator() }).observe(header)
280
+ new ResizeObserver(reportHeight).observe(header)
281
+
282
+ /**
283
+ * 标签中心的 x(给外壳采「这张标签正下方」的底色用):视图和管理页同宽同原点,
284
+ * 所以 DOM 坐标就是页面里的坐标。拿不到(刚建、已关)返回 0,外壳那边退回左上角。
285
+ */
286
+ window.launcherTabCenterX = (id) => {
287
+ const item = items.get(id)
288
+ if (!item) return 0
289
+ const box = item.getBoundingClientRect()
290
+ return Math.round(box.left + box.width / 2)
291
+ }
292
+
289
293
  // 原生标签页叠在管理页之上,管理页的弹窗(.ask)会被它盖住、遮罩却挡住了标签栏,弹窗就关不掉了。
290
294
  // 有弹窗开着就让外壳先收起标签页,关完再放回;初值为 null,保证页面重载后先把真实状态同步一次。
291
295
  let overlay = null