@hyzyn/dsh-tty 0.22.0-rc.2 → 0.22.1
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/README.en.md +9 -4
- package/README.md +7 -4
- package/client.js +24 -24
- package/lib/index.d.ts +41 -4
- package/lib/index.js +118 -28
- package/lib/index.js.map +1 -1
- package/lib/shell-integration.d.ts +11 -0
- package/lib/shell-integration.js +27 -0
- package/lib/shell-integration.js.map +1 -1
- package/lib/ssh.js +72 -19
- package/lib/ssh.js.map +1 -1
- package/lib/tmux.d.ts +39 -3
- package/lib/tmux.js +82 -26
- package/lib/tmux.js.map +1 -1
- package/package.json +2 -2
- package/scripts/preview/harness.js +51 -0
- package/scripts/preview/mock-host.js +7 -1
- package/scripts/preview.mjs +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyzyn/dsh-tty",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "DSH Web GUI 的终端面板插件:侧边栏「终端」大弹窗,xterm.js + PTY 全交互终端(路线1),ssh2 原生 SSH 远程连接(方案 C),tmux 会话持久化(0.10.0),连接栏扩展点 ttyConnbar(0.13.0)、终端服务 ttyTerminal(0.14.0 open / 0.15.0 mount 就地嵌入)、面板内挂载位 ttyPanel(0.16.0 右侧 dock)、服务器状态条(0.17.0:按标签可见性采端主机 CPU / 内存 / 磁盘 / 在线时长 / TCP / 网速 / 温度,SSH 走同连接的非 PTY exec channel、本地走 os/proc,best-effort、不碰 PTY 流)、连接密码存入官方凭据存储(0.18.0:字段里只留 env: 引用、引用名按资源身份派生、候选来自凭据存储 ∪ 连接簿;设置卡片的连接编辑与连接对话框共用同一套表单)、Windows 宿主支持(0.18.1:默认 shell = %COMSPEC%、不做 POSIX 包装层、PowerShell 走 -NoLogo/-Command;shell 集成 / tmux / 状态条三项在 Windows 本地不适用,README 有完整边界)。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@deepseek-ai/schemastery": "^3.18.4",
|
|
49
|
-
"@hyzyn/dsh-kit": "0.5.
|
|
49
|
+
"@hyzyn/dsh-kit": "0.5.1",
|
|
50
50
|
"@xterm/headless": "^5.5.0",
|
|
51
51
|
"ssh2": "^1.17.0",
|
|
52
52
|
"ws": "^8.18.0"
|
|
@@ -1133,6 +1133,57 @@
|
|
|
1133
1133
|
await waitFor(() => q('.tt_toast'))
|
|
1134
1134
|
await sleep(200)
|
|
1135
1135
|
},
|
|
1136
|
+
|
|
1137
|
+
/*
|
|
1138
|
+
* D79:后台标签的 PTY 不得被压成 2×2。
|
|
1139
|
+
*
|
|
1140
|
+
* 现场(用户截图):非活动标签的 scrollback 按 2 列折行(webpack 进度条竖排成一个
|
|
1141
|
+
* 字符一列),同一标签里较新的行却正常。根因是发帧侧只看 `proposeDimensions()`
|
|
1142
|
+
* 有没有返回 undefined,而三种不可见状态都能给出「有值但荒谬」的尺寸:
|
|
1143
|
+
* - 别的标签正亮着(display:none)⇒ 容器 0 宽 0 高 ⇒ FitAddon 夹成 2×1;
|
|
1144
|
+
* - agent 开的标签刻意不 switchTab ⇒ termEl 没进 DOM,但 `term.open(termEl)` 让
|
|
1145
|
+
* `element.parentElement` 是游离的 termEl(truthy,骗过守卫)⇒ NaN;
|
|
1146
|
+
* - 面板最小化。
|
|
1147
|
+
* NaN 被 JSON.stringify 写成 `null`,宿主 `Number(null)` = 0 是有限数 ⇒ 夹到下限 ⇒ 2×2。
|
|
1148
|
+
*
|
|
1149
|
+
* 夹具:面板里先有一个**用户自己的**活动标签,再由宿主**主动推**一帧 sessions
|
|
1150
|
+
* (`{t:'sessions', list:[{owner:'agent',…}]}`,真实宿主在 agent 开关会话后会推)。客户端
|
|
1151
|
+
* 采纳它成标签,但因为已有活动标签而**不切换**(`adoptAgentSessions` 刻意不抢焦点)
|
|
1152
|
+
* ⇒ 它的 `.tt_term` 一直没进 DOM;随后 mock 回 ready ⇒ 走的正是真实事故那条路径。
|
|
1153
|
+
* 断言:**没有任何**退化尺寸的 resize 帧;且点开该标签后必须补发一次合法尺寸
|
|
1154
|
+
* (证明门槛没把正常路径一起拦死)。
|
|
1155
|
+
*/
|
|
1156
|
+
async 'resize-hidden'() {
|
|
1157
|
+
await openPanel() // 面板打开时会自带一个本地标签(用户那个活动标签)
|
|
1158
|
+
await waitFor(() => tabs().length === 1)
|
|
1159
|
+
// 宿主主动推 agent 会话(这个帧本来就是**推**的,不必等下一次 sessions 请求)
|
|
1160
|
+
const agentSession = { sid: 'agent-background', owner: 'agent', kind: 'local', cwd: window.__PREVIEW_CWD }
|
|
1161
|
+
window.__PREVIEW_AGENT_SESSIONS = [agentSession] // 后续 sessions 响应也带上它,别被当成已结束
|
|
1162
|
+
emit({ t: 'sessions', list: [agentSession] })
|
|
1163
|
+
await waitFor(() => tabs().length === 2)
|
|
1164
|
+
const agentTab = tabs().find((el) => sidOf(el) === 'agent-background')
|
|
1165
|
+
// 事故前提:两个标签,但文档里只有一个终端元素(agent 那个还没进 DOM)——
|
|
1166
|
+
// 夹具哪天不再复现这条路径,场景必须自己报错,而不是退化成永远绿的空断言。
|
|
1167
|
+
const hiddenWhileBackground = document.querySelectorAll('.tt_term').length === 1 && q('.tt_term').clientWidth > 0
|
|
1168
|
+
await sleep(300) // attach → ready → sendResize 跑完
|
|
1169
|
+
if (agentTab !== undefined) agentTab.click() // 切到它:变可见,PTY 必须拿到真实尺寸
|
|
1170
|
+
await sleep(300)
|
|
1171
|
+
window.__previewAssert = async () => {
|
|
1172
|
+
if (agentTab === undefined) return '没有采纳出 agent 标签(夹具失效)'
|
|
1173
|
+
if (!hiddenWhileBackground) return '夹具前提不成立:采纳时 agent 的终端元素已经可见(本场景失去意义)'
|
|
1174
|
+
if (document.querySelectorAll('.tt_term').length !== 2) return '切到该标签后终端元素仍不在文档里'
|
|
1175
|
+
const frames = () => window.__mockFrames || []
|
|
1176
|
+
const degenerate = frames().filter((f) => f.t === 'resize' && (!Number.isFinite(f.cols) || !Number.isFinite(f.rows) || f.cols < 20 || f.rows < 3))
|
|
1177
|
+
if (degenerate.length > 0) return '发出了退化尺寸的 resize 帧(后台标签被压扁):' + JSON.stringify(degenerate)
|
|
1178
|
+
const spawns = frames().filter((f) => f.t === 'spawn' || f.t === 'ssh')
|
|
1179
|
+
const badSpawn = spawns.filter((f) => !Number.isFinite(f.cols) || f.cols < 20 || f.rows < 3)
|
|
1180
|
+
if (badSpawn.length > 0) return '以退化尺寸建会话:' + JSON.stringify(badSpawn)
|
|
1181
|
+
// 正面对照:切到该标签后必须有一次合法尺寸的 resize(否则 PTY 会停在旧尺寸)
|
|
1182
|
+
const sane = frames().filter((f) => f.t === 'resize' && f.sid === 'agent-background' && Number.isFinite(f.cols) && f.cols >= 20 && f.rows >= 3)
|
|
1183
|
+
if (sane.length === 0) return '切到该标签后没有补发合法尺寸(PTY 会停在旧尺寸)'
|
|
1184
|
+
return null
|
|
1185
|
+
}
|
|
1186
|
+
},
|
|
1136
1187
|
}
|
|
1137
1188
|
|
|
1138
1189
|
const name = new URLSearchParams(location.search).get('scenario') || 'local'
|
|
@@ -242,6 +242,8 @@
|
|
|
242
242
|
return
|
|
243
243
|
}
|
|
244
244
|
window.__mockLog.push('in:' + msg.t)
|
|
245
|
+
// D79:留完整帧(不只类型)——「后台标签不得发出退化尺寸的 resize」这类断言要读 cols/rows
|
|
246
|
+
window.__mockFrames.push(msg)
|
|
245
247
|
const sid = msg.sid
|
|
246
248
|
if (msg.t === 'spawn' || msg.t === 'ssh') {
|
|
247
249
|
if (!SESSIONS.some((s) => s.sid === sid)) SESSIONS.push({ sid, attachable: true, kind: msg.t === 'ssh' ? 'ssh' : 'local', tmux: msg.persistName })
|
|
@@ -262,9 +264,12 @@
|
|
|
262
264
|
return
|
|
263
265
|
}
|
|
264
266
|
if (msg.t === 'sessions') {
|
|
267
|
+
// D79 夹具:`window.__PREVIEW_AGENT_SESSIONS` = 真实宿主推的「agent 开的会话」
|
|
268
|
+
// (owner:'agent',客户端采纳成标签但不 switchTab ⇒ 它一直在后台)。
|
|
269
|
+
const agentSessions = Array.isArray(window.__PREVIEW_AGENT_SESSIONS) ? window.__PREVIEW_AGENT_SESSIONS : []
|
|
265
270
|
const list = window.__PREVIEW_AT_LIMIT
|
|
266
271
|
? [{ sid: 'other-1', attachable: true }, { sid: 'other-2', attachable: true }, { sid: 'other-3', attachable: true }, { sid: 'other-4', attachable: true }]
|
|
267
|
-
: SESSIONS.slice()
|
|
272
|
+
: SESSIONS.slice().concat(agentSessions)
|
|
268
273
|
setTimeout(() => this._deliver({ t: 'sessions', list }), 5)
|
|
269
274
|
return
|
|
270
275
|
}
|
|
@@ -341,6 +346,7 @@
|
|
|
341
346
|
}
|
|
342
347
|
window.__mockSockets = []
|
|
343
348
|
window.__mockLog = []
|
|
349
|
+
window.__mockFrames = []
|
|
344
350
|
window.WebSocket = MockSocket
|
|
345
351
|
|
|
346
352
|
/* ---------- module loader ---------- */
|
package/scripts/preview.mjs
CHANGED