@hyzyn/dsh-tty 0.22.2 → 0.24.0
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 +70 -10
- package/README.md +59 -10
- package/client.js +411 -37
- package/lib/ansi.d.ts +22 -0
- package/lib/ansi.js +37 -0
- package/lib/ansi.js.map +1 -0
- package/lib/assist.d.ts +84 -0
- package/lib/assist.js +265 -0
- package/lib/assist.js.map +1 -0
- package/lib/index.d.ts +186 -0
- package/lib/index.js +679 -45
- package/lib/index.js.map +1 -1
- package/lib/keys.d.ts +23 -0
- package/lib/keys.js +78 -0
- package/lib/keys.js.map +1 -0
- package/lib/probe.d.ts +7 -0
- package/lib/probe.js +52 -9
- package/lib/probe.js.map +1 -1
- package/lib/ssh.d.ts +12 -0
- package/lib/ssh.js +42 -2
- package/lib/ssh.js.map +1 -1
- package/package.json +4 -4
- package/scripts/integration.mjs +160 -2
- package/scripts/lib/proxy-late-evidence.mjs +27 -0
- package/scripts/preview/harness.js +658 -0
- package/scripts/preview/mock-host.js +87 -2
- package/scripts/preview.mjs +5 -0
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
persistence: window.__PREVIEW_PERSISTENCE || 'off',
|
|
41
41
|
endOnPageClose: false,
|
|
42
42
|
statsEnabled: true,
|
|
43
|
+
// AI 辅助:**默认关**(与宿主 Config 的默认值一致;夹具要开就自己改这一项,
|
|
44
|
+
// 顺带把「关着时一个徽标都不摆」这条也验掉)
|
|
45
|
+
assistEnabled: false,
|
|
46
|
+
assistProvider: '',
|
|
47
|
+
assistModel: '',
|
|
43
48
|
sftpLimits: { maxDownloadMb: 1024, maxUploadMb: 2048, maxUploadFiles: 1000 },
|
|
44
49
|
toolsRegistered: true,
|
|
45
50
|
sshHosts: [
|
|
@@ -83,8 +88,59 @@
|
|
|
83
88
|
]
|
|
84
89
|
|
|
85
90
|
const route = (url, body) => {
|
|
86
|
-
if (url.indexOf('/api/dsh-tty/config') === 0)
|
|
91
|
+
if (url.indexOf('/api/dsh-tty/config') === 0) {
|
|
92
|
+
/*
|
|
93
|
+
* POST = 保存:**照真实宿主的样子把补丁应用进 CONFIG 再回快照**(不是原样回一份旧的)。
|
|
94
|
+
* 这是 D91 的照妖镜——客户端保存成功后拿**这份响应**重置整张表单,夹具要永远回旧值,
|
|
95
|
+
* 就等于把「到底存下去了没有」这件事从测试里抠掉了。
|
|
96
|
+
*/
|
|
97
|
+
if (body !== null && typeof body === 'object') {
|
|
98
|
+
window.__mockConfigPosts = (window.__mockConfigPosts || []).concat([body])
|
|
99
|
+
for (const [key, value] of Object.entries(body)) {
|
|
100
|
+
if (key === 'sshHosts' || key === 'tunnels' || key === 'hostKeys' || key === 'sftpLimits') continue
|
|
101
|
+
CONFIG[key] = value
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { ok: true, config: CONFIG }
|
|
105
|
+
}
|
|
87
106
|
if (url.indexOf('/api/dsh-tty/shells') === 0) return { ok: true, shells: ['/bin/zsh', '/bin/bash', '/bin/sh', '/opt/homebrew/bin/fish'] }
|
|
107
|
+
/*
|
|
108
|
+
* 模型候选目录(/api/dsh-tty/model-catalog)。形状与宿主一致:providers **恒有**、models
|
|
109
|
+
* **按 provider 才有**(目录是 per-provider 的)。场景据此断言「选了 provider,模型候选跟着来」。
|
|
110
|
+
*/
|
|
111
|
+
if (url.indexOf('/api/dsh-tty/model-catalog') === 0) {
|
|
112
|
+
const provider = new URL(url, 'http://preview').searchParams.get('provider') || ''
|
|
113
|
+
window.__mockLog.push('fetch:model-catalog:' + provider)
|
|
114
|
+
/*
|
|
115
|
+
* `window.__PREVIEW_CATALOG = { fail: true }` 模拟**宿主没这条路由**(多半是没重启):
|
|
116
|
+
* 真机上那是 401,客户端只看得到「拿不到」。这是用户实测撞到的那个现场,
|
|
117
|
+
* 界面必须说「拿不到」,而不是笼统说一句「没有候选」把人引到「我配错了」上去。
|
|
118
|
+
*/
|
|
119
|
+
const canned = window.__PREVIEW_CATALOG
|
|
120
|
+
if (canned && canned.fail === true) return { ok: false, error: 'unauthorized' }
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
available: true,
|
|
124
|
+
providers: [
|
|
125
|
+
{ id: 'mock-provider', name: 'Mock Provider' },
|
|
126
|
+
{ id: 'tokenrhythm', name: 'tokenrhythm' },
|
|
127
|
+
],
|
|
128
|
+
groups: [
|
|
129
|
+
{ id: 'mock-provider', name: 'Mock Provider', models: [
|
|
130
|
+
{ id: 'mock-fast', name: 'mock-fast' },
|
|
131
|
+
{ id: 'mock-reasoning', name: 'Mock Reasoning' },
|
|
132
|
+
] },
|
|
133
|
+
{ id: 'tokenrhythm', name: 'tokenrhythm', models: [
|
|
134
|
+
{ id: 'deepseek-flash', name: 'DeepSeek Flash' },
|
|
135
|
+
] },
|
|
136
|
+
],
|
|
137
|
+
models: provider === '' ? [] : [
|
|
138
|
+
{ id: 'mock-fast', name: 'mock-fast' },
|
|
139
|
+
{ id: 'mock-reasoning', name: 'Mock Reasoning' },
|
|
140
|
+
],
|
|
141
|
+
provider: provider,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
88
144
|
if (url.indexOf('/api/dsh-tty/env-vars') === 0) return { ok: true, names: ['STAGING_DB_PASSWORD', 'PROD_DEPLOY_KEY', 'GITHUB_TOKEN', 'OPENAI_API_KEY', 'SSH_PASSPHRASE'] }
|
|
89
145
|
if (url.indexOf('/api/dsh-tty/tunnels') === 0) return { ok: true, tunnels: [
|
|
90
146
|
{ name: 'staging-pg', state: 'active', connections: 2, totalConnections: 17, error: '', lastForwardError: '' },
|
|
@@ -101,6 +157,31 @@
|
|
|
101
157
|
hostkey: { state: 'matched' },
|
|
102
158
|
auth: { ok: true, ms: 296 },
|
|
103
159
|
} }
|
|
160
|
+
/*
|
|
161
|
+
* AI 辅助「失败即解释」(0.24.0)。答案由 `window.__PREVIEW_ASSIST` 供给:
|
|
162
|
+
* - `{ error: '…' }` → 模拟「没有可用的模型路由」(宿主也是这个形状:ok:false + error)
|
|
163
|
+
* - `{ answer, command }` → 正常答案(command 就是宿主的 extractCommandFromAnswer 结果)
|
|
164
|
+
* 默认答案里刻意塞了 **HTML 与围栏**:答案必须原样显示(`<b>` 不许被当标签解析),
|
|
165
|
+
* 而围栏块是宿主机侧取命令的来源。
|
|
166
|
+
*/
|
|
167
|
+
if (url.indexOf('/api/dsh-tty/assist') === 0) {
|
|
168
|
+
// 记一笔:场景要断言「点徽标之前不许有请求」(零输入入口 ≠ 自动外发)
|
|
169
|
+
window.__mockLog.push('fetch:assist')
|
|
170
|
+
const canned = window.__PREVIEW_ASSIST
|
|
171
|
+
if (canned && typeof canned.error === 'string') return { ok: false, error: canned.error }
|
|
172
|
+
// 形状与真实宿主一致:answer 是**已去标记**的正文(Markdown 的解析在宿主做)
|
|
173
|
+
const answer = canned && typeof canned.answer === 'string'
|
|
174
|
+
? canned.answer
|
|
175
|
+
: '发生了什么:<b>这不是加粗</b>,是输出里的原文;依赖装漏了一个。\n\n下一步:\n装完再跑一次。'
|
|
176
|
+
return {
|
|
177
|
+
ok: true,
|
|
178
|
+
answer,
|
|
179
|
+
command: canned && typeof canned.command === 'string' ? canned.command : 'npm install',
|
|
180
|
+
route: 'mock/model',
|
|
181
|
+
chars: 812,
|
|
182
|
+
ms: 37,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
104
185
|
if (url.indexOf('/api/dsh-tty/ssh-config') === 0) return { ok: true, hosts: [] }
|
|
105
186
|
if (url.indexOf('/api/dsh-tty/known-hosts') === 0) return { ok: true, keys: [] }
|
|
106
187
|
if (url.indexOf('/api/dsh-docker/') === 0) return dockerRoute(url.slice('/api/dsh-docker'.length), body)
|
|
@@ -267,9 +348,13 @@
|
|
|
267
348
|
// D79 夹具:`window.__PREVIEW_AGENT_SESSIONS` = 真实宿主推的「agent 开的会话」
|
|
268
349
|
// (owner:'agent',客户端采纳成标签但不 switchTab ⇒ 它一直在后台)。
|
|
269
350
|
const agentSessions = Array.isArray(window.__PREVIEW_AGENT_SESSIONS) ? window.__PREVIEW_AGENT_SESSIONS : []
|
|
351
|
+
// D85 夹具:`window.__PREVIEW_EXITED_SESSIONS` = 宿主的**只读保留态**(进程已退出、
|
|
352
|
+
// 带 `exited: true`;D77 起这种条目会在 sessions 帧里一直留着,最多 16 条)。
|
|
353
|
+
// 它们不占并发名额,客户端的上限预检也不能数它们。
|
|
354
|
+
const exitedSessions = Array.isArray(window.__PREVIEW_EXITED_SESSIONS) ? window.__PREVIEW_EXITED_SESSIONS : []
|
|
270
355
|
const list = window.__PREVIEW_AT_LIMIT
|
|
271
356
|
? [{ sid: 'other-1', attachable: true }, { sid: 'other-2', attachable: true }, { sid: 'other-3', attachable: true }, { sid: 'other-4', attachable: true }]
|
|
272
|
-
: SESSIONS.slice().concat(agentSessions)
|
|
357
|
+
: SESSIONS.slice().concat(agentSessions, exitedSessions)
|
|
273
358
|
setTimeout(() => this._deliver({ t: 'sessions', list }), 5)
|
|
274
359
|
return
|
|
275
360
|
}
|
package/scripts/preview.mjs
CHANGED
|
@@ -65,6 +65,7 @@ const SCENARIOS = [
|
|
|
65
65
|
['ssh-probe', 'SSH 连接对话框(试连结果)'],
|
|
66
66
|
['ssh-edit', 'SSH 连接对话框(编辑)'],
|
|
67
67
|
['settings', '设置卡片'],
|
|
68
|
+
['settings-assist', '设置卡片:AI 辅助小节(默认关 + 模型路由一个控件)'],
|
|
68
69
|
['settings-docker', '设置卡片(docker 与 tty 并排对照)'],
|
|
69
70
|
['docker-stale-book', 'docker 设置卡片:失效的连接簿引用要看得出来'],
|
|
70
71
|
['tunnel-edit', '端口转发:编辑隧道(回填 / 改端口换名 / 停用的不被启用)'],
|
|
@@ -89,6 +90,10 @@ const SCENARIOS = [
|
|
|
89
90
|
['dock-pane-bottom', '底部挂载位 + 终端重排(视口钉底)'],
|
|
90
91
|
['docker-dock', '连接栏「容器」→ docker 面板挂进右侧 dock(终端不被盖住)'],
|
|
91
92
|
['resize-hidden', '后台标签的 PTY 不得被压成 2×2(D79)'],
|
|
93
|
+
['limit-retained', '只读保留(已退出)的会话不算并发名额(D85)'],
|
|
94
|
+
['tab-add', '「+」贴着页签,且标签栏溢出时不跟着滚走(D89)'],
|
|
95
|
+
['tab-list', '标签栏溢出:「⋯」标签列表(切/关/Esc,0.23.0)'],
|
|
96
|
+
['tty-assist', 'AI 辅助「失败即解释」:徽标 → 答案 → 填入不回车(0.24.0)'],
|
|
92
97
|
]
|
|
93
98
|
|
|
94
99
|
if (flags.has('--list')) {
|