dsh-code-ui 1.6.4 → 1.6.5

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 +5 -0
  2. package/lib/client.js +107 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -114,6 +114,11 @@ dsh-code-ui/
114
114
 
115
115
  | 时间 | 版本 | 修复 / 新增 |
116
116
  | --- | --- | --- |
117
+ | 2026-10-13 | v1.6.5 | 优化:**已发消息的引用标签着色**——chip 序列化进正文的 `N:文件名:起-止` / `🌐N:文件名` 标签在发出后以蓝色高亮展示(`--dsw-alias-state-business-primary`,与宿主输入框 chip 同色加粗),用户回看消息时能一眼看到引用了什么。实现:MutationObserver + TreeWalker 纯文本节点 overlay(只 split/wrap,不碰 React 管理结构;React 原位改文本时自愈 unwrap 重扫);整文件引用强制要求扩展名(不误伤 `12:30` 时间文本),匹配点黏在更长单词内(`a2:app.ts:1-2`)跳过;跳过输入框/代码块/链接/宿主 chip 及本插件自有 UI 区域 |
118
+ | 2026-10-13 | v1.6.4 | 优化:发送后消息**保留引用标签文本**——chip 序列化从「空串隐身」改为输出编号标签(`2:package.json:2-3` / `🌐4:package.json:26-32`),编号取发送时刻冻结的 seq;消息里看得见引用了什么,AI 侧仍经宿主 systemPrompt 动态 section(`code-ui-references`)获知全部引用并调 `get_referenced_snippets` 读取 |
119
+ | 2026-10-13 | v1.6.3 | 优化:**引用指针不再出现在消息正文**——消息末尾的 `[code UI] 已标记 N 段代码引用…` 附加块整体移除(用户反馈:希望不展示出来但还让 AI 知道);引用信息改由宿主 `systemPrompt` 动态 section(order 2950,按 session 头取 `lastRefBatches` 批次渲染)注入模型上下文,AI 对用户的观感不可见;客户端 chip serialize 返回空串、consumePendingRefs 每次发送带 `{ids, sessionId}` 调 `use-references`(空=清批次) |
120
+ | 2026-10-13 | v1.6.2 | 修复:**引用 chip 插入静默失败**(0.2.0 适配)——0.2.0 起 `inputTriggers.live.controllers` 改按 SessionBinding 对象键控,字符串 sid 取不到 controller;chip 插入改走 `shell.insertReference` 公共契约(实时取 `conversation.input.shell(sid).snapshot` 的 draft/draftRev 计算插入位置),0.1.5-rc.x 与 0.2.0-rc.x 双兼容 |
121
+ | 2026-10-13 | v1.6.1 | 修复:**0.2.0-rc.2 上 `Cannot read properties of undefined (reading 'resolve')` 崩溃**——插件 apply 早于 fs-sandbox 挂载时永久取到 undefined;fs 服务延迟获取(首次调用时才解析)+ 原生 `fs/promises` 兜底 |
117
122
  | 2026-10-10 | v1.6.0 | 里程碑:**契约迁移到 DSH 官方 Typert Remote**——自定义 RPC 通道从 `connection.rpc.handle("/dsh-code-ui")`(0.1.5 起有 Cordis guard 405 bug,靠自挂 webServer 路由兜底)整体迁到 `/api` 通道:Host 侧 `CodeUiService` 经 `bindTypertRemote` + `@Remote` 标记注册并 `ctx.root.reflect.provide` 到宿主 root Context,api-gateway SRC 扫描自动发现(`internal/service` 事件失效缓存,插件后加载有效);浏览器侧 `rpc.call('/api', 'dsh-code-ui/<端点>', { args: { args: payload } })`,33 个端点名/参数/返回值全部不变;handle 调用、自挂路由、Host/Origin 认证门整套删除。同一套代码兼容宿主 0.1.5-rc.x 与 0.2.0-rc.x。配套:peer 增加 `@deepseek-ai/dsh >=0.1.5-rc.1 <0.3.0`(宿主安装门强制校验)、`dsh-typert-protocol`/`dsh-tools` 补声明,`dsh-settings` 范围扩到 `<0.3.0`(旧 `^0.1.x` 范围在 0.2.0 宿主会被拒载);`engines.dsh` 同步声明;`client.inject` 移除新版已不存在的 `dsh-client-runtime`;新增 `icon.svg` + `exports["./icon"]`(插件管理器不再回退默认图);卸载卡 NO_ENDPOINT 识别覆盖 gateway 新错误文案;新增 `tools/typert-smoke.mjs` 冒烟(真实 cordis 4.0.2 + typert-protocol 0.1.5-rc.2 上验证注册全链路 11/11) |
118
123
  | 2026-09-18 | v1.5.3 | 新增:**插件自卸载**——配置卡新增「卸载插件」危险区(两击确认防误触,5 秒不点第二下自动回弹)。主路径调官方 CLI(`dsh plugin --profile <p> remove dsh-code-ui`,自动识别三种启动形态:源码入口带 execArgv 再唤起 / POSIX 直装 / Windows .cmd shim 经 cmd.exe 引号包装);CLI 不可用自动退路 pnpm remove(hoist 漂移先 pnpm install 重建再重试);全程实时滚动 pnpm 日志;收尾按**磁盘真相**核对依赖行 / bundles 清单行 / node_modules(#65 幽灵清单保护:文件已删而清单未清时原子补删清单行,tmp+rename,防 profile 起不来)。成功绿字明确告知**需重启 DSH 彻底生效**(重启前插件仍运行,浏览器建议 Ctrl+F5);幂等(已卸载再点显示已是未安装状态);失败红字留档:错误码(PROFILE_NOT_FOUND / PLUGIN_CMD_FAILED / PNPM_FAILED / TIMEOUT / MANIFEST_LOCKED / FILES_STILL_PRESENT)+ 原因 + 逐条手动处理指导。旧宿主无此端点时给出「先升级插件再卸载」的 NO_ENDPOINT 兜底提示 |
119
124
  | 2026-09-11 | v1.5.2 | 修复:停靠面板拖拽分界线不再折行——编辑区去掉 min-width 下限,面板标题/按钮/状态栏等文本全部 nowrap + 省略号截断,拖到多窄只截断、不把文字竖排成一列;停靠面板左缘拖到宽度 < 80px 时自动切回 dsh UI(还原拖前宽度,下次展开不残留细条) |
package/lib/client.js CHANGED
@@ -188,6 +188,8 @@ window.__ModuleLoader__.load({
188
188
  .cdex-aiinput-tag-tr{background:#b45309;color:#fff;}
189
189
  .cdex-aiinput-tag-x{border:none;background:transparent;color:var(--cdex-accent-fg,#ffffff);cursor:pointer;font-size:13px;padding:0;line-height:1;opacity:.85;}
190
190
  .cdex-aiinput-tag-x:hover{opacity:1;}
191
+ /* 已发消息里的引用标签着色:与宿主输入框 chip 同色(--dsw-alias-state-business-primary) */
192
+ .cdex-sent-chip{color:var(--dsw-alias-state-business-primary,#4176e6);font-weight:500;margin:0 2px;border-radius:var(--dsw-radius-sm,4px);}
191
193
  .cdex-aiinput{display:flex;gap:10px;align-items:flex-end;padding:10px 12px 12px;}
192
194
  .cdex-aiinput-ta{flex:1;background:transparent;color:var(--dsw-alias-label-primary,#d4d4d4);border:none;outline:none;resize:none;font-family:inherit;font-size:13px;line-height:1.55;min-height:40px;max-height:160px;padding:4px 2px;}
193
195
  .cdex-aiinput-ta::placeholder{color:var(--dsw-alias-label-secondary,#8a8a8a);}
@@ -1473,6 +1475,109 @@ window.__ModuleLoader__.load({
1473
1475
  }
1474
1476
  }
1475
1477
 
1478
+ /* —— 已发消息引用标签着色:把气泡里 serialize 出去的 `N:文件:起-止` / `🌐N:文件` 文本
1479
+ 包成蓝色 chip 样式(与输入框里的 chip 同色)。宿主 projectUserText 只装饰 @// 形态,
1480
+ 不认识本插件的编号引用格式,故由本观察器做纯展示层 overlay:
1481
+ 只split/wrap 文本节点,不改写 React 管理结构;React 原位更新文本时由自愈逻辑unwrap 后重扫。 —— */
1482
+ // 带行号:N:文件名:起-止(文件名段不含空白/冒号,Windows basename 本无冒号)
1483
+ // 整文件:N:文件名.扩展名(强制要求扩展名,避免误伤 "12:30" 这类时间文本)
1484
+ const SENT_CHIP_RE = /(🌐)?\d+:([^\s::,,;;()()「」]+?:\d+-\d+|[^\s::,,;;()()「」]+?\.[A-Za-z0-9]{1,8})(?![\w.:-])/gu
1485
+ const SENT_CHIP_SKIP_SEL = 'textarea,input,script,style,pre,code,a,[contenteditable="true"],[data-ref-chip],.cdex-aiinput-tags,.cdex-refcard,.cdex-workbench,.cdex-dock,.cdex-sent-chip'
1486
+ function sentChipAncestorSkip(node) {
1487
+ let el = node.parentElement
1488
+ while (el) {
1489
+ if (el.nodeType === 1 && typeof el.matches === 'function' && el.matches(SENT_CHIP_SKIP_SEL)) return true
1490
+ el = el.parentElement
1491
+ }
1492
+ return false
1493
+ }
1494
+ function wrapSentChipsInTextNode(node) {
1495
+ const text = node.nodeValue
1496
+ if (!text || text.indexOf(':') < 0) return
1497
+ SENT_CHIP_RE.lastIndex = 0
1498
+ if (!SENT_CHIP_RE.test(text)) return
1499
+ SENT_CHIP_RE.lastIndex = 0
1500
+ const parent = node.parentNode
1501
+ if (!parent) return
1502
+ const frag = document.createDocumentFragment()
1503
+ let cursor = 0
1504
+ let m
1505
+ while ((m = SENT_CHIP_RE.exec(text)) !== null) {
1506
+ // 匹配点若黏在更长单词里(前一个是字母/数字/./:/-)跳过本次
1507
+ const prev = m.index > 0 ? text.charAt(m.index - 1) : ''
1508
+ if (/[\w.:-]/.test(prev)) continue
1509
+ if (m.index > cursor) frag.appendChild(document.createTextNode(text.slice(cursor, m.index)))
1510
+ const chip = document.createElement('span')
1511
+ chip.className = 'cdex-sent-chip'
1512
+ chip.setAttribute('data-cdex-sent', '1')
1513
+ chip.title = m[0]
1514
+ chip.textContent = m[0]
1515
+ frag.appendChild(chip)
1516
+ cursor = m.index + m[0].length
1517
+ }
1518
+ if (cursor === 0) return // 全部因黏词跳过
1519
+ if (cursor < text.length) frag.appendChild(document.createTextNode(text.slice(cursor)))
1520
+ parent.replaceChild(frag, node)
1521
+ }
1522
+ function scanSentChips(root) {
1523
+ // 自愈:React 原位改写文本后,chip 内容可能残缺,unwrap 让文本回归自然后再重扫
1524
+ try {
1525
+ root.querySelectorAll('.cdex-sent-chip').forEach((chip) => {
1526
+ const t = chip.textContent || ''
1527
+ SENT_CHIP_RE.lastIndex = 0
1528
+ const full = SENT_CHIP_RE.test(t)
1529
+ SENT_CHIP_RE.lastIndex = 0
1530
+ const m = full ? SENT_CHIP_RE.exec(t) : null
1531
+ if (!m || m.index !== 0 || m[0].length !== t.length) {
1532
+ const p = chip.parentNode
1533
+ if (!p) return
1534
+ while (chip.firstChild) p.insertBefore(chip.firstChild, chip)
1535
+ p.removeChild(chip)
1536
+ }
1537
+ })
1538
+ } catch (e) {}
1539
+ if (typeof document.createTreeWalker !== 'function') return
1540
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
1541
+ acceptNode(node) {
1542
+ const v = node.nodeValue
1543
+ if (!v || v.indexOf(':') < 0) return NodeFilter.FILTER_REJECT
1544
+ if (sentChipAncestorSkip(node)) return NodeFilter.FILTER_REJECT
1545
+ SENT_CHIP_RE.lastIndex = 0
1546
+ return SENT_CHIP_RE.test(v) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
1547
+ },
1548
+ })
1549
+ const targets = []
1550
+ let n
1551
+ while ((n = walker.nextNode()) !== null) targets.push(n)
1552
+ for (const t of targets) { try { wrapSentChipsInTextNode(t) } catch (e) {} }
1553
+ }
1554
+ function observeSentChips() {
1555
+ if (typeof MutationObserver === 'undefined' || typeof document === 'undefined' || !document.body) return () => {}
1556
+ let scheduled = false
1557
+ const scan = () => { scheduled = false; try { scanSentChips(document.body) } catch (e) {} }
1558
+ const schedule = () => {
1559
+ if (scheduled) return
1560
+ scheduled = true
1561
+ if (typeof window !== 'undefined' && window.requestAnimationFrame) window.requestAnimationFrame(scan)
1562
+ else setTimeout(scan, 60)
1563
+ }
1564
+ const mo = new MutationObserver(schedule)
1565
+ mo.observe(document.body, { childList: true, subtree: true, characterData: true })
1566
+ schedule()
1567
+ return () => {
1568
+ try { mo.disconnect() } catch (e) {}
1569
+ // unwrap 还原文本,不留痕迹
1570
+ try {
1571
+ document.querySelectorAll('.cdex-sent-chip').forEach((chip) => {
1572
+ const p = chip.parentNode
1573
+ if (!p) return
1574
+ while (chip.firstChild) p.insertBefore(chip.firstChild, chip)
1575
+ p.removeChild(chip)
1576
+ })
1577
+ } catch (e) {}
1578
+ }
1579
+ }
1580
+
1476
1581
  /* ============ §6 Pointer 处理层(窗口拖拽 / 缩放 / 模式条移动) ============ */
1477
1582
  function onBarPointerDown(e) {
1478
1583
  if (e.target && typeof e.target.closest === 'function' && e.target.closest('button')) return
@@ -2427,6 +2532,8 @@ window.__ModuleLoader__.load({
2427
2532
  )))
2428
2533
  // 消息区引用卡:观察宿主对话区,把带 [code UI] 指针的用户消息补挂引用卡(卸载时连卡移除)
2429
2534
  disposers.push(observeRefCards())
2535
+ // 已发消息引用标签蓝色着色:观察宿主对话区,把气泡里的 `N:文件:起-止` 文本包成蓝色 chip
2536
+ disposers.push(observeSentChips())
2430
2537
  // —— 路线A原型:@ 菜单行内引用 chip(宿主 ReferenceChipNode 装饰器)——
2431
2538
  // 注册官方触发源:输入框打 @ 弹菜单出现「code UI 引用」分组(与 @file 同菜单),
2432
2539
  // 选中即在光标处插入行内 chip;发送时宿主调 codec.serialize 把每颗 chip 在原位
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-ui",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Cursor 风格的 AI 代码编辑器工作台插件(DSH):文件树、多标签页编辑、语法高亮、引用/翻译行内标签给 AI、右侧停靠/悬浮窗口双布局",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",