@hyzyn/dsh-tty 0.18.2 → 0.19.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 +85 -34
- package/README.md +65 -29
- package/client.js +51 -37
- package/lib/index.d.ts +367 -1
- package/lib/index.js +420 -138
- package/lib/index.js.map +1 -1
- package/lib/known-hosts.d.ts +7 -1
- package/lib/known-hosts.js +26 -12
- package/lib/known-hosts.js.map +1 -1
- package/lib/probe.d.ts +2 -2
- package/lib/probe.js +8 -36
- package/lib/probe.js.map +1 -1
- package/lib/sftp.d.ts +63 -10
- package/lib/sftp.js +244 -44
- package/lib/sftp.js.map +1 -1
- package/lib/shell-integration.js +33 -13
- package/lib/shell-integration.js.map +1 -1
- package/lib/ssh.d.ts +21 -7
- package/lib/ssh.js +114 -19
- package/lib/ssh.js.map +1 -1
- package/lib/tunnels.js +67 -13
- package/lib/tunnels.js.map +1 -1
- package/package.json +7 -2
- package/scripts/build-client.mjs +30 -0
- package/scripts/integration.mjs +1691 -0
- package/scripts/lib/test-sshd.mjs +314 -0
- package/scripts/live.mjs +68 -0
- package/scripts/preview/harness.html +75 -0
- package/scripts/preview/harness.js +875 -0
- package/scripts/preview/mock-host.js +365 -0
- package/scripts/preview.mjs +502 -0
- package/scripts/probe-route-smoke.mjs +111 -0
- package/scripts/probe-smoke.mjs +143 -0
- package/scripts/probe.mjs +240 -0
- package/scripts/sftplimits-smoke.mjs +92 -0
- package/scripts/ssh-smoke.mjs +593 -0
- package/scripts/tui-smoke.mjs +112 -0
- package/scripts/windows-smoke.mjs +179 -0
package/lib/index.d.ts
CHANGED
|
@@ -67,7 +67,12 @@
|
|
|
67
67
|
* 子进程),必须 best-effort:失败降级为对顶层 shell 直接 SIGKILL。
|
|
68
68
|
*/
|
|
69
69
|
import type { Context } from '@deepseek-ai/cordis';
|
|
70
|
-
import
|
|
70
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
71
|
+
import WebSocket from 'ws';
|
|
72
|
+
import xtermHeadless from '@xterm/headless';
|
|
73
|
+
declare const HeadlessTerminal: typeof xtermHeadless.Terminal;
|
|
74
|
+
type HeadlessTerminal = InstanceType<typeof HeadlessTerminal>;
|
|
75
|
+
import type { HostKeyRecord, SshHostEntry, TermHandle } from './ssh.js';
|
|
71
76
|
import type { TunnelSpec } from './tunnels.js';
|
|
72
77
|
export type { HostKeyRecord } from './ssh.js';
|
|
73
78
|
export interface Config {
|
|
@@ -119,4 +124,365 @@ export interface SftpLimits {
|
|
|
119
124
|
/** 一次批量/拖拽上传的文件数上限。默认 1000。 */
|
|
120
125
|
maxUploadFiles: number;
|
|
121
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* 本地 PTY 顶层 shell 的 best-effort 强杀(D48)。
|
|
129
|
+
*
|
|
130
|
+
* **Windows 绝不能带 signal**:node-pty 的 `WindowsTerminal.kill(signal)` 会同步
|
|
131
|
+
* `throw new Error('Signals not supported on windows.')`,而且它内部 `_deferNoArgs`
|
|
132
|
+
* 会把回调排进队列、稍后从 socket 回调里执行——调用方的 try/catch 拦不住,直接变成
|
|
133
|
+
* **宿主进程崩溃**。CI 的 windows-latest 上实测:spawn → kill 跑完就崩在
|
|
134
|
+
* `windowsTerminal.js:161`。不带 signal 时 node-pty 走 `_close()` + `agent.kill()`,
|
|
135
|
+
* 正是 Windows 上正确的终止语义。
|
|
136
|
+
*
|
|
137
|
+
* 导出仅供单测(test/host-frames.test.ts):平台参数注入,两个分支都能在 macOS/Linux 断言。
|
|
138
|
+
*/
|
|
139
|
+
export declare function killLocalShellTerminal(terminal: unknown, platform?: NodeJS.Platform): void;
|
|
140
|
+
/** 一次「会话 → 帧」采集器的句柄(本地 = 定时器,SSH = 远端长驻 exec channel)。 */
|
|
141
|
+
interface StatsCollector {
|
|
142
|
+
stop(): void;
|
|
143
|
+
}
|
|
144
|
+
interface TtySession {
|
|
145
|
+
id: string;
|
|
146
|
+
handle: TermHandle;
|
|
147
|
+
/**
|
|
148
|
+
* 绑定的 WS 连接集合(0.19.0 起支持跨连接共享):持久(tmux)会话可被多个
|
|
149
|
+
* 窗口同时绑定——同 tmuxName 的 spawn 不再新建 PTY 而是重绑定到现有会话
|
|
150
|
+
* (单 PTY 多客户端扇出,名额不翻倍)。
|
|
151
|
+
*
|
|
152
|
+
* 键 = `<连接 id>:<该连接侧标签 sid>`,值 = { ws, sid }(帧寻址用连接侧
|
|
153
|
+
* sid):只用 sid 做键时,「复制标签页」复制出的同 sid 第二连接会覆盖第一
|
|
154
|
+
* 连接的绑定(前者收不到输出还自以为在线),且前者关闭时误删后者的绑定。
|
|
155
|
+
* map 为空 = 孤儿状态。
|
|
156
|
+
*/
|
|
157
|
+
clients: Map<string, {
|
|
158
|
+
ws: WebSocket;
|
|
159
|
+
sid: string;
|
|
160
|
+
}>;
|
|
161
|
+
closed: boolean;
|
|
162
|
+
paused: boolean;
|
|
163
|
+
/** exit 帧只发一次(kill 主动关闭与 shell 自然退出共用同一回调)。 */
|
|
164
|
+
exitSent?: boolean;
|
|
165
|
+
/** agent 工具展示用的元数据。 */
|
|
166
|
+
cwd: string;
|
|
167
|
+
kind: 'local' | 'ssh';
|
|
168
|
+
/** SSH 会话的展示目标(user@host[:port]);本地会话为空串。 */
|
|
169
|
+
target: string;
|
|
170
|
+
startedAt: number;
|
|
171
|
+
lastOutputAt: number;
|
|
172
|
+
/** 最近一次 PTY 输入(input 帧 / tty_send)的时间戳:tty_capture{last} 的在途判据之一。 */
|
|
173
|
+
lastInputAt: number;
|
|
174
|
+
/** 输出环形缓冲(尾部 256KB,供 tty_capture 与断线重连回放)。 */
|
|
175
|
+
buffer: string;
|
|
176
|
+
/** utf8 分帧兜底:跨 chunk 的多字节序列由 StringDecoder 缓存补齐。 */
|
|
177
|
+
decoder: StringDecoder;
|
|
178
|
+
/** 虚拟屏(xterm-headless):tty_screen 的数据源;创建失败为 null。 */
|
|
179
|
+
screen: HeadlessTerminal | null;
|
|
180
|
+
/** 转入孤儿状态的时间戳;null 表示已连接(客户端在线)。 */
|
|
181
|
+
orphanedAt: number | null;
|
|
182
|
+
/** shell 集成状态(OSC 133/7 解析;本地与 SSH 会话都喂)。 */
|
|
183
|
+
shellState: ShellIntegrationState;
|
|
184
|
+
/** data 帧合并暂存区(flush 前不发)。 */
|
|
185
|
+
pendingOutput: string;
|
|
186
|
+
/** 合并冲刷定时器;null 表示无待冲刷窗口。 */
|
|
187
|
+
flushTimer: NodeJS.Timeout | null;
|
|
188
|
+
/** tmux 持久会话名(本地与 SSH 同语义);null = 非持久会话。 */
|
|
189
|
+
tmuxName: string | null;
|
|
190
|
+
/**
|
|
191
|
+
* 服务器状态条(0.17.0):已订阅该会话 stats 的客户端 sid 集合(tab 可见性
|
|
192
|
+
* 驱动)。空集合 = 该会话不需要采集,采集器必须停(防定时器/远程 channel 泄漏)。
|
|
193
|
+
*/
|
|
194
|
+
statsSubs: Set<string>;
|
|
195
|
+
/** 采集器句柄;null = 未启动(懒启动:首个 statsOn 才起)。 */
|
|
196
|
+
stats: StatsCollector | null;
|
|
197
|
+
/** 采集已永久失败(远端无 /proc、exec 被拒、连接断开):不再重启,前端隐藏状态条。 */
|
|
198
|
+
statsFailed: boolean;
|
|
199
|
+
}
|
|
200
|
+
interface ReqLike {
|
|
201
|
+
method?: string;
|
|
202
|
+
headers: Record<string, string | string[] | undefined>;
|
|
203
|
+
socket: {
|
|
204
|
+
remoteAddress?: string;
|
|
205
|
+
};
|
|
206
|
+
url?: string;
|
|
207
|
+
}
|
|
208
|
+
interface SocketLike {
|
|
209
|
+
destroy(): void;
|
|
210
|
+
}
|
|
211
|
+
/** 可热更新的运行时配置(settings/updated 动态应用)。 */
|
|
212
|
+
declare class LiveConfig {
|
|
213
|
+
shell: string;
|
|
214
|
+
term: string;
|
|
215
|
+
colorTerm: string;
|
|
216
|
+
cwd: string;
|
|
217
|
+
/** 异常断开后会话保活毫秒数(0 = 立即结束)。 */
|
|
218
|
+
reconnectGraceMs: number;
|
|
219
|
+
sshHosts: SshHostEntry[];
|
|
220
|
+
hostKeys: HostKeyRecord[];
|
|
221
|
+
/** 是否注入 OSC 133/7 shell 集成。 */
|
|
222
|
+
shellIntegration: boolean;
|
|
223
|
+
/** 端口转发隧道规格。 */
|
|
224
|
+
tunnels: TunnelSpec[];
|
|
225
|
+
/** 会话持久化模式(off / tmux)。 */
|
|
226
|
+
persistence: 'off' | 'tmux';
|
|
227
|
+
/** 页面断开且保活期结束时是否结束 tmux 持久会话(默认 false = 留存)。 */
|
|
228
|
+
endOnPageClose: boolean;
|
|
229
|
+
/** 服务器状态条:是否采集并推送会话资源指标(默认 true)。 */
|
|
230
|
+
statsEnabled: boolean;
|
|
231
|
+
/** SSH 持久会话名(远程 tmux 托管;本机 socket 清单看不到,随 settings 留存)。 */
|
|
232
|
+
persistSessions: string[];
|
|
233
|
+
/** SFTP 传输限制(客户端浏览器侧执行)。 */
|
|
234
|
+
sftpLimits: Required<SftpLimits>;
|
|
235
|
+
constructor(init: {
|
|
236
|
+
shell: string;
|
|
237
|
+
term: string;
|
|
238
|
+
colorTerm: string;
|
|
239
|
+
cwd: string;
|
|
240
|
+
reconnectGraceSec: number;
|
|
241
|
+
sshHosts?: SshHostEntry[];
|
|
242
|
+
hostKeys?: HostKeyRecord[];
|
|
243
|
+
shellIntegration: boolean;
|
|
244
|
+
tunnels?: TunnelSpec[];
|
|
245
|
+
persistence?: 'off' | 'tmux';
|
|
246
|
+
endOnPageClose?: boolean;
|
|
247
|
+
statsEnabled?: boolean;
|
|
248
|
+
sftpLimits?: Partial<SftpLimits>;
|
|
249
|
+
persistSessions?: string[];
|
|
250
|
+
});
|
|
251
|
+
/** 合并部分更新;空字符串/undefined 保持原值;sshHosts/hostKeys/tunnels 传数组即整体替换。 */
|
|
252
|
+
apply(partial: Partial<{
|
|
253
|
+
shell: string;
|
|
254
|
+
term: string;
|
|
255
|
+
colorTerm: string;
|
|
256
|
+
cwd: string;
|
|
257
|
+
reconnectGraceSec: number;
|
|
258
|
+
sshHosts: SshHostEntry[];
|
|
259
|
+
hostKeys: HostKeyRecord[];
|
|
260
|
+
shellIntegration: boolean;
|
|
261
|
+
tunnels: TunnelSpec[];
|
|
262
|
+
persistence: 'off' | 'tmux';
|
|
263
|
+
endOnPageClose: boolean;
|
|
264
|
+
statsEnabled: boolean;
|
|
265
|
+
sftpLimits?: Partial<SftpLimits>;
|
|
266
|
+
persistSessions: string[];
|
|
267
|
+
}>): void;
|
|
268
|
+
findSshHost(name: string): SshHostEntry | undefined;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* shell 集成状态:OSC 133/7 解析产物(每会话一份)。
|
|
272
|
+
*/
|
|
273
|
+
interface ShellIntegrationState {
|
|
274
|
+
/** 跨 chunk 未闭合 OSC 序列的残包缓冲(≤512KB,超限丢弃;上限容纳 T 快照——200 行 tmux capture-pane 的 base64 可到数百 KB)。 */
|
|
275
|
+
carry: string;
|
|
276
|
+
/** B..D 之间:命令输出捕获中。 */
|
|
277
|
+
inCommand: boolean;
|
|
278
|
+
cmdBuffer: string;
|
|
279
|
+
/** T 标记带来的 pane 快照(tmux 持久标签;D 时优先于 cmdBuffer)。 */
|
|
280
|
+
pendingT: string | null;
|
|
281
|
+
lastCommand: {
|
|
282
|
+
output: string;
|
|
283
|
+
exitCode: number | null;
|
|
284
|
+
endedAt: number;
|
|
285
|
+
} | null;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 把一块输出喂进 shell 集成解析(cwd 跟随 + 命令边界捕获)。
|
|
289
|
+
* 残包处理:尾部若有未闭合的 OSC 序列(lastIndexOf('\x1b]') 起无终结符),
|
|
290
|
+
* 扣回 carry 等下一块拼齐;扣留部分不进命令捕获,避免半截序列混入。
|
|
291
|
+
* 命令捕获按「标记之间的文本段」累积——B、输出、D 常在同一 chunk 到达,
|
|
292
|
+
* 先处理段再翻转状态,才能把 B..D 之间的输出完整收进 lastCommand。
|
|
293
|
+
*/
|
|
294
|
+
/** 导出仅供单测(test/shell-capture.test.ts):B/D 配对与未配对 D 的忽略语义。 */
|
|
295
|
+
export declare function feedShellIntegration(session: TtySession, text: string): void;
|
|
296
|
+
/**
|
|
297
|
+
* TOFU 主机指纹存储:get/record 面向 spawnSsh 的 hostVerifier;
|
|
298
|
+
* record 时经 persist 回调写入 settings(宿主重启后钉扎仍在)。
|
|
299
|
+
*/
|
|
300
|
+
declare class HostKeyStore {
|
|
301
|
+
private readonly live;
|
|
302
|
+
private readonly persist;
|
|
303
|
+
constructor(live: LiveConfig, persist: (records: HostKeyRecord[]) => void);
|
|
304
|
+
private key;
|
|
305
|
+
get(host: string, port: number): string[] | undefined;
|
|
306
|
+
/** 记录指纹:同 host:port 已有记录则并入集合(一机多把钥匙),否则新建。 */
|
|
307
|
+
record(host: string, port: number, fingerprint: string): void;
|
|
308
|
+
}
|
|
309
|
+
/** 导出仅供单测(test/host-frames.test.ts):上限 / 孤儿回收 / grace 热改的行为护栏。 */
|
|
310
|
+
export declare class SessionManager {
|
|
311
|
+
private readonly sessions;
|
|
312
|
+
private limit;
|
|
313
|
+
/** 回收器销毁孤儿时是否连 tmux 持久会话一起结束(endOnPageClose 策略)。 */
|
|
314
|
+
private endTmuxOnReap;
|
|
315
|
+
constructor(maxSessions: number, endTmuxOnReap?: () => boolean);
|
|
316
|
+
get limitValue(): number;
|
|
317
|
+
/** 配置热生效时调整上限(1~16)。 */
|
|
318
|
+
setLimit(maxSessions: number): void;
|
|
319
|
+
get count(): number;
|
|
320
|
+
canSpawn(): boolean;
|
|
321
|
+
add(session: TtySession): void;
|
|
322
|
+
remove(id: string): void;
|
|
323
|
+
get(id: string): TtySession | undefined;
|
|
324
|
+
/** 会话的只读快照(SSH 会话无本地 pid,该字段省略;tmux 持久会话带 persist)。 */
|
|
325
|
+
private snapshotOf;
|
|
326
|
+
/** agent 工具用的只读快照。 */
|
|
327
|
+
list(): Array<{
|
|
328
|
+
sid: string;
|
|
329
|
+
pid?: number;
|
|
330
|
+
cwd: string;
|
|
331
|
+
kind: 'local' | 'ssh';
|
|
332
|
+
target: string;
|
|
333
|
+
startedAt: number;
|
|
334
|
+
lastOutputAt: number;
|
|
335
|
+
persist?: true;
|
|
336
|
+
}>;
|
|
337
|
+
/** sessions 帧用:额外带 attachable(孤儿且未关闭的会话可被新连接 attach)。 */
|
|
338
|
+
listForAttach(): Array<{
|
|
339
|
+
sid: string;
|
|
340
|
+
pid?: number;
|
|
341
|
+
cwd: string;
|
|
342
|
+
kind: 'local' | 'ssh';
|
|
343
|
+
target: string;
|
|
344
|
+
startedAt: number;
|
|
345
|
+
lastOutputAt: number;
|
|
346
|
+
persist?: true;
|
|
347
|
+
attachable: boolean;
|
|
348
|
+
}>;
|
|
349
|
+
/** 遍历全部会话(状态条采集器的批量收尾等按会话维度的操作)。 */
|
|
350
|
+
forEach(fn: (session: TtySession) => void): void;
|
|
351
|
+
/** 按 tmux 持久会话名查找存活会话(跨窗口共享用);不存在/已关闭返回 undefined。 */
|
|
352
|
+
findByTmuxName(tmuxName: string): TtySession | undefined;
|
|
353
|
+
/** 同步退役:移出全局表 + 释放虚拟屏(幂等,不杀进程)。 */
|
|
354
|
+
retire(session: TtySession): void;
|
|
355
|
+
/** 释放并销毁会话:退役 + 树级终止(等待 terminate 完成,最慢 ~20s)。
|
|
356
|
+
* endOnPageClose 策略下,回收器销毁孤儿时连 tmux 持久会话一起结束。 */
|
|
357
|
+
destroy(session: TtySession): Promise<void>;
|
|
358
|
+
/**
|
|
359
|
+
* 回收孤儿会话(回收器定时调用):超过保活期的回收。graceMs<=0 时立即回收
|
|
360
|
+
* 全部孤儿——孤儿只在「断开瞬间 grace>0」时产生,热改 grace 为 0 不能只管
|
|
361
|
+
* 以后:已存在的孤儿会永久占 PTY 与名额,满额后新标签一直报「会话数已达上限」。
|
|
362
|
+
*/
|
|
363
|
+
reapOrphans(graceMs: number): Promise<void>;
|
|
364
|
+
disposeAll(): Promise<void>;
|
|
365
|
+
}
|
|
366
|
+
/** 导出仅供单测(test/host-frames.test.ts):帧校验 / 绑定 / 孤儿语义的行为护栏。 */
|
|
367
|
+
export declare class TtyServer {
|
|
368
|
+
private readonly ctx;
|
|
369
|
+
private readonly sessions;
|
|
370
|
+
private readonly options;
|
|
371
|
+
private readonly hostKeyStore;
|
|
372
|
+
/** SSH 持久会话名留存回调(apply 闭包实现,settings 落盘)。 */
|
|
373
|
+
private readonly trackPersist;
|
|
374
|
+
private readonly wss;
|
|
375
|
+
/** 在途的持久会话创建(tmuxName → 创建 promise):dsh 重启后多页面并发恢复时收敛竞态。 */
|
|
376
|
+
private readonly pendingTmux;
|
|
377
|
+
/** WS 闸门(插件禁用时关闭):拒绝新升级 + 断开存量连接。 */
|
|
378
|
+
private wsGateOpen;
|
|
379
|
+
/** 服务器状态条总开关(配置热生效;关闭时停掉全部采集,重开按订阅恢复)。 */
|
|
380
|
+
private statsOn;
|
|
381
|
+
constructor(ctx: Context, sessions: SessionManager, options: LiveConfig, hostKeyStore: HostKeyStore,
|
|
382
|
+
/** SSH 持久会话名留存回调(apply 闭包实现,settings 落盘)。 */
|
|
383
|
+
trackPersist: (tmuxName: string, present: boolean) => void);
|
|
384
|
+
/**
|
|
385
|
+
* 按启用状态对齐 WS 闸门(幂等)。关闭时对存量连接发正常关闭帧:客户端走
|
|
386
|
+
* 既有重连循环,禁用期间升级被拒,重新启用后自动重连并 attach 孤儿会话。
|
|
387
|
+
* PTY 进程不受影响(转孤儿保活),不因禁用杀用户进程。
|
|
388
|
+
*/
|
|
389
|
+
setWsGate(open: boolean): void;
|
|
390
|
+
/**
|
|
391
|
+
* 配置热生效:关闭时停掉全部采集(本地定时器 + 远端 exec channel);重新打开
|
|
392
|
+
* 时对**仍有订阅**的会话懒启动。订阅集合刻意不清——客户端只在标签可见性变化
|
|
393
|
+
* 时发 statsOn/statsOff,开关来回切不该要求它重发。
|
|
394
|
+
*/
|
|
395
|
+
setStatsEnabled(enabled: boolean): void;
|
|
396
|
+
/** 订阅/退订(tab 可见性驱动):退到 0 即停表,任何路径都不会让采集器空转。键 = 绑定键(connId:sid)。 */
|
|
397
|
+
private setStatsSub;
|
|
398
|
+
/** 清掉指向已解绑客户端(WS 关闭 / 标签换 sid 重绑)的订阅,防采集器永不收尾。 */
|
|
399
|
+
private pruneStatsSubs;
|
|
400
|
+
/**
|
|
401
|
+
* 懒启动采集(首个 statsOn 才起)。两条路径产出同形状的帧:
|
|
402
|
+
* - 本地:宿主进程就是那台机器,1s 定时器 + 进程级共享采样器(多个本地标签
|
|
403
|
+
* 共享一次 df/netstat);
|
|
404
|
+
* - SSH:远端 sh + awk 常驻循环,每秒一行 JSON 走**非 PTY** exec channel;
|
|
405
|
+
* 速率类由远端算好,宿主只解析 + 清洗。
|
|
406
|
+
* 任何失败都静默停表并置 statsFailed(粘性,避免每秒重启):前端靠「无数据」
|
|
407
|
+
* 隐藏状态条,PTY 数据路径与终端体验完全不受影响。
|
|
408
|
+
*/
|
|
409
|
+
private startStats;
|
|
410
|
+
/** 停表(幂等):订阅清零 / 会话结束 / 插件禁用 / 配置关闭都走它。 */
|
|
411
|
+
private stopStats;
|
|
412
|
+
private stopAllStats;
|
|
413
|
+
/** 帧只发给订阅了该会话的客户端(绑定键寻址,回帧带各连接自己的 sid,跨窗口共享也成立)。 */
|
|
414
|
+
private sendStats;
|
|
415
|
+
/** registerUpgrade 的 handler(loopback 围栏 + ws 握手)。 */
|
|
416
|
+
handleUpgrade(req: ReqLike, socket: SocketLike, head: Buffer): void;
|
|
417
|
+
private onConnection;
|
|
418
|
+
/**
|
|
419
|
+
* 解析帧里的 sid。返回:
|
|
420
|
+
* { sid } 目标会话;
|
|
421
|
+
* { unknown } 显式 sid 但本连接无此会话(客户端竞态,如 resize 先于
|
|
422
|
+
* spawn 就绪到达;调用方应静默忽略,而不是报错);
|
|
423
|
+
* undefined 已发送错误帧(非法 sid / sid 缺省但无法唯一路由)。
|
|
424
|
+
*/
|
|
425
|
+
private resolveSid;
|
|
426
|
+
/** 把一个客户端连接重绑定到既有会话(跨窗口共享 / 并发恢复收敛共用)。 */
|
|
427
|
+
private rebindClient;
|
|
428
|
+
/** 等待同 tmuxName 的在途创建完成;返回可重绑定的会话(null = 无在途/已失败)。 */
|
|
429
|
+
private waitPendingTmux;
|
|
430
|
+
/**
|
|
431
|
+
* 立即终止会话:同步退役 + 顶层 shell 直接 SIGKILL,让 done/exit 帧立刻可发;
|
|
432
|
+
* 树级子进程清理(SIGTERM→grace→SIGKILL,交互式 zsh 忽略 SIGTERM 时最慢
|
|
433
|
+
* 可拖 ~20s)由 terminate 在后台继续收尾,不阻塞 kill 帧处理。
|
|
434
|
+
* tmux 背书会话先向 tmux server 发 kill-session(杀客户端只会 detach,
|
|
435
|
+
* 会话会留在 tmux server 上);2.5s 兜底 forceKill 防收尾悬挂。
|
|
436
|
+
*/
|
|
437
|
+
private killSessionNow;
|
|
438
|
+
/** 每会话一块虚拟屏(xterm-headless):tty_screen 的数据源;失败降级为 null。 */
|
|
439
|
+
private createScreen;
|
|
440
|
+
private handleMessage;
|
|
441
|
+
/**
|
|
442
|
+
* 服务器状态条订阅(0.17.0):按「标签可见性」驱动——只有可见标签才发
|
|
443
|
+
* statsOn。未知 sid(客户端竞态)静默忽略,不回错误帧。订阅键与客户端
|
|
444
|
+
* 绑定键同构(connId:sid),跨连接共享同一 sid 时互不踩。
|
|
445
|
+
*/
|
|
446
|
+
private handleStatsFrame;
|
|
447
|
+
/** 会话退出事实 → exit 帧(恰好一次;本地 PTY 与 SSH 共用)。 */
|
|
448
|
+
private watchDone;
|
|
449
|
+
/** 输出下行 + 基于 ws.bufferedAmount 的背压(暂停/恢复 PassThrough)。 */
|
|
450
|
+
private attachOutput;
|
|
451
|
+
/** 立即冲刷待发的合并输出(exit/kill 前调用,保证 exit 帧永远在最后一帧 data 之后)。 */
|
|
452
|
+
private flushPendingOutput;
|
|
453
|
+
close(): void;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* 凭据存储里**已知的引用名**(~/.dsh/.credentials.yaml 的 `refs:` 块键),只读给
|
|
457
|
+
* SSH 对话框的引用选择器当候选。
|
|
458
|
+
*
|
|
459
|
+
* 为什么必须读文件:官方把「引用半边」设计成**不可枚举**——
|
|
460
|
+
* `CredentialProvider.listRecords` 的注释原话是 "Unlike the reference half, which has no
|
|
461
|
+
* enumeration because configuration surfaces learn which references exist from settings
|
|
462
|
+
* schemas",而浏览器侧 `ctx.remote.credentials`(dsh-api-settings-controller)只开
|
|
463
|
+
* `describe` / `set` / `unset`,连 `listRecords` 都没开。所以要让用户在下拉里看见
|
|
464
|
+
* "这本存储里已经有什么名字",宿主侧读文件是唯一出路;**只要键名、不取值**(值只在本函数
|
|
465
|
+
* 的局部 `lines` 里路过,不进任何返回值,也不写日志)。
|
|
466
|
+
*
|
|
467
|
+
* 解析刻意最小(与上面 readManagedEnvKeys 同款,不为它引 YAML 依赖):只认 `refs:` 顶层
|
|
468
|
+
* 区块内「恰好两个空格 + POSIX 标识符 + 冒号」的行。本地 provider 写入时用 `yaml` 严格
|
|
469
|
+
* 校验过(version: 1 / 值必须非空字符串 / 键必须是标识符),所以这个格式是稳的;真被手改
|
|
470
|
+
* 坏了也只是候选少几个 —— 引用最终仍由连接时的凭据层校验存在性。
|
|
471
|
+
*
|
|
472
|
+
* 路径解析与本地 provider 的默认一致($DSH_HOME 优先,空串视为未设,再退 ~/.dsh)。边界:
|
|
473
|
+
* 若有人给 provider 配了自定义 `path` / `dshHome`,这里看不到那些引用(字段仍可手输名字)。
|
|
474
|
+
*/
|
|
475
|
+
/**
|
|
476
|
+
* `/api/dsh-tty/credential-refs` 的路由逻辑(导出仅供单测
|
|
477
|
+
* test/credential-refs.test.ts):loopback 闸门 + 方法闸门 + 只回引用名的载荷。
|
|
478
|
+
* 值在任何分支都不进响应——SSH 对话框的选择器只需要「我存过哪些名字」。
|
|
479
|
+
*/
|
|
480
|
+
export declare function handleCredentialRefsRoute(req: ReqLike, res: ResLike): Promise<void>;
|
|
481
|
+
/** 导出仅供单测(test/credential-refs.test.ts):只验键名解析,不取值。 */
|
|
482
|
+
export declare function readCredentialRefNames(): string[];
|
|
483
|
+
interface ResLike {
|
|
484
|
+
writeHead(status: number, headers?: Record<string, string>): void;
|
|
485
|
+
/** 二进制响应(SFTP 下载)也走 end;Node 的 ServerResponse 原生接受 Uint8Array。 */
|
|
486
|
+
end(body?: string | Uint8Array): void;
|
|
487
|
+
}
|
|
122
488
|
export declare const name: string, inject: string[] | undefined, apply: (ctx: Context, config?: Config | undefined) => void;
|