@lyhue1991/dsh-soup 0.5.0 → 0.5.2

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 (3) hide show
  1. package/README.md +1 -0
  2. package/lib/client.js +52 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -40,6 +40,7 @@ DSH(DeepSeek Harness)功能很强:会话、工具、轨迹、Goal 一应
40
40
  - **类型图标**:按品牌色区分文件类型(notebook 橙、markdown 紫、json 黄、pdf 红…)。
41
41
  - **沙箱围栏**:所有文件操作只能落在你的工作区与会话目录内,越界自动拒绝。
42
42
  - **自动刷新**:每 3 秒轻量探测一次目录签名(mtime),有变化才重拉——agent 或终端里的文件增删改无需手动刷新即可出现;面板关闭且无预览时零开销;宿主不可达(如 DSH 重启中)自动指数退避(封顶 60s)。
43
+ - **新建文件夹**:顶部 🗂+ 在当前目录创建文件夹;工具栏顺序为 🗂+ 新建、⬆ 上传、⟳ 刷新、✕ 关闭。
43
44
  - **一键刷新**:顶部 ⟳ 重拉整个文件树,已展开的子目录保持展开不收起。
44
45
 
45
46
  ### 🫚 会话内文件预览(主打 · 姜 · 去腥增香)
package/lib/client.js CHANGED
@@ -98,8 +98,9 @@ window.__ModuleLoader__.load({
98
98
  '.expl-confirm-title{font-size:14px;font-weight:600;margin-bottom:10px;}.expl-confirm-text{font-size:13px;line-height:20px;overflow-wrap:anywhere;}.expl-confirm-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px;}' +
99
99
  '.expl-confirm-btn{height:30px;padding:0 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer;font-size:13px;}.expl-confirm-btn:hover{background:var(--dsw-alias-interactive-bg-hover);}.expl-confirm-primary{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:#fff;}' +
100
100
  '.expl-menu{position:fixed;z-index:1990;min-width:172px;background:var(--dsw-specific-menu);border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;padding:4px;box-shadow:var(--dsw-shadow-lv3);pointer-events:auto;}' +
101
- '.expl-menu-item{display:flex;align-items:center;gap:7px;width:100%;text-align:left;background:transparent;border:none;color:var(--dsw-alias-label-primary);font-size:13px;padding:6px 10px;border-radius:8px;cursor:pointer;line-height:1.4;}' +
102
- '.expl-menu-ico{flex:none;display:inline-flex;align-items:center;}.expl-menu-ico svg{width:14px;height:14px;display:block;}' +
101
+ '.expl-menu-item{display:flex;align-items:center;width:100%;text-align:left;background:transparent;border:none;color:var(--dsw-alias-label-primary);font-size:13px;padding:6px 10px;border-radius:8px;cursor:pointer;line-height:1.4;}' +
102
+ '.expl-menu-ico,.expl-menu-ico-ph{flex:none;display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;margin-right:7px;}' +
103
+ '.expl-menu-ico svg{width:14px;height:14px;display:block;}' +
103
104
  '.expl-menu-item:hover{background:var(--dsw-alias-interactive-bg-hover);}' +
104
105
  '.expl-danger{color:var(--dsw-alias-state-error-primary);} .expl-menu-item.expl-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger);}' +
105
106
  '.expl-menu-sep{height:1px;margin:4px 6px;background:var(--dsw-alias-border-l1);}' +
@@ -119,12 +120,13 @@ window.__ModuleLoader__.load({
119
120
  }
120
121
 
121
122
  // ------------------------------------------------------------------
122
- // 工具栏图标:取自 JupyterLab ui-components(BSD-3-Clause,与文件类型
123
- // 图标同源的引用方式),fill 改为 currentColor 以跟随 DSH 主题 token。
124
- // 注意:只引入刷新图标——ICON_CLOSE / ICON_UPLOAD 与 GoalBar 段的
125
- // 同名 React 元素常量冲突(var 提升覆盖),字符样式反而更稳。
123
+ // 工具栏图标:刷新图标取自 JupyterLab ui-components(BSD-3-Clause);
124
+ // fill 改为 currentColor 以跟随 DSH 主题 token。
125
+ // 注意:ICON_CLOSE / ICON_UPLOAD 与 GoalBar 段的同名 React 元素常量
126
+ // 冲突(var 提升覆盖),字符样式反而更稳。
126
127
  // ------------------------------------------------------------------
127
128
  var ICON_REFRESH = '<svg xmlns="http://www.w3.org/2000/svg" width="16" viewBox="0 0 18 18"><path fill="currentColor" d="M9 13.5c-2.49 0-4.5-2.01-4.5-4.5S6.51 4.5 9 4.5c1.24 0 2.36.52 3.17 1.33L10 8h5V3l-1.76 1.76A6 6 0 0 0 9 3C5.69 3 3.01 5.69 3.01 9S5.69 15 9 15a5.98 5.98 0 0 0 5.9-5h-1.52c-.46 2-2.24 3.5-4.38 3.5"/></svg>'
129
+ var ICON_NEW_FOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-2 7h-3v3h-2v-3h-3v-2h3V8h2v3h3v2z"/></svg>'
128
130
  var ICON_MORE = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><circle cx="3" cy="8" r="1.3" fill="currentColor"/><circle cx="8" cy="8" r="1.3" fill="currentColor"/><circle cx="13" cy="8" r="1.3" fill="currentColor"/></svg>'
129
131
  var EXPAND_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>'
130
132
  var SHRINK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/></svg>'
@@ -257,10 +259,13 @@ window.__ModuleLoader__.load({
257
259
  'explorer.label': '资源管理器',
258
260
  'explorer.refresh': '刷新',
259
261
  'explorer.refreshTitle': '刷新(保持已展开目录)',
262
+ 'explorer.newFolder': '新建文件夹',
263
+ 'explorer.newFolderTitle': '在当前目录新建文件夹',
260
264
  'explorer.upload': '上传文件',
261
265
  'explorer.close': '关闭',
262
266
  'explorer.closePanel': '关闭资源管理器',
263
267
  'explorer.emptyDir': '(空目录)',
268
+ 'explorer.waitCwd': '正在获取会话目录…',
264
269
  'explorer.bulkSelected': '已选 {n} 项 · 拖到文件夹可移动',
265
270
  'explorer.readFail': '读取失败',
266
271
  'explorer.actionFail': '失败',
@@ -291,7 +296,7 @@ window.__ModuleLoader__.load({
291
296
  'menu.trash': '🗑 移到废纸篓',
292
297
  'menu.trashMulti': '🗑 移到废纸篓 ({n} 项)',
293
298
  'menu.newFile': '📄 新建文件',
294
- 'menu.newFolder': '📁 新建文件夹',
299
+ 'menu.newFolder': '新建文件夹',
295
300
  'menu.copyPath': '⧉ 复制路径',
296
301
  'menu.download': '⬇ 下载',
297
302
  'menu.deselect': '取消选择',
@@ -325,10 +330,13 @@ window.__ModuleLoader__.load({
325
330
  'explorer.label': 'Explorer',
326
331
  'explorer.refresh': 'Refresh',
327
332
  'explorer.refreshTitle': 'Refresh (keeps expanded folders)',
333
+ 'explorer.newFolder': 'New folder',
334
+ 'explorer.newFolderTitle': 'Create a folder in the current directory',
328
335
  'explorer.upload': 'Upload files',
329
336
  'explorer.close': 'Close',
330
337
  'explorer.closePanel': 'Close explorer',
331
338
  'explorer.emptyDir': '(empty)',
339
+ 'explorer.waitCwd': 'Waiting for the session directory…',
332
340
  'explorer.bulkSelected': '{n} selected · drag onto a folder to move',
333
341
  'explorer.readFail': 'Failed to read',
334
342
  'explorer.actionFail': 'Failed',
@@ -359,7 +367,7 @@ window.__ModuleLoader__.load({
359
367
  'menu.trash': '🗑 Move to trash',
360
368
  'menu.trashMulti': '🗑 Move to trash ({n})',
361
369
  'menu.newFile': '📄 New file',
362
- 'menu.newFolder': '📁 New folder',
370
+ 'menu.newFolder': 'New folder',
363
371
  'menu.copyPath': '⧉ Copy path',
364
372
  'menu.download': '⬇ Download',
365
373
  'menu.deselect': 'Clear selection',
@@ -516,9 +524,15 @@ window.__ModuleLoader__.load({
516
524
  var res = await rpc('sessionCwd', { sessionId: sessionId })
517
525
  if (res && res.ok && res.cwd) return res.cwd
518
526
  }
519
- // 已知会话但不能确定其工作目录时,回退到文件系统根,而不是"宿主启动目录"——
520
- // 后者(root)会让资源管理器误显示与当前会话无关的启动路径。
521
- return '/'
527
+ // 已知会话但尚未拿到 cwd 时不要猜路径:猜测 '/' 正好会撞上桌面端
528
+ // 工作区围栏(Desktop 主进程通常从用户主目录启动)。等 useSessions
529
+ // 补上 cwd 后 effect 会重跑。
530
+ if (sessionId) return undefined
531
+ // 无当前会话(例如启动时右栏先挂载)回退到宿主 root;它一定在围栏内,
532
+ // 而文件系统根 '/' 不一定在。
533
+ var rootRes = await rpc('root')
534
+ if (rootRes && rootRes.ok && rootRes.root) return rootRes.root
535
+ return undefined
522
536
  }
523
537
 
524
538
  async function trackSession(sessionId, cwdHint) {
@@ -526,6 +540,10 @@ window.__ModuleLoader__.load({
526
540
  activeSessionId = sessionId || null
527
541
  var cwd = await defaultCwd(sessionId, cwdHint)
528
542
  if (generation !== sessionTrackGeneration) return
543
+ if (!cwd) {
544
+ setState({ cwd: '', tree: [], selected: new Set(), renaming: null, newItem: null, menu: null, error: '', notice: '', files: [] })
545
+ return
546
+ }
529
547
  // 换项目时展开状态没有跨项目意义;回到同一项目则保留。
530
548
  if (expandedDirsCwd !== cwd) {
531
549
  expandedDirs.clear()
@@ -1407,7 +1425,7 @@ window.__ModuleLoader__.load({
1407
1425
  }
1408
1426
  if (node.type === 'directory') {
1409
1427
  items.push({ key: 'newfile', label: T('menu.newFile'), onClick: function () { startNew(node.path, false) } })
1410
- items.push({ key: 'newfolder', label: T('menu.newFolder'), onClick: function () { startNew(node.path, true) }, separatorAfter: true })
1428
+ items.push({ key: 'newfolder', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_NEW_FOLDER } }), T('menu.newFolder')], onClick: function () { startNew(node.path, true) }, separatorAfter: true })
1411
1429
  }
1412
1430
  items.push({ key: 'copy', label: T('menu.copyPath'), onClick: function () { copyPath(node) } })
1413
1431
  if (node.type !== 'directory') {
@@ -1415,7 +1433,7 @@ window.__ModuleLoader__.load({
1415
1433
  }
1416
1434
  } else {
1417
1435
  items.push({ key: 'newfile', label: T('menu.newFile'), onClick: function () { startNew(state.cwd, false) } })
1418
- items.push({ key: 'newfolder', label: T('menu.newFolder'), onClick: function () { startNew(state.cwd, true) } })
1436
+ items.push({ key: 'newfolder', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_NEW_FOLDER } }), T('menu.newFolder')], onClick: function () { startNew(state.cwd, true) } })
1419
1437
  items.push({ key: 'refresh', label: [create('span', { className: 'expl-menu-ico', dangerouslySetInnerHTML: { __html: ICON_REFRESH } }), T('explorer.refresh')], onClick: refresh, separatorAfter: true })
1420
1438
  if (state.selected.size > 0) items.push({ key: 'none', label: T('menu.deselect'), onClick: function () { setState({ selected: new Set(), lastIndex: null }) } })
1421
1439
  }
@@ -1454,11 +1472,16 @@ window.__ModuleLoader__.load({
1454
1472
  var items = buildMenuItems(menu.node)
1455
1473
  var menuChildren = []
1456
1474
  items.forEach(function (it) {
1475
+ var children = it.label instanceof Array ? it.label : (function () {
1476
+ var match = it.label.match(/^([\u2190-\u2BFF\u{1F000}-\u{1FAFF}])\s*/u)
1477
+ if (match) return [create('span', { className: 'expl-menu-ico' }, match[1]), it.label.slice(match[0].length)]
1478
+ return [create('span', { className: 'expl-menu-ico-ph' }), it.label]
1479
+ })()
1457
1480
  menuChildren.push(create('button', {
1458
1481
  key: it.key,
1459
1482
  className: 'expl-menu-item' + (it.danger ? ' expl-danger' : ''),
1460
1483
  onClick: function () { setState({ menu: null }); it.onClick() },
1461
- }, it.label))
1484
+ }, children))
1462
1485
  if (it.separatorAfter) menuChildren.push(create('div', { key: it.key + '-sep', className: 'expl-menu-sep' }))
1463
1486
  })
1464
1487
  return create('div', { ref: menuRef, className: 'expl-menu', style: { left: pos.x, top: pos.y } }, menuChildren)
@@ -1485,7 +1508,7 @@ window.__ModuleLoader__.load({
1485
1508
  var widthRef = React.useRef(DETAILS_DEFAULT)
1486
1509
  var dragRef = React.useRef({ startX: 0, startW: 0, active: false })
1487
1510
 
1488
- React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId])
1511
+ React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId, liveCwd])
1489
1512
 
1490
1513
  var setFrameWidth = React.useCallback(function (w) {
1491
1514
  var frame = frameRef.current
@@ -1583,7 +1606,8 @@ window.__ModuleLoader__.load({
1583
1606
  ))
1584
1607
  }
1585
1608
  if (s.tree.length === 0 && !(state.newItem && state.newItem.parent === state.cwd)) {
1586
- bodyChildren.push(create('div', { key: '__empty__', className: 'expl-muted' }, T('explorer.emptyDir')))
1609
+ bodyChildren.push(create('div', { key: '__empty__', className: 'expl-muted' },
1610
+ s.cwd ? T('explorer.emptyDir') : T('explorer.waitCwd')))
1587
1611
  } else {
1588
1612
  s.tree.forEach(function (node) { bodyChildren.push(create(Row, { key: node.path, node: node, depth: 0 })) })
1589
1613
  }
@@ -1604,16 +1628,22 @@ window.__ModuleLoader__.load({
1604
1628
  create('div', { className: 'expl-head-btns' },
1605
1629
  create('button', {
1606
1630
  className: 'expl-btn',
1607
- onClick: function () { refreshAll() },
1608
- title: T('explorer.refreshTitle'),
1609
- 'aria-label': T('explorer.refresh'),
1610
- }, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } })),
1631
+ onClick: function () { startNew(state.cwd, true) },
1632
+ title: T('explorer.newFolderTitle'),
1633
+ 'aria-label': T('explorer.newFolder'),
1634
+ }, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_NEW_FOLDER } })),
1611
1635
  create('button', {
1612
1636
  className: 'expl-btn',
1613
1637
  onClick: function () { if (uploadInputEl) uploadInputEl.click() },
1614
1638
  title: T('explorer.upload'),
1615
1639
  'aria-label': T('explorer.upload'),
1616
1640
  }, '⬆'),
1641
+ create('button', {
1642
+ className: 'expl-btn',
1643
+ onClick: function () { refreshAll() },
1644
+ title: T('explorer.refreshTitle'),
1645
+ 'aria-label': T('explorer.refresh'),
1646
+ }, create('span', { className: 'expl-icon', dangerouslySetInnerHTML: { __html: ICON_REFRESH } })),
1617
1647
  create('button', {
1618
1648
  className: 'expl-btn',
1619
1649
  onClick: function (event) {
@@ -1673,7 +1703,7 @@ window.__ModuleLoader__.load({
1673
1703
  }) : undefined
1674
1704
  var cwdRef = React.useRef(liveCwd)
1675
1705
  cwdRef.current = liveCwd || cwdRef.current
1676
- React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId])
1706
+ React.useEffect(function () { trackSession(sessionId, cwdRef.current) }, [sessionId, liveCwd])
1677
1707
  return create('button', {
1678
1708
  type: 'button',
1679
1709
  className: 'expl-toggle expl-tool' + (s.open ? ' expl-active' : ''),
@@ -1707,7 +1737,7 @@ window.__ModuleLoader__.load({
1707
1737
  }) : undefined
1708
1738
  React.useEffect(function () {
1709
1739
  if (blank) trackSession(sessionId, liveCwd)
1710
- }, [blank, sessionId])
1740
+ }, [blank, sessionId, liveCwd])
1711
1741
  if (!blank) return null
1712
1742
  return create('div', { className: 'expl-hero-dock' },
1713
1743
  create('button', {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lyhue1991/dsh-soup",
3
3
  "description": "DSH 项目资源管理器:右侧 details 列文件树(并列网格、宽度对齐宿主 details 合同 300-520px),有 Session log 时在其右侧、空白新会话在输入框上方右端提供 📁 切换按钮;支持多选、双击展开/打开、右键菜单(打开/系统打开/重命名/废纸篓/新建/复制路径)、拖拽移动、上传。文件「打开」在会话区新增「文件」标签页(原生 conversation.view,与对话/轨迹同级):文本可编辑 ⌘S 保存、图片预览、二进制提示。附带模型吞吐徽标:输入框正上方显示「正在等待模型...」与彩色 t/s 徽标(数据来自 llm/stream 真实流)。附带 GoalBar 多行展示:把原生单行截断 GoalBar 放开为多行完整显示,编辑用 textarea,工具与数据仍走原生 goal。",
4
- "version": "0.5.0",
4
+ "version": "0.5.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {