dsh-remote-local-plugin 0.0.0-stage → 0.8.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/LICENSE +21 -0
- package/README.md +85 -2
- package/assets/dsh-remote-agent-0.2.1.tgz +0 -0
- package/cordis.patch.yml +50 -0
- package/lib/client.js +1511 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +4565 -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/rpc/contract.d.ts +114 -0
- package/lib/types/rpc/handler.d.ts +12 -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/client.js.map +7 -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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local plugin configuration and path resolution.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-remote-local/config
|
|
5
|
+
*/
|
|
6
|
+
import z from '@deepseek-ai/schemastery';
|
|
7
|
+
/** Raw plugin config. */
|
|
8
|
+
export interface Config {
|
|
9
|
+
/** Empty means `$DSH_HOME/remote`. */
|
|
10
|
+
readonly stateDir?: string;
|
|
11
|
+
/** SSH executable. */
|
|
12
|
+
readonly sshBinary?: string;
|
|
13
|
+
/** Remote discovery document path; empty derives from `remoteDshHome`. */
|
|
14
|
+
readonly remoteStateFile?: string;
|
|
15
|
+
readonly probeTimeoutMs?: number;
|
|
16
|
+
readonly connectTimeoutMs?: number;
|
|
17
|
+
readonly healthIntervalMs?: number;
|
|
18
|
+
readonly reconnect?: boolean;
|
|
19
|
+
readonly maxReconnectDelayMs?: number;
|
|
20
|
+
/** `yes`, `no`, `ask`, or `accept-new`. */
|
|
21
|
+
readonly strictHostKeyChecking?: string;
|
|
22
|
+
/** Provision DSH + dsh-remote-agent when discovery finds no agent. */
|
|
23
|
+
readonly autoProvision?: boolean;
|
|
24
|
+
readonly remoteDshHome?: string;
|
|
25
|
+
readonly remoteInstallDir?: string;
|
|
26
|
+
readonly remoteDshVersion?: string;
|
|
27
|
+
readonly remotePnpmVersion?: string;
|
|
28
|
+
/** Node.js version installed into the remote install dir when none is usable. */
|
|
29
|
+
readonly remoteNodeVersion?: string;
|
|
30
|
+
/** Download Node.js into the remote install dir when it is missing or too old. */
|
|
31
|
+
readonly autoInstallNode?: boolean;
|
|
32
|
+
/** `systemd`, `nohup`, or `none`. */
|
|
33
|
+
readonly remoteStart?: string;
|
|
34
|
+
readonly provisionTimeoutMs?: number;
|
|
35
|
+
/** Keep the model key local and proxy it through a reverse SSH tunnel. Default false. */
|
|
36
|
+
readonly credentialProxy?: boolean;
|
|
37
|
+
/** Copy the resolved local model key into the remote service environment. */
|
|
38
|
+
readonly copyModelKey?: boolean;
|
|
39
|
+
/** Provider base URL used when the key is copied to the remote host. */
|
|
40
|
+
readonly remoteModelBaseUrl?: string;
|
|
41
|
+
readonly credentialProxyUpstream?: string;
|
|
42
|
+
/** Remote loopback port that receives the reverse-forwarded proxy. */
|
|
43
|
+
readonly credentialProxyRemotePort?: number;
|
|
44
|
+
/** Credential reference resolved in the local DSH process. */
|
|
45
|
+
readonly modelCredentialRef?: string;
|
|
46
|
+
/** Mirror remote sessions into the local conversation UI. */
|
|
47
|
+
readonly mirrorSessions?: boolean;
|
|
48
|
+
/** Poll interval for mirrored remote event logs. */
|
|
49
|
+
readonly mirrorPollMs?: number;
|
|
50
|
+
/** Maximum remote events replayed when a mirror is created. */
|
|
51
|
+
readonly mirrorMaxEvents?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Loader-validated schema. */
|
|
54
|
+
export declare const Config: z<Config>;
|
|
55
|
+
/** How the remote DSH web host is supervised after provisioning. */
|
|
56
|
+
export type RemoteStartMode = 'systemd' | 'nohup' | 'none';
|
|
57
|
+
/** Resolved config. */
|
|
58
|
+
export interface ResolvedConfig {
|
|
59
|
+
readonly stateDir: string;
|
|
60
|
+
readonly sshBinary: string;
|
|
61
|
+
readonly remoteStateFile: string;
|
|
62
|
+
readonly probeTimeoutMs: number;
|
|
63
|
+
readonly connectTimeoutMs: number;
|
|
64
|
+
readonly healthIntervalMs: number;
|
|
65
|
+
readonly reconnect: boolean;
|
|
66
|
+
readonly maxReconnectDelayMs: number;
|
|
67
|
+
readonly strictHostKeyChecking: 'yes' | 'no' | 'ask' | 'accept-new';
|
|
68
|
+
readonly autoProvision: boolean;
|
|
69
|
+
readonly remoteDshHome: string;
|
|
70
|
+
readonly remoteInstallDir: string;
|
|
71
|
+
readonly remoteDshVersion: string;
|
|
72
|
+
readonly remotePnpmVersion: string;
|
|
73
|
+
readonly remoteNodeVersion: string;
|
|
74
|
+
readonly autoInstallNode: boolean;
|
|
75
|
+
readonly remoteStart: RemoteStartMode;
|
|
76
|
+
readonly provisionTimeoutMs: number;
|
|
77
|
+
readonly credentialProxy: boolean;
|
|
78
|
+
readonly copyModelKey: boolean;
|
|
79
|
+
readonly remoteModelBaseUrl: string;
|
|
80
|
+
readonly credentialProxyUpstream: string;
|
|
81
|
+
readonly credentialProxyRemotePort: number;
|
|
82
|
+
readonly modelCredentialRef: string;
|
|
83
|
+
readonly mirrorSessions: boolean;
|
|
84
|
+
readonly mirrorPollMs: number;
|
|
85
|
+
readonly mirrorMaxEvents: number;
|
|
86
|
+
}
|
|
87
|
+
/** DSH home. */
|
|
88
|
+
export declare function resolveDshHome(env?: NodeJS.ProcessEnv): string;
|
|
89
|
+
/** Expand `~` and resolve relative paths. */
|
|
90
|
+
export declare function resolveLocalPath(path: string, home?: string): string;
|
|
91
|
+
/** Join a remote path expression without resolving it locally. */
|
|
92
|
+
export declare function joinRemotePath(base: string, suffix: string): string;
|
|
93
|
+
/** Materialize defaults. */
|
|
94
|
+
export declare function resolveConfig(config?: Config, env?: NodeJS.ProcessEnv): ResolvedConfig;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local model credential proxy.
|
|
3
|
+
*
|
|
4
|
+
* The remote DSH process is configured with a per-host proxy token and a base
|
|
5
|
+
* URL that points at a reverse SSH tunnel. Requests arrive here over the
|
|
6
|
+
* tunnel after token validation; the proxy then injects the real local API
|
|
7
|
+
* key and forwards to the provider. The key never leaves the local machine.
|
|
8
|
+
*
|
|
9
|
+
* The proxy binds loopback only. The reverse tunnel exposes it only on the
|
|
10
|
+
* remote host's loopback interface while the SSH connection is up.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-remote-local/credentials/proxy
|
|
13
|
+
*/
|
|
14
|
+
/** Options for one proxy instance. */
|
|
15
|
+
export interface CredentialProxyOptions {
|
|
16
|
+
/** Real provider key. Never logged or returned. */
|
|
17
|
+
readonly apiKey: string;
|
|
18
|
+
/** Provider origin, for example `https://api.deepseek.com`. */
|
|
19
|
+
readonly upstreamBaseUrl: string;
|
|
20
|
+
/** Loopback bind address; defaults to 127.0.0.1. */
|
|
21
|
+
readonly host?: string;
|
|
22
|
+
/** Per-request upstream idle timeout. */
|
|
23
|
+
readonly timeoutMs?: number;
|
|
24
|
+
/** Whether a caller-supplied placeholder is one of this profile's proxy tokens. */
|
|
25
|
+
readonly isTokenValid: (token: string) => boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Started proxy. */
|
|
28
|
+
export interface CredentialProxyAddress {
|
|
29
|
+
readonly port: number;
|
|
30
|
+
readonly baseUrl: string;
|
|
31
|
+
}
|
|
32
|
+
/** Loopback credential proxy. */
|
|
33
|
+
export declare class ModelCredentialProxy {
|
|
34
|
+
#private;
|
|
35
|
+
constructor(options: CredentialProxyOptions);
|
|
36
|
+
/** Start on an ephemeral loopback port. */
|
|
37
|
+
start(): Promise<CredentialProxyAddress>;
|
|
38
|
+
/** Stop and release active sockets. */
|
|
39
|
+
stop(): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local plugin error taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* These codes are surfaced to the Settings UI and to agent tools; they are
|
|
5
|
+
* never sent to a remote host as-is.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-remote-local/errors
|
|
8
|
+
*/
|
|
9
|
+
/** Stable local error codes. */
|
|
10
|
+
export type LocalErrorCode = 'request/invalid' | 'host/invalid' | 'host/duplicate' | 'host/not-found' | 'host/not-connected' | 'host/connect-failed' | 'ssh/probe-failed' | 'ssh/tunnel-failed' | 'agent/unavailable' | 'agent/error' | 'directory/unreadable' | 'internal';
|
|
11
|
+
/** Error carrying a stable code and JSON-safe details. */
|
|
12
|
+
export declare class LocalError extends Error {
|
|
13
|
+
readonly code: LocalErrorCode;
|
|
14
|
+
readonly details: Record<string, unknown>;
|
|
15
|
+
readonly retryable: boolean;
|
|
16
|
+
constructor(code: LocalErrorCode, message: string, options?: {
|
|
17
|
+
readonly details?: Record<string, unknown>;
|
|
18
|
+
readonly retryable?: boolean;
|
|
19
|
+
readonly cause?: unknown;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Normalize unknown failures without leaking stack traces to the browser. */
|
|
23
|
+
export declare function asLocalError(error: unknown, fallback?: LocalErrorCode): LocalError;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host API contract shared by the host handler and the browser Settings page.
|
|
3
|
+
*
|
|
4
|
+
* The route is registered inside DSH Connection's authenticated `/api` fence,
|
|
5
|
+
* so it works through the web HTTP carrier and the desktop IPC carrier without
|
|
6
|
+
* a second authentication scheme.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/host-api/contract
|
|
9
|
+
*/
|
|
10
|
+
import type { HostView } from '../hosts/types.ts';
|
|
11
|
+
import type { AcceptedResult, CreateSessionResult, HostInfo, PromptDeliveryMode, RemoteSessionSummary, RemoteWorkspace, SessionSnapshot } from '@dsh-remote/protocol';
|
|
12
|
+
/** Exact authenticated Fetch route owned by the local plugin. */
|
|
13
|
+
export declare const HOST_API_PATH = "/api/dsh-remote";
|
|
14
|
+
/** Endpoint names. */
|
|
15
|
+
export type HostApiEndpoint = 'state' | 'target/validate' | 'host/add' | 'host/remove' | 'host/connect' | 'host/disconnect' | 'host/refresh' | 'host/provision' | 'host/workspaces' | 'host/workspace/create' | 'host/directory/list' | 'local/directory/list' | 'local/workspace/create' | 'host/sessions' | 'host/session' | 'host/session/create' | 'host/session/prompt' | 'host/session/cancel' | 'host/web-url' | 'host/files/list' | 'host/files/read' | 'host/files/stat' | 'host/files/bytes';
|
|
16
|
+
/** Browser-safe state snapshot. */
|
|
17
|
+
export interface RemoteHostsState {
|
|
18
|
+
readonly protocolVersion: number;
|
|
19
|
+
readonly hosts: readonly HostView[];
|
|
20
|
+
}
|
|
21
|
+
/** Result of parsing a destination. */
|
|
22
|
+
export interface ValidatedTarget {
|
|
23
|
+
readonly canonical: string;
|
|
24
|
+
readonly host: string;
|
|
25
|
+
readonly user: string | null;
|
|
26
|
+
readonly port: number | null;
|
|
27
|
+
}
|
|
28
|
+
/** Result of listing workspaces. */
|
|
29
|
+
export interface HostWorkspace extends RemoteWorkspace {
|
|
30
|
+
/** Local mirror workspace id, when it has been synced. */
|
|
31
|
+
readonly localWorkspaceId?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface WorkspacesResult {
|
|
34
|
+
readonly workspaces: readonly HostWorkspace[];
|
|
35
|
+
}
|
|
36
|
+
/** Result of listing sessions. */
|
|
37
|
+
export interface SessionsResult {
|
|
38
|
+
readonly sessions: readonly RemoteSessionSummary[];
|
|
39
|
+
}
|
|
40
|
+
export interface HostCreateSessionResult extends CreateSessionResult {
|
|
41
|
+
/** Local mirror session that opens in the ordinary Conversation UI. */
|
|
42
|
+
readonly localSessionId?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface MirrorFileResult {
|
|
45
|
+
readonly mirrored: boolean;
|
|
46
|
+
readonly value?: unknown;
|
|
47
|
+
}
|
|
48
|
+
/** Result of resolving the remote Web UI. */
|
|
49
|
+
export interface WebUrlResult {
|
|
50
|
+
readonly url: string;
|
|
51
|
+
}
|
|
52
|
+
/** One request body. */
|
|
53
|
+
export interface HostApiRequest {
|
|
54
|
+
readonly endpoint: HostApiEndpoint;
|
|
55
|
+
readonly payload?: unknown;
|
|
56
|
+
}
|
|
57
|
+
/** Success/failure result returned by the route. */
|
|
58
|
+
export type HostApiResult<T> = {
|
|
59
|
+
readonly ok: true;
|
|
60
|
+
readonly value: T;
|
|
61
|
+
} | {
|
|
62
|
+
readonly ok: false;
|
|
63
|
+
readonly error: {
|
|
64
|
+
readonly code: string;
|
|
65
|
+
readonly message: string;
|
|
66
|
+
readonly details: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/** Payloads accepted by each endpoint. */
|
|
70
|
+
export interface HostApiPayloads {
|
|
71
|
+
state: undefined;
|
|
72
|
+
'target/validate': {
|
|
73
|
+
sshTarget: string;
|
|
74
|
+
};
|
|
75
|
+
'host/add': {
|
|
76
|
+
sshTarget: string;
|
|
77
|
+
label?: string;
|
|
78
|
+
token?: string;
|
|
79
|
+
remoteAgentPort?: number;
|
|
80
|
+
autoConnect?: boolean;
|
|
81
|
+
};
|
|
82
|
+
'host/remove': {
|
|
83
|
+
hostId: string;
|
|
84
|
+
};
|
|
85
|
+
'host/connect': {
|
|
86
|
+
hostId: string;
|
|
87
|
+
};
|
|
88
|
+
'host/disconnect': {
|
|
89
|
+
hostId: string;
|
|
90
|
+
};
|
|
91
|
+
'host/refresh': {
|
|
92
|
+
hostId: string;
|
|
93
|
+
};
|
|
94
|
+
'host/provision': {
|
|
95
|
+
hostId: string;
|
|
96
|
+
};
|
|
97
|
+
'host/workspaces': {
|
|
98
|
+
hostId: string;
|
|
99
|
+
};
|
|
100
|
+
'host/workspace/create': {
|
|
101
|
+
hostId: string;
|
|
102
|
+
path: string;
|
|
103
|
+
title?: string;
|
|
104
|
+
};
|
|
105
|
+
'host/directory/list': {
|
|
106
|
+
hostId: string;
|
|
107
|
+
path?: string;
|
|
108
|
+
};
|
|
109
|
+
'local/directory/list': {
|
|
110
|
+
path?: string;
|
|
111
|
+
};
|
|
112
|
+
'local/workspace/create': {
|
|
113
|
+
path: string;
|
|
114
|
+
title?: string;
|
|
115
|
+
};
|
|
116
|
+
'host/sessions': {
|
|
117
|
+
hostId: string;
|
|
118
|
+
workspaceId?: string;
|
|
119
|
+
};
|
|
120
|
+
'host/session': {
|
|
121
|
+
hostId: string;
|
|
122
|
+
sessionId: string;
|
|
123
|
+
limit?: number;
|
|
124
|
+
};
|
|
125
|
+
'host/session/create': {
|
|
126
|
+
hostId: string;
|
|
127
|
+
workspaceId?: string;
|
|
128
|
+
cwd?: string;
|
|
129
|
+
agentPreset?: string;
|
|
130
|
+
};
|
|
131
|
+
'host/session/prompt': {
|
|
132
|
+
hostId: string;
|
|
133
|
+
sessionId: string;
|
|
134
|
+
text: string;
|
|
135
|
+
mode?: PromptDeliveryMode;
|
|
136
|
+
provider?: string;
|
|
137
|
+
model?: string;
|
|
138
|
+
reasoningEffort?: string;
|
|
139
|
+
};
|
|
140
|
+
'host/session/cancel': {
|
|
141
|
+
hostId: string;
|
|
142
|
+
sessionId: string;
|
|
143
|
+
};
|
|
144
|
+
'host/web-url': {
|
|
145
|
+
hostId: string;
|
|
146
|
+
};
|
|
147
|
+
'host/files/list': {
|
|
148
|
+
sessionId: string;
|
|
149
|
+
path: string;
|
|
150
|
+
};
|
|
151
|
+
'host/files/read': {
|
|
152
|
+
sessionId: string;
|
|
153
|
+
path: string;
|
|
154
|
+
offset?: number;
|
|
155
|
+
limit?: number;
|
|
156
|
+
};
|
|
157
|
+
'host/files/stat': {
|
|
158
|
+
sessionId: string;
|
|
159
|
+
path: string;
|
|
160
|
+
};
|
|
161
|
+
'host/files/bytes': {
|
|
162
|
+
sessionId: string;
|
|
163
|
+
path: string;
|
|
164
|
+
offset?: number;
|
|
165
|
+
length?: number;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Values returned by each endpoint. */
|
|
169
|
+
export interface HostApiValues {
|
|
170
|
+
state: RemoteHostsState;
|
|
171
|
+
'target/validate': ValidatedTarget;
|
|
172
|
+
'host/add': HostView;
|
|
173
|
+
'host/remove': {
|
|
174
|
+
removed: true;
|
|
175
|
+
};
|
|
176
|
+
'host/connect': HostView;
|
|
177
|
+
'host/disconnect': HostView;
|
|
178
|
+
'host/refresh': HostView;
|
|
179
|
+
'host/provision': HostView;
|
|
180
|
+
'host/workspaces': WorkspacesResult;
|
|
181
|
+
'host/workspace/create': RemoteWorkspace;
|
|
182
|
+
'host/directory/list': {
|
|
183
|
+
path: string;
|
|
184
|
+
parent: string;
|
|
185
|
+
entries: readonly {
|
|
186
|
+
name: string;
|
|
187
|
+
path: string;
|
|
188
|
+
}[];
|
|
189
|
+
truncated: boolean;
|
|
190
|
+
};
|
|
191
|
+
'local/directory/list': {
|
|
192
|
+
path: string;
|
|
193
|
+
parent: string;
|
|
194
|
+
entries: readonly {
|
|
195
|
+
name: string;
|
|
196
|
+
path: string;
|
|
197
|
+
}[];
|
|
198
|
+
truncated: boolean;
|
|
199
|
+
};
|
|
200
|
+
'local/workspace/create': {
|
|
201
|
+
workspaceId: string;
|
|
202
|
+
};
|
|
203
|
+
'host/sessions': SessionsResult;
|
|
204
|
+
'host/session': SessionSnapshot;
|
|
205
|
+
'host/session/create': HostCreateSessionResult;
|
|
206
|
+
'host/session/prompt': AcceptedResult;
|
|
207
|
+
'host/session/cancel': AcceptedResult;
|
|
208
|
+
'host/web-url': WebUrlResult;
|
|
209
|
+
'host/files/list': MirrorFileResult;
|
|
210
|
+
'host/files/read': MirrorFileResult;
|
|
211
|
+
'host/files/stat': MirrorFileResult;
|
|
212
|
+
'host/files/bytes': MirrorFileResult;
|
|
213
|
+
}
|
|
214
|
+
/** Informational type for the Settings page. */
|
|
215
|
+
export interface HostSummary extends Pick<HostView, 'id' | 'label' | 'sshTarget' | 'phase'> {
|
|
216
|
+
readonly hostInfo: HostInfo | null;
|
|
217
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated host API over Connection's exact Fetch route.
|
|
3
|
+
*
|
|
4
|
+
* The route is mounted under `/api`, so Connection owns the Host/Origin fence,
|
|
5
|
+
* browser authentication, owner-Peer check, and body limits. This module only
|
|
6
|
+
* validates the endpoint/payload, dispatches to the service, and shapes the
|
|
7
|
+
* JSON result.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-remote-local/host-api/handler
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { RemoteHostsService } from '../service.ts';
|
|
13
|
+
import type { RemoteMirrorService } from '../mirror/service.ts';
|
|
14
|
+
/** Register the route; returns its asynchronous disposer. */
|
|
15
|
+
export declare function registerHostApi(ctx: Context, service: RemoteHostsService, mirrors?: RemoteMirrorService): () => Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authenticated streaming route for mirrored remote terminals.
|
|
3
|
+
*
|
|
4
|
+
* The stock terminal Remote service is bound before an instance adapter can
|
|
5
|
+
* replace it, so the client terminal proxy talks to this exact Fetch route
|
|
6
|
+
* instead. Unary actions return JSON; attach returns SSE frames already in the
|
|
7
|
+
* shape the normal terminal UI consumes.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-remote-local/host-api/terminal
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { RemoteTerminals } from '../remote/terminal.ts';
|
|
13
|
+
export declare const TERMINAL_API_PATH = "/api/dsh-remote-terminal";
|
|
14
|
+
/** Register the terminal route. */
|
|
15
|
+
export declare function registerTerminalApi(ctx: Context, terminals: RemoteTerminals): () => Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable host ids.
|
|
3
|
+
*
|
|
4
|
+
* An id is a human-readable slug plus a short hash of the canonical SSH
|
|
5
|
+
* destination. Re-adding the same destination is therefore a duplicate, while
|
|
6
|
+
* two aliases for the same machine remain distinct hosts (the user decides
|
|
7
|
+
* whether to merge them).
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-remote-local/hosts/id
|
|
10
|
+
*/
|
|
11
|
+
/** Whether a value is a valid host id. */
|
|
12
|
+
export declare function isHostId(value: string): boolean;
|
|
13
|
+
/** Slugify a label or destination. */
|
|
14
|
+
export declare function slugify(value: string): string;
|
|
15
|
+
/** Derive a stable id from one canonical destination. */
|
|
16
|
+
export declare function hostIdFor(canonicalDestination: string, label?: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable host registry.
|
|
3
|
+
*
|
|
4
|
+
* One JSON document, mode 0600, serialized through an in-process mutation
|
|
5
|
+
* queue. The token lives here and is never returned to the browser.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-remote-local/hosts/store
|
|
8
|
+
*/
|
|
9
|
+
import type { HostRecord } from './types.ts';
|
|
10
|
+
/** Current on-disk schema version. */
|
|
11
|
+
export declare const HOST_STORE_VERSION = 1;
|
|
12
|
+
/** Load result, including non-fatal malformed-record warnings. */
|
|
13
|
+
export interface HostStoreLoad {
|
|
14
|
+
readonly hosts: HostRecord[];
|
|
15
|
+
readonly warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Whether a value is a complete host record. */
|
|
18
|
+
export declare function isHostRecord(value: unknown): value is HostRecord;
|
|
19
|
+
/** Serialized host registry. */
|
|
20
|
+
export declare class HostStore {
|
|
21
|
+
#private;
|
|
22
|
+
readonly file: string;
|
|
23
|
+
constructor(file: string);
|
|
24
|
+
/** Load valid records; malformed entries are skipped with a warning. */
|
|
25
|
+
load(): Promise<HostStoreLoad>;
|
|
26
|
+
/** Current records. */
|
|
27
|
+
list(): Promise<HostRecord[]>;
|
|
28
|
+
/** Add one record, rejecting a duplicate id or destination. */
|
|
29
|
+
add(record: HostRecord): Promise<HostRecord>;
|
|
30
|
+
/** Merge a patch into one record. */
|
|
31
|
+
update(id: string, patch: Partial<HostRecord>): Promise<HostRecord>;
|
|
32
|
+
/** Remove one record. */
|
|
33
|
+
remove(id: string): Promise<boolean>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host records, runtime state, and browser-safe projections.
|
|
3
|
+
*
|
|
4
|
+
* The token is part of the durable record because the local plugin must
|
|
5
|
+
* reconnect after a restart without prompting. It never crosses the RPC
|
|
6
|
+
* boundary: {@link HostView} carries only `hasToken`.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-remote-local/hosts/types
|
|
9
|
+
*/
|
|
10
|
+
import type { HostInfo } from '@dsh-remote/protocol';
|
|
11
|
+
/** Lifecycle phase shown by the Settings page. */
|
|
12
|
+
export type HostPhase = 'idle' | 'discovering' | 'installing' | 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
13
|
+
/** Durable host record (mode 0600 state file). */
|
|
14
|
+
export interface HostRecord {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
/** Canonical SSH destination exactly as typed by the user. */
|
|
18
|
+
readonly sshTarget: string;
|
|
19
|
+
/** Explicit SSH port, or null when the destination uses SSH config/default 22. */
|
|
20
|
+
readonly sshPort: number | null;
|
|
21
|
+
/** Cached remote agent port discovered over SSH. */
|
|
22
|
+
readonly remoteAgentPort: number | null;
|
|
23
|
+
/** Bearer token issued by the remote agent. Secret. */
|
|
24
|
+
readonly token: string | null;
|
|
25
|
+
/** Per-host placeholder accepted by the local credential proxy. Secret. */
|
|
26
|
+
readonly proxyToken?: string | null;
|
|
27
|
+
readonly autoConnect: boolean;
|
|
28
|
+
readonly createdAt: string;
|
|
29
|
+
readonly updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
/** Ephemeral runtime state for one host. */
|
|
32
|
+
export interface HostRuntime {
|
|
33
|
+
readonly phase: HostPhase;
|
|
34
|
+
readonly localPort: number | null;
|
|
35
|
+
readonly localUrl: string | null;
|
|
36
|
+
readonly hostInfo: HostInfo | null;
|
|
37
|
+
readonly workspaceCount: number | null;
|
|
38
|
+
readonly error: string | null;
|
|
39
|
+
readonly errorCode: string | null;
|
|
40
|
+
readonly connectedAt: string | null;
|
|
41
|
+
/** Human-readable progress detail for discovery/provisioning. */
|
|
42
|
+
readonly detail: string | null;
|
|
43
|
+
/** Whether the local model credential proxy is active for this host. */
|
|
44
|
+
readonly modelProxy: boolean;
|
|
45
|
+
/** Whether the resolved local model key was copied into the remote environment. */
|
|
46
|
+
readonly modelDirect: boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Browser/tool-safe host projection. */
|
|
49
|
+
export interface HostView {
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly label: string;
|
|
52
|
+
readonly sshTarget: string;
|
|
53
|
+
readonly sshPort: number | null;
|
|
54
|
+
readonly remoteAgentPort: number | null;
|
|
55
|
+
readonly autoConnect: boolean;
|
|
56
|
+
readonly createdAt: string;
|
|
57
|
+
readonly updatedAt: string;
|
|
58
|
+
readonly hasToken: boolean;
|
|
59
|
+
readonly phase: HostPhase;
|
|
60
|
+
readonly localPort: number | null;
|
|
61
|
+
readonly localUrl: string | null;
|
|
62
|
+
readonly hostInfo: HostInfo | null;
|
|
63
|
+
readonly workspaceCount: number | null;
|
|
64
|
+
readonly error: string | null;
|
|
65
|
+
readonly errorCode: string | null;
|
|
66
|
+
readonly connectedAt: string | null;
|
|
67
|
+
/** Human-readable progress detail for discovery/provisioning. */
|
|
68
|
+
readonly detail: string | null;
|
|
69
|
+
/** Whether the local model credential proxy is active for this host. */
|
|
70
|
+
readonly modelProxy: boolean;
|
|
71
|
+
/** Whether the resolved local model key was copied into the remote environment. */
|
|
72
|
+
readonly modelDirect: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** Input accepted by `host/add`. */
|
|
75
|
+
export interface AddHostInput {
|
|
76
|
+
readonly label?: string;
|
|
77
|
+
readonly sshTarget: string;
|
|
78
|
+
readonly token?: string;
|
|
79
|
+
readonly remoteAgentPort?: number;
|
|
80
|
+
readonly autoConnect?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** Create an idle runtime state. */
|
|
83
|
+
export declare function idleRuntime(): HostRuntime;
|
|
84
|
+
/** Project one record + runtime into the browser-safe view. */
|
|
85
|
+
export declare function toHostView(record: HostRecord, runtime: HostRuntime): HostView;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-remote-local-plugin host entry.
|
|
3
|
+
*
|
|
4
|
+
* Owns the host registry, SSH tunnels, the authenticated Connection RPC
|
|
5
|
+
* channel used by the Settings page, and the model-facing remote tools.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-remote-local
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import { Config, type Config as LocalConfig } from './config.ts';
|
|
11
|
+
export declare const name = "dsh-remote-local";
|
|
12
|
+
/** Connection owns the authenticated `/api` carrier; credentials stay local. */
|
|
13
|
+
export declare const inject: string[];
|
|
14
|
+
export { Config };
|
|
15
|
+
export type { LocalConfig };
|
|
16
|
+
export { RemoteHostsService } from './service.ts';
|
|
17
|
+
export { HOST_API_PATH } from './host-api/contract.ts';
|
|
18
|
+
/** Mount the host registry and its extension points. */
|
|
19
|
+
export declare function apply(ctx: Context, config?: LocalConfig): Promise<void>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native remote-session mirror.
|
|
3
|
+
*
|
|
4
|
+
* A remote DSH session is replayed into a local Session and registered with a
|
|
5
|
+
* custom Agent. The existing local client therefore lists it beside local
|
|
6
|
+
* workspaces and renders it with the ordinary Conversation UI, while prompts
|
|
7
|
+
* and cancellation are routed back to the remote agent.
|
|
8
|
+
*
|
|
9
|
+
* This is a mirror, not a second agent loop: the local Session is a durable
|
|
10
|
+
* projection of the remote log, and the remote DSH remains the execution owner.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-remote-local/mirror
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import type { Session, SessionId } from '@deepseek-ai/dsh-session';
|
|
16
|
+
import type { PromptDeliveryMode } from '@dsh-remote/protocol';
|
|
17
|
+
import type { ResolvedConfig } from '../config.ts';
|
|
18
|
+
import type { RemoteHostsService } from '../service.ts';
|
|
19
|
+
interface MirrorRecord {
|
|
20
|
+
readonly hostId: string;
|
|
21
|
+
readonly remoteSessionId: string;
|
|
22
|
+
localSessionId: string;
|
|
23
|
+
readonly remoteWorkspaceId: string | null;
|
|
24
|
+
readonly anchor: string;
|
|
25
|
+
lastSeq: number;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
interface MirrorRuntime {
|
|
29
|
+
readonly record: MirrorRecord;
|
|
30
|
+
readonly agent: MirrorAgent;
|
|
31
|
+
readonly session: Session;
|
|
32
|
+
timer: NodeJS.Timeout | undefined;
|
|
33
|
+
running: boolean;
|
|
34
|
+
stopped: boolean;
|
|
35
|
+
lastError: string | null;
|
|
36
|
+
pendingUsers: unknown[];
|
|
37
|
+
waiters: Array<() => void>;
|
|
38
|
+
}
|
|
39
|
+
interface MirrorAgent {
|
|
40
|
+
readonly id: SessionId;
|
|
41
|
+
readonly session: Session;
|
|
42
|
+
readonly options: {
|
|
43
|
+
provider: string;
|
|
44
|
+
model: string;
|
|
45
|
+
reasoningEffort?: string;
|
|
46
|
+
};
|
|
47
|
+
readonly ctx: Context;
|
|
48
|
+
readonly inbox: Record<string, unknown[]>;
|
|
49
|
+
readonly status: 'idle' | 'running';
|
|
50
|
+
cancel(cause?: unknown): void;
|
|
51
|
+
whenIdle(): Promise<void>;
|
|
52
|
+
runMaintenance<T>(task: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
53
|
+
send(message: unknown, target?: unknown, wakeup?: boolean): void;
|
|
54
|
+
followup(message: unknown): void;
|
|
55
|
+
steer(message: unknown): void;
|
|
56
|
+
inject(message: unknown): void;
|
|
57
|
+
}
|
|
58
|
+
/** Mirror remote sessions into local Sessions and custom Agents. */
|
|
59
|
+
export declare class RemoteMirrorService {
|
|
60
|
+
#private;
|
|
61
|
+
constructor(ctx: Context, hosts: RemoteHostsService, config: ResolvedConfig);
|
|
62
|
+
/** Load mappings and begin periodic discovery. */
|
|
63
|
+
start(): Promise<void>;
|
|
64
|
+
/** Stop mirrors and timers. */
|
|
65
|
+
dispose(): Promise<void>;
|
|
66
|
+
/** Reconcile connected hosts with the local workspace/session catalog. */
|
|
67
|
+
sync(): Promise<void>;
|
|
68
|
+
/** Resolve a local workspace anchor to its remote filesystem context. */
|
|
69
|
+
remoteFileContext(workspaceRoot: string): {
|
|
70
|
+
hostId: string;
|
|
71
|
+
remoteRoot: string;
|
|
72
|
+
anchor: string;
|
|
73
|
+
} | undefined;
|
|
74
|
+
/** Whether the test-only manifest excludes this remote workspace from UI listings. */
|
|
75
|
+
isTestHidden(remoteWorkspaceId: string, remotePath: string): boolean;
|
|
76
|
+
/** Find the local mirror workspace for one remote workspace. */
|
|
77
|
+
localWorkspaceId(hostId: string, remoteWorkspaceId: string): string | undefined;
|
|
78
|
+
/** Find the local mirror for one remote session. */
|
|
79
|
+
localSessionId(hostId: string, remoteSessionId: string): string | undefined;
|
|
80
|
+
/** Create a mirror on demand and return its local session id. */
|
|
81
|
+
ensureRemoteSession(hostId: string, remoteSessionId: string): Promise<string | undefined>;
|
|
82
|
+
/** Route one prompt from the local Agent façade to the remote session. */
|
|
83
|
+
prompt(runtime: MirrorRuntime, message: unknown, mode: PromptDeliveryMode): void;
|
|
84
|
+
/** Route cancellation to the remote session. */
|
|
85
|
+
cancel(runtime: MirrorRuntime): void;
|
|
86
|
+
}
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-only visibility manifest.
|
|
3
|
+
*
|
|
4
|
+
* This file lives only in a test DSH_HOME. It is intentionally not a Settings
|
|
5
|
+
* option and not an agent tool: a test harness writes it before/after a run.
|
|
6
|
+
*
|
|
7
|
+
* $DSH_HOME/remote/test-visibility.json
|
|
8
|
+
* { "hiddenPaths": ["/tmp/dsh-remote-tests"], "hiddenWorkspaceIds": ["..."] }
|
|
9
|
+
*
|
|
10
|
+
* Hidden paths/ids are not registered into the local workspace registry. Hiding
|
|
11
|
+
* an already-mirrored workspace archives its local mirror Sessions and removes
|
|
12
|
+
* the local row; removing the entry restores it from the remote log later.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-remote-local/mirror/test-visibility
|
|
15
|
+
*/
|
|
16
|
+
export interface TestVisibility {
|
|
17
|
+
readonly hiddenPaths: readonly string[];
|
|
18
|
+
readonly hiddenWorkspaceIds: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/** Read the manifest; absent or malformed files disable test visibility. */
|
|
21
|
+
export declare function loadTestVisibility(stateDir: string): Promise<TestVisibility>;
|
|
22
|
+
/** Whether one remote workspace/path is hidden from local registration. */
|
|
23
|
+
export declare function isTestHidden(remotePath: string, remoteWorkspaceId: string, visibility: TestVisibility): boolean;
|