dsh-remote-local-plugin 0.0.0-stage → 0.8.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/LICENSE +21 -0
- package/README.md +97 -2
- package/assets/dsh-remote-agent-0.2.1.tgz +0 -0
- package/cordis.patch.yml +50 -0
- package/lib/client.js +1571 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +4637 -0
- package/lib/types/config.d.ts +94 -0
- package/lib/types/credentials/proxy.d.ts +40 -0
- package/lib/types/errors.d.ts +23 -0
- package/lib/types/host-api/contract.d.ts +217 -0
- package/lib/types/host-api/handler.d.ts +15 -0
- package/lib/types/host-api/terminal.d.ts +15 -0
- package/lib/types/hosts/id.d.ts +16 -0
- package/lib/types/hosts/store.d.ts +34 -0
- package/lib/types/hosts/types.d.ts +85 -0
- package/lib/types/index.d.ts +19 -0
- package/lib/types/mirror/service.d.ts +87 -0
- package/lib/types/mirror/test-visibility.d.ts +23 -0
- package/lib/types/remote/directory.d.ts +19 -0
- package/lib/types/remote/files.d.ts +14 -0
- package/lib/types/remote/local-directory.d.ts +11 -0
- package/lib/types/remote/provisioner.d.ts +32 -0
- package/lib/types/remote/terminal.d.ts +55 -0
- package/lib/types/service.d.ts +92 -0
- package/lib/types/ssh/probe.d.ts +33 -0
- package/lib/types/ssh/target.d.ts +24 -0
- package/lib/types/ssh/tunnel.d.ts +50 -0
- package/lib/types/tools.d.ts +19 -0
- package/node_modules/dsh-remote-terminal-controller/README.md +9 -0
- package/node_modules/dsh-remote-terminal-controller/cordis.patch.yml +8 -0
- package/node_modules/dsh-remote-terminal-controller/lib/client.js +1367 -0
- package/node_modules/dsh-remote-terminal-controller/lib/index.js +3 -0
- package/node_modules/dsh-remote-terminal-controller/package.json +59 -0
- package/package.json +146 -4
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only remote directory browser used by the workspace creation UI.
|
|
3
|
+
* It reuses the host's SSH transport and runs a tiny Python lister remotely.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-remote-local/remote/directory
|
|
6
|
+
*/
|
|
7
|
+
import type { RemoteHostsService } from '../service.ts';
|
|
8
|
+
export interface RemoteDirectoryEntry {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly path: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RemoteDirectoryListing {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly parent: string;
|
|
15
|
+
readonly entries: readonly RemoteDirectoryEntry[];
|
|
16
|
+
readonly truncated: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** List child directories of one remote path. */
|
|
19
|
+
export declare function listRemoteDirectories(hosts: RemoteHostsService, hostId: string, path?: string): Promise<RemoteDirectoryListing>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-backed `workspaceFiles` for native workspace trees and file previews.
|
|
3
|
+
*
|
|
4
|
+
* Mirrored workspaces have a local anchor path. The stock workspace-files
|
|
5
|
+
* service would read that empty anchor; this adapter maps the anchor prefix to
|
|
6
|
+
* the remote workspace root and runs a small read-only helper over SSH.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/remote/files
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { RemoteHostsService } from '../service.ts';
|
|
12
|
+
import type { RemoteMirrorService } from '../mirror/service.ts';
|
|
13
|
+
/** Install the adapter over the host's workspace-files service. */
|
|
14
|
+
export declare function installRemoteWorkspaceFiles(ctx: Context, mirrors: RemoteMirrorService, hosts: RemoteHostsService): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface LocalDirectoryListing {
|
|
2
|
+
readonly path: string;
|
|
3
|
+
readonly parent: string;
|
|
4
|
+
readonly entries: readonly {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly path: string;
|
|
7
|
+
}[];
|
|
8
|
+
readonly truncated: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** List child directories of one local path. */
|
|
11
|
+
export declare function listLocalDirectories(path?: string): Promise<LocalDirectoryListing>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-owned remote provisioning.
|
|
3
|
+
*
|
|
4
|
+
* On first connect the plugin uploads its bundled agent tarball, runs the
|
|
5
|
+
* installer bootstrap script over SSH, then waits for the remote state file.
|
|
6
|
+
* No separate installer command is required.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/remote/provisioner
|
|
9
|
+
*/
|
|
10
|
+
import type { ResolvedConfig } from '../config.ts';
|
|
11
|
+
import type { ParsedSshTarget } from '../ssh/target.ts';
|
|
12
|
+
/** Progress callback shown in the host card. */
|
|
13
|
+
export type ProvisionProgress = (message: string) => void;
|
|
14
|
+
/** Provisioner bound to one plugin config. */
|
|
15
|
+
export declare class RemoteProvisioner {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(config: ResolvedConfig);
|
|
18
|
+
/**
|
|
19
|
+
* Install/start the remote agent when discovery finds no live agent.
|
|
20
|
+
*
|
|
21
|
+
* @param target - validated SSH target.
|
|
22
|
+
* @param options - optional local proxy endpoint and progress callback.
|
|
23
|
+
*/
|
|
24
|
+
ensureInstalled(target: ParsedSshTarget, options?: {
|
|
25
|
+
readonly modelBaseUrl?: string;
|
|
26
|
+
readonly modelApiKey?: string;
|
|
27
|
+
readonly onProgress?: ProvisionProgress;
|
|
28
|
+
readonly signal?: AbortSignal;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** Locate the packed agent shipped in this plugin's assets directory. */
|
|
32
|
+
export declare function findAgentTarball(): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote PTY terminals for mirrored Sessions.
|
|
3
|
+
*
|
|
4
|
+
* The stock terminal controller spawns processes in the local execution
|
|
5
|
+
* environment. For a mirror, this manager spawns an SSH PTY in the remote
|
|
6
|
+
* workspace instead and exposes the browser protocol the normal terminal UI
|
|
7
|
+
* already consumes.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-remote-local/remote/terminal
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { TerminalFrame, TerminalInput, TerminalShell, WebTerminalInfo } from '@deepseek-ai/dsh-api-terminal-controller';
|
|
13
|
+
import type { RemoteHostsService } from '../service.ts';
|
|
14
|
+
import type { RemoteMirrorService } from '../mirror/service.ts';
|
|
15
|
+
/** One host-side terminal manager. */
|
|
16
|
+
export declare class RemoteTerminals {
|
|
17
|
+
#private;
|
|
18
|
+
constructor(ctx: Context, hosts: RemoteHostsService, mirrors: RemoteMirrorService);
|
|
19
|
+
/** Whether this Session owns a mirrored workspace. */
|
|
20
|
+
isMirrored(sessionId: string): boolean;
|
|
21
|
+
/** Return the remote cwd/limits for one Session. */
|
|
22
|
+
environment(sessionId: string): {
|
|
23
|
+
cwd: string;
|
|
24
|
+
maxInputBytes: number;
|
|
25
|
+
maxBufferedBytes: number;
|
|
26
|
+
maxCols: number;
|
|
27
|
+
maxRows: number;
|
|
28
|
+
scrollback: number;
|
|
29
|
+
};
|
|
30
|
+
/** One remote shell candidate. */
|
|
31
|
+
shells(): TerminalShell[];
|
|
32
|
+
/** Terminals retained for one Session. */
|
|
33
|
+
list(sessionId: string): WebTerminalInfo[];
|
|
34
|
+
/** Allocate an SSH PTY in the mirrored workspace. */
|
|
35
|
+
create(sessionId: string, request: {
|
|
36
|
+
id?: string;
|
|
37
|
+
shellPath?: string;
|
|
38
|
+
cols?: number;
|
|
39
|
+
rows?: number;
|
|
40
|
+
}): WebTerminalInfo;
|
|
41
|
+
/** Feed input/resize into one remote PTY. */
|
|
42
|
+
input(id: string, input: TerminalInput): void;
|
|
43
|
+
/** Rename a retained terminal. */
|
|
44
|
+
rename(id: string, title: string): void;
|
|
45
|
+
/** Close one terminal. */
|
|
46
|
+
close(id: string): void;
|
|
47
|
+
/** Stream frames for one attachment. */
|
|
48
|
+
attach(id: string, signal: AbortSignal): AsyncGenerator<TerminalFrame>;
|
|
49
|
+
/** Retain acknowledgement used by reconnecting windows. */
|
|
50
|
+
retain(): AsyncGenerator<{
|
|
51
|
+
type: 'retained';
|
|
52
|
+
}>;
|
|
53
|
+
/** Dispose every process. */
|
|
54
|
+
dispose(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host registry + tunnel lifecycle service.
|
|
3
|
+
*
|
|
4
|
+
* One instance owns every configured remote host, at most one SSH tunnel per
|
|
5
|
+
* host, the remote agent client, health checks, and bounded reconnection with
|
|
6
|
+
* exponential backoff. The browser and agent tools only ever see
|
|
7
|
+
* {@link HostView}; tokens stay in this process.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-remote-local/service
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import { type AcceptedResult, type ApprovalOutcome, type RemoteApprovalFrame, type CreateSessionRequest, type CreateSessionResult, type CreateWorkspaceRequest, type ListSessionsResult, type PromptDeliveryMode, type RemoteWorkspace, type SessionSnapshot } from '@dsh-remote/protocol';
|
|
13
|
+
import type { ResolvedConfig } from './config.ts';
|
|
14
|
+
import { type AddHostInput, type HostView } from './hosts/types.ts';
|
|
15
|
+
/** Snapshot returned to the Settings page. */
|
|
16
|
+
export interface HostsState {
|
|
17
|
+
readonly protocolVersion: number;
|
|
18
|
+
readonly hosts: readonly HostView[];
|
|
19
|
+
}
|
|
20
|
+
/** Constructor inputs beyond config. */
|
|
21
|
+
export interface RemoteHostsServiceOptions {
|
|
22
|
+
/** Resolve the local provider key at connect time; never cached in the view. */
|
|
23
|
+
readonly resolveModelKey?: () => Promise<string | undefined>;
|
|
24
|
+
}
|
|
25
|
+
/** One host registry service. */
|
|
26
|
+
export declare class RemoteHostsService {
|
|
27
|
+
#private;
|
|
28
|
+
readonly ctx: Context;
|
|
29
|
+
readonly config: ResolvedConfig;
|
|
30
|
+
constructor(ctx: Context, config: ResolvedConfig, options?: RemoteHostsServiceOptions);
|
|
31
|
+
/** Load the registry and auto-connect opted-in hosts. */
|
|
32
|
+
start(): Promise<void>;
|
|
33
|
+
/** Browser/tool snapshot. */
|
|
34
|
+
state(): HostsState;
|
|
35
|
+
/** All hosts in creation order. */
|
|
36
|
+
list(): HostView[];
|
|
37
|
+
/** One host view. */
|
|
38
|
+
view(id: string): HostView;
|
|
39
|
+
/** Parse a destination without touching SSH. */
|
|
40
|
+
validateTarget(raw: string): {
|
|
41
|
+
canonical: string;
|
|
42
|
+
host: string;
|
|
43
|
+
user: string | null;
|
|
44
|
+
port: number | null;
|
|
45
|
+
};
|
|
46
|
+
/** Register a host and (by default) start connecting in the background. */
|
|
47
|
+
add(input: AddHostInput): Promise<HostView>;
|
|
48
|
+
/** Remove a host and tear down its tunnel. */
|
|
49
|
+
remove(id: string): Promise<void>;
|
|
50
|
+
/** Connect one host, waiting for discovery, tunnel, and health. */
|
|
51
|
+
connect(id: string): Promise<HostView>;
|
|
52
|
+
/** Disconnect one host and suppress automatic reconnection. */
|
|
53
|
+
disconnect(id: string): Promise<HostView>;
|
|
54
|
+
/** Re-discover the remote agent; reconnect when its port or token changed. */
|
|
55
|
+
refresh(id: string): Promise<HostView>;
|
|
56
|
+
/** Force a fresh remote install/restart, then reconnect. */
|
|
57
|
+
provision(id: string): Promise<HostView>;
|
|
58
|
+
/** List remote workspaces. */
|
|
59
|
+
workspaces(id: string): Promise<readonly RemoteWorkspace[]>;
|
|
60
|
+
/** List remote sessions, optionally within one workspace. */
|
|
61
|
+
sessions(id: string, workspaceId?: string): Promise<ListSessionsResult>;
|
|
62
|
+
/** Read one session snapshot. */
|
|
63
|
+
snapshot(id: string, sessionId: string, limit?: number): Promise<SessionSnapshot>;
|
|
64
|
+
/** Register a remote workspace. */
|
|
65
|
+
createWorkspace(id: string, request: CreateWorkspaceRequest): Promise<RemoteWorkspace>;
|
|
66
|
+
/** Create a remote session. */
|
|
67
|
+
createSession(id: string, request: CreateSessionRequest): Promise<CreateSessionResult>;
|
|
68
|
+
/** Send a prompt to a remote session. */
|
|
69
|
+
promptSession(id: string, sessionId: string, text: string, mode?: PromptDeliveryMode, selection?: {
|
|
70
|
+
provider: string;
|
|
71
|
+
model: string;
|
|
72
|
+
reasoningEffort?: string;
|
|
73
|
+
}): Promise<AcceptedResult>;
|
|
74
|
+
/** Cancel a remote session's active turn. */
|
|
75
|
+
cancelSession(id: string, sessionId: string): Promise<AcceptedResult>;
|
|
76
|
+
/** SSH invocation facts for auxiliary remote file/terminal adapters. */
|
|
77
|
+
sshFor(id: string): {
|
|
78
|
+
binary: string;
|
|
79
|
+
target: string;
|
|
80
|
+
port: number | null;
|
|
81
|
+
};
|
|
82
|
+
/** Follow remote approval questions for one host. */
|
|
83
|
+
followApprovals(id: string, signal: AbortSignal): AsyncGenerator<RemoteApprovalFrame>;
|
|
84
|
+
/** Send one approval answer to the remote agent. */
|
|
85
|
+
answerApproval(id: string, approvalId: string, outcome: ApprovalOutcome): Promise<AcceptedResult>;
|
|
86
|
+
/** Resolve the remote Web UI behind this host's tunnel. */
|
|
87
|
+
webUrl(id: string): Promise<{
|
|
88
|
+
url: string;
|
|
89
|
+
}>;
|
|
90
|
+
/** Stop every tunnel and timer. */
|
|
91
|
+
dispose(): Promise<void>;
|
|
92
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH discovery of the remote agent's private state file.
|
|
3
|
+
*
|
|
4
|
+
* This is the only command the local plugin runs before a tunnel exists. It
|
|
5
|
+
* is fixed, bounded, and never interpolates user input into shell syntax.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-remote-local/ssh/probe
|
|
8
|
+
*/
|
|
9
|
+
import { type AgentDiscoveryState } from '@dsh-remote/protocol';
|
|
10
|
+
import type { ParsedSshTarget } from './target.ts';
|
|
11
|
+
/** Probe options. */
|
|
12
|
+
export interface AgentProbeOptions {
|
|
13
|
+
readonly sshBinary: string;
|
|
14
|
+
readonly timeoutMs: number;
|
|
15
|
+
readonly strictHostKeyChecking: 'yes' | 'no' | 'ask' | 'accept-new';
|
|
16
|
+
readonly remoteStateFile: string;
|
|
17
|
+
}
|
|
18
|
+
/** Successful discovery. */
|
|
19
|
+
export interface DiscoveryResult {
|
|
20
|
+
readonly state: AgentDiscoveryState;
|
|
21
|
+
readonly latencyMs: number;
|
|
22
|
+
}
|
|
23
|
+
/** Shell-quote one literal for the fixed remote command. */
|
|
24
|
+
export declare function shellQuote(value: string): string;
|
|
25
|
+
/** Build the fixed `cat` command for a remote state path. */
|
|
26
|
+
export declare function discoveryCommand(remoteStateFile: string): string;
|
|
27
|
+
/** SSH argv shared by discovery and tunnel startup. */
|
|
28
|
+
export declare function baseSshArgs(target: ParsedSshTarget, options: {
|
|
29
|
+
timeoutMs: number;
|
|
30
|
+
strictHostKeyChecking: 'yes' | 'no' | 'ask' | 'accept-new';
|
|
31
|
+
}): string[];
|
|
32
|
+
/** Discover one host's running agent. */
|
|
33
|
+
export declare function discoverAgent(target: ParsedSshTarget, options: AgentProbeOptions): Promise<DiscoveryResult>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict SSH destination parser.
|
|
3
|
+
*
|
|
4
|
+
* The plugin spawns `ssh` with an argv array, never a shell, so the parser's
|
|
5
|
+
* job is to reject strings that could be mistaken for options or that carry
|
|
6
|
+
* shell metacharacters before they reach the process boundary.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/ssh/target
|
|
9
|
+
*/
|
|
10
|
+
/** One validated SSH destination. */
|
|
11
|
+
export interface ParsedSshTarget {
|
|
12
|
+
readonly raw: string;
|
|
13
|
+
readonly user: string | null;
|
|
14
|
+
readonly host: string;
|
|
15
|
+
readonly port: number | null;
|
|
16
|
+
/** `user@host` for argv. */
|
|
17
|
+
readonly destination: string;
|
|
18
|
+
/** Canonical user/host/port spelling used for ids and duplicate checks. */
|
|
19
|
+
readonly canonical: string;
|
|
20
|
+
}
|
|
21
|
+
/** Parse and validate a destination such as `user@host`, `host:2222`, or `ssh://user@host:22`. */
|
|
22
|
+
export declare function parseSshTarget(raw: string): ParsedSshTarget;
|
|
23
|
+
/** Validate a display label. */
|
|
24
|
+
export declare function validateHostLabel(label: string): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One SSH local-forward tunnel to a remote agent.
|
|
3
|
+
*
|
|
4
|
+
* The tunnel binds `127.0.0.1` only and forwards to the remote agent's
|
|
5
|
+
* loopback port. It never exposes a public interface and never starts a
|
|
6
|
+
* shell on the remote side (`-N`).
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/ssh/tunnel
|
|
9
|
+
*/
|
|
10
|
+
import type { ParsedSshTarget } from './target.ts';
|
|
11
|
+
/** Tunnel options. */
|
|
12
|
+
export interface TunnelOptions {
|
|
13
|
+
readonly sshBinary: string;
|
|
14
|
+
readonly target: ParsedSshTarget;
|
|
15
|
+
readonly remotePort: number;
|
|
16
|
+
readonly connectTimeoutMs: number;
|
|
17
|
+
readonly strictHostKeyChecking: 'yes' | 'no' | 'ask' | 'accept-new';
|
|
18
|
+
/** Reverse forward used by the local model credential proxy. */
|
|
19
|
+
readonly reverseForward?: {
|
|
20
|
+
readonly remotePort: number;
|
|
21
|
+
readonly localPort: number;
|
|
22
|
+
};
|
|
23
|
+
/** Called once when an unexpected exit happens. */
|
|
24
|
+
readonly onExit?: (info: {
|
|
25
|
+
code: number | null;
|
|
26
|
+
signal: NodeJS.Signals | null;
|
|
27
|
+
stderr: string;
|
|
28
|
+
}) => void;
|
|
29
|
+
}
|
|
30
|
+
/** Build the tunnel argv (exported for tests). */
|
|
31
|
+
export declare function tunnelArgs(options: TunnelOptions, localPort: number): string[];
|
|
32
|
+
/** Ask the OS for one free loopback port. */
|
|
33
|
+
export declare function allocateLocalPort(): Promise<number>;
|
|
34
|
+
/** Wait until a loopback port accepts a TCP connection. */
|
|
35
|
+
export declare function waitForPort(port: number, timeoutMs: number): Promise<void>;
|
|
36
|
+
/** A started SSH tunnel. */
|
|
37
|
+
export declare class Tunnel {
|
|
38
|
+
#private;
|
|
39
|
+
readonly localPort: number;
|
|
40
|
+
readonly localUrl: string;
|
|
41
|
+
private constructor();
|
|
42
|
+
/** Start a tunnel, retrying port allocation if the first bind races. */
|
|
43
|
+
static start(options: TunnelOptions, attempts?: number): Promise<Tunnel>;
|
|
44
|
+
/** Child process id. */
|
|
45
|
+
get pid(): number | undefined;
|
|
46
|
+
/** Captured stderr tail. */
|
|
47
|
+
get stderr(): string;
|
|
48
|
+
/** Stop the tunnel; idempotent. */
|
|
49
|
+
stop(): Promise<void>;
|
|
50
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing tools for remote development.
|
|
3
|
+
*
|
|
4
|
+
* These are deliberately thin wrappers over the same service the Settings
|
|
5
|
+
* page uses. They let an agent add a host, inspect its status, and read remote
|
|
6
|
+
* workspaces/sessions without a UI detour; secrets are never returned.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/tools
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { RemoteHostsService } from './service.ts';
|
|
12
|
+
/**
|
|
13
|
+
* Require an explicit approval before model-facing host writes. These tools can
|
|
14
|
+
* start SSH connections and, in direct-key mode, copy the local model key into
|
|
15
|
+
* the remote service environment.
|
|
16
|
+
*/
|
|
17
|
+
export declare function installRemoteToolGuards(ctx: Context): void;
|
|
18
|
+
/** Register the remote-development tool family. */
|
|
19
|
+
export declare function registerRemoteTools(ctx: Context, service: RemoteHostsService): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# dsh-remote-terminal-controller
|
|
2
|
+
|
|
3
|
+
Profile-level fork of `@deepseek-ai/dsh-api-terminal-controller`.
|
|
4
|
+
|
|
5
|
+
- Host half re-exports the stock `TerminalController`.
|
|
6
|
+
- Client half constructs `ClientTerminals` with a Session-routing remote face:
|
|
7
|
+
- local Sessions keep the stock local PTY controller;
|
|
8
|
+
- mirrored remote Sessions use `/api/dsh-remote-terminal` (SSH PTY).
|
|
9
|
+
- The bundle patch replaces the stock `terminal-controller` row in the selected DSH profile.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Disable the stock terminal-controller row and insert this fork in its place.
|
|
2
|
+
# `name` in a non-insert patch only asserts an existing name, so replacement is
|
|
3
|
+
# expressed as disable + insert.
|
|
4
|
+
- id: terminal-controller
|
|
5
|
+
disabled: true
|
|
6
|
+
- insert:
|
|
7
|
+
- id: terminal-controller-remote
|
|
8
|
+
name: dsh-remote-terminal-controller
|