dsh-neotui 0.4.3 → 0.4.4

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/CHANGELOG.md CHANGED
@@ -1,9 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.4 — 2026-08-26
4
+
5
+ ### Fixed
6
+
7
+ - **兼容 dsh 0.1.2-rc.1**:dsh-base 收编 storage 行与 session-projection-cache 后,组合树同 id 条目按新加载器规则去重(app 补丁仅保留裸 id 覆盖行做 attach 隔离);`dsh-host-apiproxy` 包停更,其 /api 网关职责并入 connection 行的 node 半端(旧包在新版下启动即崩);补挂 message-feedback 行。
8
+
9
+ ### Added
10
+
11
+ - **统一前缀体系**:Ctrl+Space 面板前缀页与前缀引擎共用同一张表(which-key 模式)——面板内按键与 NORMAL 前缀同义,`p` 在面板内链入窗格子表(权限迁至 `a`);`Ctrl+H`/`Ctrl+L` 切换主窗口标签页(上一/下一,`Shift+Tab` 保留;`Ctrl+H` 原技能键释放);前缀触发字符可在 tui-config.json `prefixKeys` 段重定义(`Ctrl+K` 打开配置)。
12
+
13
+ - **窗口/标签页操作模型重构**:会话列表/窗口模式——`Ctrl+←→` 或 `Tab` 在「会话列表 ↔ 主窗口」间切换,`Shift+Tab` 在主窗口内循环标签页(对话/轨迹/子代理/后台任务,顶部标签条);INSERT 内所有结构键保持编辑语义不冲突;侧栏预览卡片触发键 `p`(仅列表窗口作用域生效)。
14
+ - **nvim 式逐层前缀键**:NORMAL 下按 `p` 进入待定层(状态栏提示 + Esc/2s/错误键取消),
15
+ - `p r/l/u/n`:把当前窗口向 右/左/上/下 分屏(新窗口自动成为**空白会话**窗口,窗口=会话容器;(p c) 关窗后会话保留在侧栏,关掉全部主窗口自动生成空白会话保底);
16
+ - `p c`:关闭当前窗口(焦点自动落到相邻窗口,布局合并恢复);
17
+ - 硬护栏:最小 40 列×12 行、窗口 ≤8、嵌套深度 ≤4,超限拒绝并提示。
18
+ - **`Ctrl+W` 回收为工作区选择小 buffer**:为当前会话选择/移动工作区(`workspace.insertSessionBefore`),附带 重命名/新建 工作区管理行;旧的大面板仍可从命令面板进入。
19
+ - **分屏分隔线鼠标拖动调节比例**(0.2–0.8 夹紧,实时重排)。
20
+ - **状态栏会话标题前实时显示窗口号前缀**(`W1 · <标题> · …`,多窗导航一目了然)。
21
+ - **子代理窗格续接输入**:`i`/`Tab` 聚焦、Enter 发送 `subagent.prompt(mode:"continuable")`(Esc 回列表;`i` 仅在子代理窗格局部生效)。
22
+ - **`@` 文件提及**:输入中 `@src/…` Tab 补全(目录内扫描、可循环),发送时非图片文件/目录自动附加为有界文本块(图片走原图片通道,不存在的 `@` 静默忽略)。
23
+ - **终端 cell 几何探测**(CSI 14t/16t):启动探测像素/字符尺寸,Kitty 预览按真实 cell 比例(非假设 2:1)计算,无响应回退。
24
+ - 既有未发布功能一并进入本 pre:Tab 路径补全、`/rewind` 回退分支、Ctrl+Space 前缀面板(s/m/d/p/h/r/c/?/G/e)、键位释放(Ctrl+F///M/D/A/J/N/T/F8)、输入历史持久化+搜索、`/btw` 侧问(一次性密钥子进程,密钥不进 TUI)、来源徽标(●提问/◇注入/◆目标轮)、侧栏会话预览卡片、场景化帮助(Shift+/?)、外部编辑器草稿(前缀 `G`)。
25
+
26
+
3
27
 
4
28
  ### Added
5
29
 
6
30
  - 欢迎页改版:**吉祥物 logo 默认关闭**(此前的 40×19 象限块/58×27 半块等实现保留,`Ctrl+R` 面板可随时启用预设/自定义/关闭;视觉迭代未能达成理想效果,先回退到仅标题)(真实色 255 色调色板,数据内嵌 `src/logo.js`,去背景、无残块、裁剪掉原图底部黑条);自定义 logo JSON 支持象限格式(`[mask,fg,bg]`)与旧半块格式(`[top,bottom]`);品牌行用 **`█▀▄` 半块像素字** 绘制(`DEEPSEEK` / `DSH NEOTUI` 两行大字标,5×7 像素字模),**每约 3.5 秒一道斜向高光扫过字标**(0.9s 扫程,纯 TUI 渲染并随主题语义色,扫过字标全宽含末尾 `I`;`DSH NEOTUI` 用深灰底保证白色高光可见);**logo 三态** `Ctrl+R` buffer:预设(内置)/ 自定义(复用 Ctrl+O 与新建工作区的 `UploadPicker` 单选 `{palette, grid}` 40×19 JSON)/ 关闭(仅标题),持久化到配置文件;版本更新提示收敛到底部(仅「可更新/失败/检查中」时显示,可点击检查,**最新则不提示**);模式选择从内联 4 列表收敛为底部提示「模式: <当前> · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自定义预设的方向一致;矮视口(<32 行或 <62 列)回退为普通文本品牌行。
31
+
32
+ - 启动动画(经典游戏启动器风格,**opt-in meme**):纯黑 → 白色从屏幕中心圆形扩散至全屏(1s)→ 四行健康游戏忠告逐句淡入(2s)→ 忠告淡出的同时 `DEEPSEEK` 半块大字交叉淡入(2s)→ 底部闪烁「按任意键进入游戏」,任意键进入主界面——全程纯 ANSI(`█▀▄`),无图片协议;默认关闭,`--launcher-anime`(或 `DSH_TUI_LAUNCHER_ANIME=1`)显式启用,`DSH_TUI_NO_SPLASH=1` 强制关闭;`--script` 模式不播放,窄小终端自动跳过。
33
+
34
+ ## 0.4.2 — 2026-08-23
35
+
36
+ ### Added
37
+
38
+ - 欢迎页改版:40×19 半块渲染的 DeepSeek 吉祥物 logo(真实色 255 色调色板 + 逐格上下半色,数据内嵌 `src/logo.js`,去背景、无残块、裁剪掉原图底部黑条);品牌行用 **`█▀▄` 半块像素字** 绘制(`DEEPSEEK` / `DSH NEOTUI` 两行大字标,5×7 像素字模),**每约 3.5 秒一道斜向高光扫过字标**(0.9s 扫程,纯 TUI 渲染并随主题语义色,扫过字标全宽含末尾 `I`;`DSH NEOTUI` 用深灰底保证白色高光可见);**logo 三态** `Ctrl+R` buffer:预设(内置)/ 自定义(复用 Ctrl+O 与新建工作区的 `UploadPicker` 单选 `{palette, grid}` 40×19 JSON)/ 关闭(仅标题),持久化到配置文件;版本更新提示收敛到底部(仅「可更新/失败/检查中」时显示,可点击检查,**最新则不提示**);模式选择从内联 4 列表收敛为底部提示「模式: <当前> · F9 打开模式选择(支持自定义)」——与 WebUI 鼓励自定义预设的方向一致;矮视口(<32 行或 <62 列)回退为普通文本品牌行。
7
39
  - 启动动画(经典游戏启动器风格,**opt-in meme**):纯黑 → 白色从屏幕中心圆形扩散至全屏(1s)→ 四行健康游戏忠告逐句淡入(2s)→ 忠告淡出的同时 `DEEPSEEK` 半块大字交叉淡入(2s)→ 底部闪烁「按任意键进入游戏」,任意键进入主界面——全程纯 ANSI(`█▀▄`),无图片协议;默认关闭,`--launcher-anime`(或 `DSH_TUI_LAUNCHER_ANIME=1`)显式启用,`DSH_TUI_NO_SPLASH=1` 强制关闭;`--script` 模式不播放,窄小终端自动跳过。
8
40
 
9
41
  ## 0.4.1 — 2026-08-22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-neotui",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Keyboard-first Vim/tmux-style terminal client for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "engines": {
package/src/config.js CHANGED
@@ -73,6 +73,37 @@ export function busyEnter() {
73
73
  }
74
74
 
75
75
  /** Persist the newest 20 unique cross-session search queries (oldest→newest). */
76
+
77
+
78
+ /** 前缀键字符表(leader/面板首屏与窗格子表共用)。可在 tui-config.json 的
79
+ * prefixKeys 段覆盖单键(如 { "rewind": "R" });非法字符回退默认。 */
80
+ const PREFIX_KEY_DEFAULTS = {
81
+ search: "s", model: "m", theme: "d", history: "h", rewind: "r",
82
+ config: "c", help: "?", editor: "G", permission: "a", panes: "p",
83
+ };
84
+ export function prefixKeys() {
85
+ const raw = loadTuiConfig().prefixKeys ?? {};
86
+ const out = { ...PREFIX_KEY_DEFAULTS };
87
+ for (const [k, v] of Object.entries(raw)) {
88
+ if (k in out && typeof v === "string" && v.length === 1) out[k] = v;
89
+ }
90
+ return out;
91
+ }
92
+
93
+ /** Last ~50 user prompts typed into the chat input (persisted, deduped). */
94
+ export function promptHistory() {
95
+ const raw = loadTuiConfig().promptHistory;
96
+ return Array.isArray(raw) ? raw.filter((x) => typeof x === "string") : [];
97
+ }
98
+
99
+ export function rememberPrompt(text) {
100
+ const t = String(text ?? "").trim();
101
+ if (!t) return promptHistory();
102
+ const next = [t, ...promptHistory().filter((x) => x !== t)].slice(0, 50);
103
+ saveTuiConfig({ promptHistory: next });
104
+ return next;
105
+ }
106
+
76
107
  export function searchHistory() {
77
108
  const raw = loadTuiConfig().searchHistory;
78
109
  if (!Array.isArray(raw)) return [];
@@ -143,9 +143,11 @@ export class UploadPicker extends Widget {
143
143
  const inner=this.w-4,l=Math.floor(inner*.25),m=Math.floor(inner*.38),x=this.x+5+l+m,y=this.y+4;
144
144
  const sourceAspect=p.pixelWidth&&p.pixelHeight?p.pixelWidth/p.pixelHeight:1;
145
145
  // WezTerm does not consistently infer the missing dimension. Compute an
146
- // aspect-fit box ourselves using the terminal's ~2:1 cell height ratio.
147
- let cols=Math.max(4,p.width),rows=Math.max(3,Math.round(cols/sourceAspect/2));
148
- if(rows>p.height){rows=Math.max(3,p.height);cols=Math.max(4,Math.min(p.width,Math.round(rows*sourceAspect*2)));}
146
+ // aspect-fit box ourselves; use the probed cell ratio (CSI 14t/16t) when
147
+ // the terminal answered, otherwise fall back to the ~2:1 cell ratio.
148
+ const ratio = this.app.term?.cellAspect?.ratio ?? 0.5;
149
+ let cols=Math.max(4,p.width),rows=Math.max(3,Math.round(cols/sourceAspect*ratio));
150
+ if(rows>p.height){rows=Math.max(3,p.height);cols=Math.max(4,Math.min(p.width,Math.round(rows*sourceAspect/ratio)));}
149
151
  return payload+`\x1b[${y};${x}H\x1b_Ga=p,i=${this.kittyId},c=${cols},r=${rows},q=2\x1b\\`;
150
152
  }
151
153
  clearKitty(){if(this.kittyId&&this.app.term?.output)this.app.term.output.write(`\x1b_Ga=d,d=i,i=${this.kittyId},q=2\x1b\\`);this.kittyId=null;this.kittyShownKey=null;this.imagePreview=null;if(this.app.screen){this.app.screen.prev=null;this.app.redraw();}}
@@ -22,7 +22,7 @@ export const DEFAULT_KEYBINDINGS = {
22
22
  expandInput: { mode: "insert", key: "Ctrl+L", key2: "" },
23
23
  undoInput: { mode: "insert", key: "Ctrl+Z", key2: "" },
24
24
  redoInput: { mode: "insert", key: "Ctrl+Y", key2: "" },
25
- sessionFilter: { mode: "normal", key: "Ctrl+F", key2: "/" },
25
+ sessionFilter: { mode: "normal", key: "", key2: "" }, // freed: cross-session search lives on the Ctrl+Space prefix page (s)
26
26
  copySelection: { mode: "normal", key: "Ctrl+Shift+C", key2: "" },
27
27
  newSession: { mode: "normal", key: "n", key2: "" },
28
28
  top: { mode: "normal", key: "g g", key2: "" },
@@ -30,18 +30,18 @@ export const DEFAULT_KEYBINDINGS = {
30
30
  prevQuestion: { mode: "normal", key: "[", key2: "" },
31
31
  nextQuestion: { mode: "normal", key: "]", key2: "" },
32
32
  panel: { mode: "all", key: "Ctrl+Space", key2: "F7" },
33
- model: { mode: "normal", key: "Ctrl+M", key2: "" },
34
- trajectory: { mode: "normal", key: "Ctrl+T", key2: "" },
33
+ model: { mode: "normal", key: "", key2: "" }, // freed: model switch moved to the prefix page (m)
34
+ trajectory: { mode: "normal", key: "", key2: "" }, // freed: panel container moved to the prefix page (t)
35
35
  panePrev: { mode: "normal", key: "Ctrl+Left", key2: "" },
36
36
  paneNext: { mode: "normal", key: "Ctrl+Right", key2: "" },
37
- permissionRotate: { mode: "normal", key: "F8", key2: "" },
37
+ permissionRotate: { mode: "normal", key: "", key2: "" }, // freed: permission modes moved to the prefix page (p)
38
38
  workspace: { mode: "normal", key: "Ctrl+W", key2: "" },
39
39
  settings: { mode: "normal", key: "Ctrl+S", key2: "" },
40
- subagent: { mode: "normal", key: "Ctrl+A", key2: "" },
41
- skills: { mode: "normal", key: "Ctrl+H", key2: "" },
40
+ subagent: { mode: "normal", key: "", key2: "" }, // freed: subagent lives on the unified panel page 2 (Ctrl+T → Tab)
41
+ skills: { mode: "normal", key: "", key2: "" }, // freed: Ctrl+H is now tabPrev
42
42
  goal: { mode: "normal", key: "Ctrl+G", key2: "" },
43
- jobs: { mode: "normal", key: "Ctrl+J", key2: "" },
44
- queue: { mode: "normal", key: "Ctrl+N", key2: "" },
43
+ jobs: { mode: "normal", key: "", key2: "" }, // freed: jobs live on the unified panel page 3 (Ctrl+T → Tab)
44
+ queue: { mode: "normal", key: "", key2: "" }, // freed: queue lives on the unified panel page 3 (Ctrl+T → Tab)
45
45
  busyEnter: { mode: "normal", key: "Ctrl+Y", key2: "" },
46
46
  attachments: { mode: "normal", key: "Ctrl+O", key2: "" },
47
47
  stepJump: { mode: "normal", key: "Ctrl+E", key2: "" },
@@ -50,15 +50,18 @@ export const DEFAULT_KEYBINDINGS = {
50
50
  addWorkspace: { mode: "normal", key: "Ctrl+Shift+W", key2: "" },
51
51
  commandPalette: { mode: "normal", key: "Ctrl+P", key2: "" },
52
52
  modePicker: { mode: "normal", key: "F9", key2: "" },
53
- themePicker: { mode: "normal", key: "Ctrl+D", key2: "" },
53
+ themePicker: { mode: "normal", key: "", key2: "" }, // freed: theme picker moved to the Ctrl+Space prefix page (d)
54
54
  logoPicker: { mode: "normal", key: "Ctrl+R", key2: "" },
55
+ help: { mode: "normal", key: "Shift+/", key2: "Shift+?" }, // 场景帮助(Shift+/ 或 Shift+?)——按焦点路由
56
+ tabNext: { mode: "normal", key: "Ctrl+L", key2: "" }, // 主窗口标签页下一页
57
+ tabPrev: { mode: "normal", key: "Ctrl+H", key2: "" }, // 主窗口标签页上一页
55
58
  quitDouble: { mode: "normal", key: "Ctrl+C", key2: "" },
56
59
  quit: { mode: "all", key: "Ctrl+Q", key2: "" },
57
60
  };
58
61
 
59
62
  /** App-level dispatch precedence: the first matching binding wins. */
60
63
  export const KEYBINDING_ORDER = [
61
- "sessionFilter", "panel", "panePrev", "paneNext", "permissionRotate", "editConfig", "addWorkspace", "commandPalette", "modePicker", "themePicker", "logoPicker", "copySelection", "quitDouble", "quit",
64
+ "sessionFilter", "help", "tabNext", "tabPrev", "panel", "panePrev", "paneNext", "permissionRotate", "editConfig", "addWorkspace", "commandPalette", "modePicker", "themePicker", "logoPicker", "copySelection", "quitDouble", "quit",
62
65
  "model", "trajectory", "workspace", "settings", "subagent", "skills", "goal",
63
66
  "jobs", "queue", "busyEnter", "attachments", "stepJump", "sidebar",
64
67
  ];