dsh-remote-local-plugin 0.8.0 → 0.8.2

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/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /* dsh-remote-local-plugin@0.8.0 — bundled host entry */
1
+ /* dsh-remote-local-plugin@0.8.2 — bundled host entry */
2
2
 
3
3
  // src/index.ts
4
4
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
@@ -23,6 +23,8 @@ var Config = z.object({
23
23
  remoteDshVersion: z.string().default("0.2.1-rc.2"),
24
24
  remotePnpmVersion: z.string().default("11.21.0"),
25
25
  remoteNodeVersion: z.string().default("22.19.0"),
26
+ remoteInstallProxy: z.string().default(""),
27
+ remoteInstallNoProxy: z.string().default("127.0.0.1,localhost"),
26
28
  autoInstallNode: z.boolean().default(true),
27
29
  remoteStart: z.string().default("systemd"),
28
30
  provisionTimeoutMs: z.number().min(6e4).max(18e5).default(6e5),
@@ -80,6 +82,8 @@ function resolveConfig(config = {}, env = process.env) {
80
82
  remoteDshVersion: config.remoteDshVersion ?? "0.2.1-rc.2",
81
83
  remotePnpmVersion: config.remotePnpmVersion ?? "11.21.0",
82
84
  remoteNodeVersion: config.remoteNodeVersion ?? "22.19.0",
85
+ remoteInstallProxy: config.remoteInstallProxy ?? "",
86
+ remoteInstallNoProxy: config.remoteInstallNoProxy ?? "127.0.0.1,localhost",
83
87
  autoInstallNode: config.autoInstallNode ?? true,
84
88
  remoteStart,
85
89
  provisionTimeoutMs: config.provisionTimeoutMs ?? 6e5,
@@ -2793,11 +2797,32 @@ AUTO_INSTALL_NODE=${options.autoInstallNode ? "1" : "0"}
2793
2797
  AGENT_TARBALL=${shellLiteral(options.agentTarball)}
2794
2798
  MODEL_BASE_URL=${options.modelBaseUrl === void 0 ? "''" : shellLiteral(options.modelBaseUrl)}
2795
2799
  MODEL_API_KEY=${options.modelApiKey === void 0 ? "''" : shellLiteral(options.modelApiKey)}
2800
+ PROXY_URL=${options.proxyUrl === void 0 ? "''" : shellLiteral(options.proxyUrl)}
2801
+ PROXY_NO_PROXY=${options.noProxy === void 0 ? "''" : shellLiteral(options.noProxy)}
2796
2802
  STATE_FILE="$DSH_HOME/remote-agent/agent.json"
2797
2803
  WAIT_FOR_STATE=${options.start === "none" ? 0 : 1}
2798
2804
 
2799
2805
  export DSH_HOME
2800
2806
  mkdir -p "$INSTALL_DIR" "$DSH_HOME/remote-agent"
2807
+ echo "dsh-remote: preparing remote install"
2808
+ if [ -z "$PROXY_URL" ]; then
2809
+ # Reuse a proxy already configured in the remote login environment.
2810
+ PROXY_URL="$(bash -lc 'printf "%s" "\${HTTPS_PROXY:-\${HTTP_PROXY:-}}"' 2>/dev/null | tail -n 1 || true)"
2811
+ fi
2812
+ if [ -z "$PROXY_URL" ]; then
2813
+ # Some clusters configure the proxy in .bashrc, which login shells skip.
2814
+ PROXY_URL="$(bash -ic 'printf "%s" "\${HTTPS_PROXY:-\${HTTP_PROXY:-}}"' 2>/dev/null | tail -n 1 || true)"
2815
+ fi
2816
+ if [ -n "$PROXY_URL" ]; then
2817
+ export HTTP_PROXY="$PROXY_URL" http_proxy="$PROXY_URL"
2818
+ export HTTPS_PROXY="$PROXY_URL" https_proxy="$PROXY_URL"
2819
+ export ALL_PROXY="$PROXY_URL" all_proxy="$PROXY_URL"
2820
+ export npm_config_proxy="$PROXY_URL" npm_config_https_proxy="$PROXY_URL"
2821
+ if [ -n "$PROXY_NO_PROXY" ]; then
2822
+ export NO_PROXY="$PROXY_NO_PROXY" no_proxy="$PROXY_NO_PROXY"
2823
+ fi
2824
+ echo "dsh-remote: install proxy configured"
2825
+ fi
2801
2826
 
2802
2827
  ${nodePathPrelude()}
2803
2828
 
@@ -2819,6 +2844,7 @@ if ! command -v npm >/dev/null 2>&1; then
2819
2844
  echo "dsh-remote: npm is required; install a Node.js distribution that includes npm" >&2
2820
2845
  exit 23
2821
2846
  fi
2847
+ echo "dsh-remote: node ready"
2822
2848
 
2823
2849
  export PATH="$INSTALL_DIR/node_modules/.bin:$PATH"
2824
2850
  if ! command -v pnpm >/dev/null 2>&1; then
@@ -2839,9 +2865,11 @@ fi
2839
2865
  echo "dsh-remote: adding plugin to profile $PROFILE"
2840
2866
  "$DSH_BIN" plugin --profile "$PROFILE" add "$AGENT_TARBALL"
2841
2867
 
2868
+ echo "dsh-remote: starting dsh web"
2842
2869
  ${startBlock}
2843
2870
 
2844
2871
  if [ "$WAIT_FOR_STATE" -eq 1 ]; then
2872
+ echo "dsh-remote: waiting for agent state"
2845
2873
  i=0
2846
2874
  while [ "$i" -lt 60 ]; do
2847
2875
  if [ -s "$STATE_FILE" ]; then break; fi
@@ -2853,6 +2881,7 @@ fi
2853
2881
  echo "dsh-remote: installed"
2854
2882
  echo "dsh-remote: state file $STATE_FILE"
2855
2883
  if [ "$WAIT_FOR_STATE" -eq 1 ] && [ -s "$STATE_FILE" ]; then
2884
+ echo "dsh-remote: agent ready"
2856
2885
  PORT_VALUE=$(sed -n 's/.*"port": *\\([0-9][0-9]*\\).*/\\1/p' "$STATE_FILE" | head -1)
2857
2886
  echo "dsh-remote: remote agent port \${PORT_VALUE:-unknown}"
2858
2887
  else
@@ -2875,6 +2904,7 @@ cat > "$UNIT_DIR/dsh-remote-agent.service" <<DSH_REMOTE_UNIT
2875
2904
  ${unit}DSH_REMOTE_UNIT
2876
2905
  chmod 600 "$UNIT_DIR/dsh-remote-agent.service"
2877
2906
 
2907
+ echo "dsh-remote: restarting systemd user unit"
2878
2908
  if command -v systemctl >/dev/null 2>&1 && systemctl --user daemon-reload >/dev/null 2>&1; then
2879
2909
  if systemctl --user enable dsh-remote-agent.service >/dev/null 2>&1 && systemctl --user restart dsh-remote-agent.service; then
2880
2910
  echo "dsh-remote: restarted systemd user unit dsh-remote-agent.service"
@@ -2892,6 +2922,7 @@ function renderNohup(options) {
2892
2922
  return `if [ -f "$DSH_HOME/remote-agent/web.pid" ]; then
2893
2923
  kill "$(cat "$DSH_HOME/remote-agent/web.pid")" 2>/dev/null || true
2894
2924
  fi
2925
+ echo "dsh-remote: starting dsh web under nohup"
2895
2926
  ${modelEnv}nohup "$DSH_BIN" web --host 127.0.0.1 --port "$PORT" --no-open > "$DSH_HOME/remote-agent/web.log" 2>&1 &
2896
2927
  echo $! > "$DSH_HOME/remote-agent/web.pid"
2897
2928
  echo "dsh-remote: started dsh web under nohup (pid $(cat "$DSH_HOME/remote-agent/web.pid"))"`;
@@ -2987,7 +3018,7 @@ function runRemoteScript(target, script, options) {
2987
3018
  reject(new Error(`remote bootstrap timed out after ${options.timeoutMs}ms`));
2988
3019
  }, options.timeoutMs);
2989
3020
  timer.unref?.();
2990
- const append = (target2, chunk) => {
3021
+ const append = (target2, stream, chunk) => {
2991
3022
  bytes += chunk.length;
2992
3023
  if (bytes > 4 * 1024 * 1024) {
2993
3024
  child.kill("SIGKILL");
@@ -2995,9 +3026,13 @@ function runRemoteScript(target, script, options) {
2995
3026
  }
2996
3027
  target2.push(chunk);
2997
3028
  if (options.verbose) process.stderr.write(chunk);
3029
+ try {
3030
+ options.onOutput?.(stream, chunk.toString("utf8"));
3031
+ } catch {
3032
+ }
2998
3033
  };
2999
- child.stdout.on("data", (chunk) => append(stdout, chunk));
3000
- child.stderr.on("data", (chunk) => append(stderr, chunk));
3034
+ child.stdout.on("data", (chunk) => append(stdout, "stdout", chunk));
3035
+ child.stderr.on("data", (chunk) => append(stderr, "stderr", chunk));
3001
3036
  child.stdin.on("error", (error) => {
3002
3037
  if (settled) return;
3003
3038
  settled = true;
@@ -3019,8 +3054,13 @@ function runRemoteScript(target, script, options) {
3019
3054
  stderr: Buffer.concat(stderr).toString("utf8"),
3020
3055
  code: code ?? (signal === null ? 1 : 137)
3021
3056
  };
3022
- if (result.code === 0) resolve4(result);
3023
- else reject(Object.assign(new Error(`remote bootstrap exited with ${String(result.code)}`), { result }));
3057
+ if (result.code === 0) {
3058
+ resolve4(result);
3059
+ return;
3060
+ }
3061
+ const tail = result.stderr.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line !== "").slice(-2).join(" | ").slice(0, 500);
3062
+ const message = `remote bootstrap exited with ${String(result.code)}${tail === "" ? "" : `: ${tail}`}`;
3063
+ reject(Object.assign(new Error(message), { result }));
3024
3064
  });
3025
3065
  child.stdin.end(script);
3026
3066
  });
@@ -3031,7 +3071,9 @@ async function provisionRemoteAgent(options) {
3031
3071
  await access(options.agentTarballPath);
3032
3072
  const remoteTarball = `/tmp/dsh-remote-agent-${Date.now()}-${process.pid}.tgz`;
3033
3073
  const timeoutMs = Math.max(options.timeoutMs ?? 6e5, 6e5);
3074
+ options.onProgress?.("dsh-remote: uploading agent package");
3034
3075
  await uploadFile2(options.agentTarballPath, remoteTarball, target, options);
3076
+ options.onProgress?.("dsh-remote: agent package uploaded");
3035
3077
  const script = renderBootstrapScript({
3036
3078
  profile: options.profile,
3037
3079
  port: 0,
@@ -3044,13 +3086,38 @@ async function provisionRemoteAgent(options) {
3044
3086
  start: options.start,
3045
3087
  agentTarball: remoteTarball,
3046
3088
  ...options.modelBaseUrl === void 0 ? {} : { modelBaseUrl: options.modelBaseUrl },
3047
- ...options.modelApiKey === void 0 ? {} : { modelApiKey: options.modelApiKey }
3089
+ ...options.modelApiKey === void 0 ? {} : { modelApiKey: options.modelApiKey },
3090
+ ...options.proxyUrl === void 0 ? {} : { proxyUrl: options.proxyUrl },
3091
+ ...options.noProxy === void 0 ? {} : { noProxy: options.noProxy }
3048
3092
  });
3093
+ let stdoutBuffer = "";
3094
+ let stderrBuffer = "";
3095
+ const emitLines = (stream, chunk) => {
3096
+ const key2 = stream === "stdout" ? "stdout" : "stderr";
3097
+ let buffer = key2 === "stdout" ? stdoutBuffer : stderrBuffer;
3098
+ buffer += chunk;
3099
+ let newline = buffer.indexOf("\n");
3100
+ while (newline >= 0) {
3101
+ const line = buffer.slice(0, newline).trim();
3102
+ buffer = buffer.slice(newline + 1);
3103
+ if (line !== "") {
3104
+ try {
3105
+ options.onProgress?.(line);
3106
+ } catch {
3107
+ }
3108
+ }
3109
+ newline = buffer.indexOf("\n");
3110
+ }
3111
+ if (buffer.length > 8192) buffer = buffer.slice(-8192);
3112
+ if (key2 === "stdout") stdoutBuffer = buffer;
3113
+ else stderrBuffer = buffer;
3114
+ };
3049
3115
  const result = await runRemoteScript(target, script, {
3050
3116
  binary: options.sshBinary,
3051
3117
  strictHostKeyChecking: options.strictHostKeyChecking,
3052
3118
  timeoutMs,
3053
- verbose: options.verbose ?? false
3119
+ verbose: options.verbose ?? false,
3120
+ onOutput: emitLines
3054
3121
  });
3055
3122
  return { stdout: result.stdout, stderr: result.stderr, remoteTarball };
3056
3123
  }
@@ -3181,7 +3248,9 @@ var RemoteProvisioner = class {
3181
3248
  */
3182
3249
  async ensureInstalled(target, options = {}) {
3183
3250
  const tarball = findAgentTarball();
3184
- options.onProgress?.("\u6B63\u5728\u8FDC\u7AEF\u5B89\u88C5 DSH \u4E0E dsh-remote-agent\uFF08\u9996\u6B21\u7EA6 1\u20133 \u5206\u949F\uFF09\u2026");
3251
+ options.onProgress?.("\u6B63\u5728\u51C6\u5907\u8FDC\u7AEF\u5B89\u88C5\u73AF\u5883\u2026");
3252
+ const proxyUrl = this.#config.remoteInstallProxy.trim() || void 0;
3253
+ if (proxyUrl !== void 0) options.onProgress?.("\u6B63\u5728\u914D\u7F6E\u8FDC\u7AEF\u5B89\u88C5\u4EE3\u7406\u2026");
3185
3254
  try {
3186
3255
  await provisionRemoteAgent({
3187
3256
  target: target.canonical,
@@ -3199,7 +3268,15 @@ var RemoteProvisioner = class {
3199
3268
  timeoutMs: this.#config.provisionTimeoutMs,
3200
3269
  ...options.modelBaseUrl === void 0 ? {} : { modelBaseUrl: options.modelBaseUrl },
3201
3270
  ...options.modelApiKey === void 0 ? {} : { modelApiKey: options.modelApiKey },
3202
- verbose: false
3271
+ verbose: false,
3272
+ ...proxyUrl === void 0 ? {} : {
3273
+ proxyUrl,
3274
+ noProxy: this.#config.remoteInstallNoProxy
3275
+ },
3276
+ onProgress: (line) => {
3277
+ const message = progressForLine(line);
3278
+ if (message !== void 0) options.onProgress?.(message);
3279
+ }
3203
3280
  });
3204
3281
  } catch (error) {
3205
3282
  const detail = error instanceof Error ? error.message : String(error);
@@ -3209,12 +3286,13 @@ var RemoteProvisioner = class {
3209
3286
  cause: error
3210
3287
  });
3211
3288
  }
3212
- options.onProgress?.("\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\uFF0C\u7B49\u5F85 DSH web \u542F\u52A8\u2026");
3289
+ options.onProgress?.("\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\uFF0C\u6B63\u5728\u7B49\u5F85 DSH web \u542F\u52A8\u2026");
3213
3290
  await this.#waitForAgent(target, options.onProgress, options.signal);
3214
3291
  }
3215
3292
  async #waitForAgent(target, onProgress, signal) {
3216
3293
  const deadline = Date.now() + 12e4;
3217
3294
  let lastError;
3295
+ onProgress?.("\u6B63\u5728\u7B49\u5F85\u8FDC\u7AEF agent \u72B6\u6001\u2026");
3218
3296
  while (Date.now() < deadline) {
3219
3297
  if (signal?.aborted === true) throw new LocalError("internal", "\u8FDC\u7AEF\u5B89\u88C5\u5DF2\u53D6\u6D88");
3220
3298
  try {
@@ -3224,10 +3302,13 @@ var RemoteProvisioner = class {
3224
3302
  strictHostKeyChecking: this.#config.strictHostKeyChecking,
3225
3303
  remoteStateFile: this.#config.remoteStateFile
3226
3304
  });
3227
- if (isAgentLive(discovered.state)) return;
3305
+ if (isAgentLive(discovered.state)) {
3306
+ onProgress?.("\u8FDC\u7AEF agent \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u8FDE\u63A5\u2026");
3307
+ return;
3308
+ }
3228
3309
  } catch (error) {
3229
3310
  lastError = error;
3230
- onProgress?.("\u7B49\u5F85\u8FDC\u7AEF dsh web \u5C31\u7EEA\u2026");
3311
+ onProgress?.("\u4ECD\u5728\u7B49\u5F85\u8FDC\u7AEF dsh web \u5C31\u7EEA\u2026");
3231
3312
  }
3232
3313
  await new Promise((resolve4) => setTimeout(resolve4, 2e3));
3233
3314
  }
@@ -3237,6 +3318,31 @@ var RemoteProvisioner = class {
3237
3318
  });
3238
3319
  }
3239
3320
  };
3321
+ var PROVISION_PROGRESS = [
3322
+ ["uploading agent package", "\u6B63\u5728\u4E0A\u4F20\u8FDC\u7AEF agent \u5B89\u88C5\u5305\u2026"],
3323
+ ["agent package uploaded", "\u5B89\u88C5\u5305\u4E0A\u4F20\u5B8C\u6210\uFF0C\u5F00\u59CB\u8FDC\u7AEF\u5B89\u88C5\u2026"],
3324
+ ["preparing remote install", "\u6B63\u5728\u51C6\u5907\u8FDC\u7AEF\u5B89\u88C5\u73AF\u5883\u2026"],
3325
+ ["install proxy configured", "\u5DF2\u914D\u7F6E\u8FDC\u7AEF\u5B89\u88C5\u4EE3\u7406\u2026"],
3326
+ ["downloading node.js", "\u6B63\u5728\u4E0B\u8F7D Node.js\u2026"],
3327
+ ["installed node.js", "Node.js \u5B89\u88C5\u5B8C\u6210\uFF0C\u7EE7\u7EED\u5B89\u88C5 pnpm/DSH\u2026"],
3328
+ ["node ready", "Node.js \u5DF2\u5C31\u7EEA\uFF0C\u51C6\u5907\u5B89\u88C5 pnpm/DSH\u2026"],
3329
+ ["installing pnpm@", "\u6B63\u5728\u5B89\u88C5 pnpm\u2026"],
3330
+ ["installing @deepseek-ai/dsh@", "\u6B63\u5728\u5B89\u88C5\u8FDC\u7AEF DSH\uFF08\u4E0B\u8F7D\u4F9D\u8D56\u53EF\u80FD\u9700\u8981\u51E0\u5206\u949F\uFF09\u2026"],
3331
+ ["adding plugin to profile", "\u6B63\u5728\u5B89\u88C5\u8FDC\u7AEF agent\u2026"],
3332
+ ["restarting systemd user unit", "\u6B63\u5728\u542F\u52A8\u8FDC\u7AEF DSH Web \u670D\u52A1\u2026"],
3333
+ ["starting dsh web under nohup", "\u6B63\u5728\u542F\u52A8\u8FDC\u7AEF DSH Web\u2026"],
3334
+ ["starting dsh web", "\u6B63\u5728\u542F\u52A8\u8FDC\u7AEF DSH Web \u670D\u52A1\u2026"],
3335
+ ["waiting for agent state", "\u6B63\u5728\u7B49\u5F85\u8FDC\u7AEF agent \u72B6\u6001\u6587\u4EF6\u2026"],
3336
+ ["agent ready", "\u8FDC\u7AEF agent \u5DF2\u5C31\u7EEA\uFF0C\u6B63\u5728\u5EFA\u7ACB\u8FDE\u63A5\u2026"],
3337
+ ["installed", "\u8FDC\u7AEF\u5B89\u88C5\u5B8C\u6210\u2026"]
3338
+ ];
3339
+ function progressForLine(line) {
3340
+ const normalized = line.toLowerCase();
3341
+ for (const [needle, message] of PROVISION_PROGRESS) {
3342
+ if (normalized.includes(needle)) return message;
3343
+ }
3344
+ return void 0;
3345
+ }
3240
3346
  function findAgentTarball() {
3241
3347
  const candidates = [
3242
3348
  new URL("../assets", import.meta.url),
@@ -3703,7 +3809,7 @@ var RemoteHostsService = class {
3703
3809
  phase: "installing",
3704
3810
  modelProxy: proxy !== void 0,
3705
3811
  modelDirect: direct !== void 0,
3706
- detail: "\u6B63\u5728\u91CD\u65B0\u5B89\u88C5\u8FDC\u7AEF agent\u2026"
3812
+ detail: "\u6B63\u5728\u51C6\u5907\u8FDC\u7AEF\u5B89\u88C5\u73AF\u5883\u2026"
3707
3813
  });
3708
3814
  try {
3709
3815
  await this.#provisioner.ensureInstalled(target, {
@@ -3881,13 +3987,14 @@ var RemoteHostsService = class {
3881
3987
  this.#setRuntime(id, {
3882
3988
  ...this.#runtimeFor(id),
3883
3989
  phase: "installing",
3884
- detail: "\u6B63\u5728\u8FDC\u7AEF\u5B89\u88C5 DSH \u4E0E dsh-remote-agent\uFF0C\u9996\u6B21\u7EA6 1\u20133 \u5206\u949F\u2026"
3990
+ detail: "\u6B63\u5728\u51C6\u5907\u8FDC\u7AEF\u5B89\u88C5\u73AF\u5883\u2026"
3885
3991
  });
3886
3992
  try {
3887
3993
  await this.#provisioner.ensureInstalled(target, {
3888
3994
  ...this.#modelEnvironment(proxy, proxyToken, direct),
3889
3995
  onProgress: (detail) => this.#setRuntime(id, { ...this.#runtimeFor(id), detail })
3890
3996
  });
3997
+ this.#setRuntime(id, { ...this.#runtimeFor(id), detail: "\u6B63\u5728\u8BFB\u53D6\u8FDC\u7AEF agent \u72B6\u6001\u2026" });
3891
3998
  const discovery = await this.#discover(target);
3892
3999
  remotePort = discovery.state.port;
3893
4000
  token = discovery.state.token;
@@ -3924,6 +4031,7 @@ var RemoteHostsService = class {
3924
4031
  phase: "connecting",
3925
4032
  detail: "\u6B63\u5728\u5EFA\u7ACB SSH \u96A7\u9053\u5E76\u6821\u9A8C\u8FDC\u7AEF agent\u2026"
3926
4033
  });
4034
+ this.#setRuntime(id, { ...this.#runtimeFor(id), phase: "connecting", detail: "\u6B63\u5728\u5EFA\u7ACB SSH \u96A7\u9053\u2026" });
3927
4035
  let tunnel;
3928
4036
  try {
3929
4037
  tunnel = await Tunnel.start({
@@ -3940,6 +4048,7 @@ var RemoteHostsService = class {
3940
4048
  token,
3941
4049
  hostId: id
3942
4050
  });
4051
+ this.#setRuntime(id, { ...this.#runtimeFor(id), detail: "\u6B63\u5728\u6821\u9A8C\u8FDC\u7AEF agent\u2026" });
3943
4052
  const health = await client.health();
3944
4053
  if (await this.#reconcileModelEnvironment(id, target, health, proxy, proxyToken, direct, tunnel)) {
3945
4054
  return this.#doConnect(id);
@@ -3977,6 +4086,7 @@ var RemoteHostsService = class {
3977
4086
  return false;
3978
4087
  }
3979
4088
  this.#modelReconciled.add(id);
4089
+ this.#setRuntime(id, { ...this.#runtimeFor(id), detail: "\u6B63\u5728\u540C\u6B65\u8FDC\u7AEF\u6A21\u578B\u73AF\u5883\u2026" });
3980
4090
  this.ctx.logger.warn(
3981
4091
  "dsh-remote: remote model environment for %s is %s, expected %s; reinstalling",
3982
4092
  id,
@@ -27,6 +27,10 @@ export interface Config {
27
27
  readonly remotePnpmVersion?: string;
28
28
  /** Node.js version installed into the remote install dir when none is usable. */
29
29
  readonly remoteNodeVersion?: string;
30
+ /** Optional HTTP(S) proxy for remote provisioning; empty means auto-detect. */
31
+ readonly remoteInstallProxy?: string;
32
+ /** Comma-separated proxy bypass list. */
33
+ readonly remoteInstallNoProxy?: string;
30
34
  /** Download Node.js into the remote install dir when it is missing or too old. */
31
35
  readonly autoInstallNode?: boolean;
32
36
  /** `systemd`, `nohup`, or `none`. */
@@ -71,6 +75,8 @@ export interface ResolvedConfig {
71
75
  readonly remoteDshVersion: string;
72
76
  readonly remotePnpmVersion: string;
73
77
  readonly remoteNodeVersion: string;
78
+ readonly remoteInstallProxy: string;
79
+ readonly remoteInstallNoProxy: string;
74
80
  readonly autoInstallNode: boolean;
75
81
  readonly remoteStart: RemoteStartMode;
76
82
  readonly provisionTimeoutMs: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-local-plugin",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "DSH remote-development local plugin: host registry, SSH tunnels, remote workspace/session discovery, an authenticated Connection RPC surface, agent tools, and a Settings page.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -1,114 +0,0 @@
1
- /**
2
- * RPC contract shared by the host handler and the browser Settings page.
3
- *
4
- * The channel rides DSH's authenticated Connection transport, so it works
5
- * through the web HTTP carrier and the desktop IPC carrier without a second
6
- * authentication scheme.
7
- *
8
- * @module dsh-remote-local/rpc/contract
9
- */
10
- import type { HostView } from '../hosts/types.ts';
11
- import type { HostInfo, RemoteSessionSummary, RemoteWorkspace, SessionSnapshot } from '@dsh-remote/protocol';
12
- /** Absolute Connection RPC channel. */
13
- export declare const RPC_CHANNEL = "/dsh-remote";
14
- /** Endpoint names. */
15
- export type RpcEndpoint = 'state' | 'target/validate' | 'host/add' | 'host/remove' | 'host/connect' | 'host/disconnect' | 'host/refresh' | 'host/workspaces' | 'host/sessions' | 'host/session' | 'host/web-url';
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 WorkspacesResult {
30
- readonly workspaces: readonly RemoteWorkspace[];
31
- }
32
- /** Result of listing sessions. */
33
- export interface SessionsResult {
34
- readonly sessions: readonly RemoteSessionSummary[];
35
- }
36
- /** Result of resolving the remote Web UI. */
37
- export interface WebUrlResult {
38
- readonly url: string;
39
- }
40
- /** Success/failure result used by the Connection RPC handler. */
41
- export type RpcResult<T> = {
42
- readonly ok: true;
43
- readonly value: T;
44
- } | {
45
- readonly ok: false;
46
- readonly error: {
47
- readonly code: string;
48
- readonly message: string;
49
- readonly details: Record<string, unknown>;
50
- };
51
- };
52
- /** Payloads accepted by each endpoint. */
53
- export interface RpcPayloads {
54
- state: undefined;
55
- 'target/validate': {
56
- sshTarget: string;
57
- };
58
- 'host/add': {
59
- sshTarget: string;
60
- label?: string;
61
- token?: string;
62
- remoteAgentPort?: number;
63
- autoConnect?: boolean;
64
- };
65
- 'host/remove': {
66
- hostId: string;
67
- };
68
- 'host/connect': {
69
- hostId: string;
70
- };
71
- 'host/disconnect': {
72
- hostId: string;
73
- };
74
- 'host/refresh': {
75
- hostId: string;
76
- };
77
- 'host/workspaces': {
78
- hostId: string;
79
- };
80
- 'host/sessions': {
81
- hostId: string;
82
- workspaceId?: string;
83
- };
84
- 'host/session': {
85
- hostId: string;
86
- sessionId: string;
87
- limit?: number;
88
- };
89
- 'host/web-url': {
90
- hostId: string;
91
- };
92
- }
93
- /** Values returned by each endpoint. */
94
- export interface RpcValues {
95
- state: RemoteHostsState;
96
- 'target/validate': ValidatedTarget;
97
- 'host/add': HostView;
98
- 'host/remove': {
99
- removed: true;
100
- };
101
- 'host/connect': HostView;
102
- 'host/disconnect': HostView;
103
- 'host/refresh': HostView;
104
- 'host/workspaces': WorkspacesResult;
105
- 'host/sessions': SessionsResult;
106
- 'host/session': SessionSnapshot;
107
- 'host/web-url': WebUrlResult;
108
- }
109
- /** Host capabilities advertised in the UI. */
110
- export declare const HOST_CAPABILITIES: readonly ["workspace.list", "session.list", "session.snapshot"];
111
- /** Informational type for the Settings page. */
112
- export interface HostSummary extends Pick<HostView, 'id' | 'label' | 'sshTarget' | 'phase'> {
113
- readonly hostInfo: HostInfo | null;
114
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Host-side RPC dispatch for the Settings page.
3
- *
4
- * @module dsh-remote-local/rpc/handler
5
- */
6
- import type { Context } from '@deepseek-ai/cordis';
7
- import type { RemoteHostsService } from '../service.ts';
8
- import { type RpcEndpoint, type RpcResult, type RpcValues } from './contract.ts';
9
- /** Register the channel; returns its asynchronous disposer. */
10
- export declare function registerHostRpc(ctx: Context, service: RemoteHostsService): () => Promise<void>;
11
- /** Narrow a result for callers that want a compiler-checked endpoint name. */
12
- export type HostRpcResult<K extends RpcEndpoint> = RpcResult<RpcValues[K]>;
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/client/index.tsx"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { ClientTerminals } from '@deepseek-ai/dsh-api-terminal-controller/client'\n\nexport const inject = ['remote', 'remote.terminal', 'connection']\nconst ROUTE = '/api/dsh-remote-terminal'\n\ninterface Handle extends AsyncIterable<unknown> {\n send(item: unknown): void\n end(): void\n dispose(): void\n}\n\nexport function apply(ctx: Context): void {\n const original = (ctx.remote as any).terminal\n const routed = route(original)\n new ClientTerminals(ctx as any, routed)\n}\n\nfunction route(original: any): any {\n const mirrored = new Map<string, Promise<boolean>>()\n const isMirror = (sessionId: string): Promise<boolean> => {\n let pending = mirrored.get(sessionId)\n if (pending === undefined) {\n pending = post({ action: 'context', sessionId })\n .then(value => (value as { mirrored?: boolean } | undefined)?.mirrored === true)\n .catch(() => false)\n mirrored.set(sessionId, pending)\n }\n return pending\n }\n const unary = (name: string) => async (...args: any[]) => {\n const sessionId = String(args[0])\n if (!await isMirror(sessionId)) return original[name](...args)\n const value = await post({ action: name, sessionId, ...payload(name, args) })\n return value === undefined ? original[name](...args) : { ok: true, value }\n }\n return {\n ...original,\n environment: unary('environment'),\n shells: unary('shells'),\n list: unary('list'),\n create: unary('create'),\n rename: unary('rename'),\n close: unary('close'),\n retain: (sessionId: string, id: string, signal: AbortSignal) => {\n const local = original.retain(sessionId, id, signal)\n return (async function* () {\n if (await isMirror(sessionId)) {\n yield { type: 'retained' }\n return\n }\n yield* local\n })()\n },\n attach: (sessionId: string, id: string, signal: AbortSignal) => new RoutedHandle(original, sessionId, id, signal, isMirror),\n }\n}\n\nfunction payload(name: string, args: any[]): Record<string, unknown> {\n if (name === 'create') {\n const request = args[1] ?? {}\n return { id: request.id, cols: request.cols, rows: request.rows, shellPath: request.shellPath }\n }\n if (name === 'rename') return { id: args[1], title: args[2] }\n if (name === 'close') return { id: args[1] }\n return {}\n}\n\nasync function post(payload: Record<string, unknown>): Promise<any> {\n const response = await fetch(ROUTE, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!response.ok) return undefined\n const body = await response.json() as { ok?: boolean; value?: unknown; mirrored?: boolean }\n if (body.mirrored !== undefined) return body\n return body.ok === true ? body.value : undefined\n}\n\nclass RoutedHandle implements Handle {\n #original: any\n #sessionId: string\n #id: string\n #signal: AbortSignal\n #isMirror: (sessionId: string) => Promise<boolean>\n #handle: Handle | undefined\n #pending: unknown[] = []\n #controller = new AbortController()\n #opening: Promise<Handle> | undefined\n\n constructor(original: any, sessionId: string, id: string, signal: AbortSignal, isMirror: (sessionId: string) => Promise<boolean>) {\n this.#original = original\n this.#sessionId = sessionId\n this.#id = id\n this.#signal = signal\n this.#isMirror = isMirror\n signal.addEventListener('abort', () => this.#controller.abort(), { once: true })\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<unknown> {\n const handle = await this.#open()\n yield* handle\n }\n\n send(item: unknown): void {\n if (this.#handle !== undefined) this.#handle.send(item)\n else this.#pending.push(item)\n }\n\n end(): void { this.#handle?.end() }\n\n dispose(): void {\n this.#controller.abort()\n this.#handle?.dispose()\n }\n\n async #open(): Promise<Handle> {\n if (this.#handle !== undefined) return this.#handle\n this.#opening ??= (async () => {\n const remote = await this.#isMirror(this.#sessionId)\n const handle: Handle = remote\n ? remoteHandle(this.#sessionId, this.#id, this.#controller.signal)\n : this.#original.attach(this.#sessionId, this.#id, this.#signal)\n this.#handle = handle\n for (const item of this.#pending.splice(0)) handle.send(item)\n return handle\n })()\n return this.#opening\n }\n}\n\nfunction remoteHandle(sessionId: string, id: string, signal: AbortSignal): Handle {\n let controller = new AbortController()\n const abort = (): void => controller.abort()\n signal.addEventListener('abort', abort, { once: true })\n return {\n async *[Symbol.asyncIterator]() {\n const response = await fetch(ROUTE, {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'content-type': 'application/json', accept: 'text/event-stream' },\n body: JSON.stringify({ action: 'attach', sessionId, id }),\n signal: controller.signal,\n })\n if (!response.ok || response.body === null) throw new Error('remote terminal attach failed')\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n try {\n for (;;) {\n const { done, value } = await reader.read()\n buffer += decoder.decode(value, { stream: !done })\n let newline = buffer.indexOf('\\n')\n while (newline >= 0) {\n const line = buffer.slice(0, newline).replace(/\\r$/u, '')\n buffer = buffer.slice(newline + 1)\n if (line.startsWith('data: ')) {\n const parsed = JSON.parse(line.slice(6).trim())\n if (parsed.message !== undefined && parsed.type === undefined) throw new Error(parsed.message)\n yield parsed\n }\n newline = buffer.indexOf('\\n')\n }\n if (done) return\n }\n } finally {\n reader.releaseLock()\n }\n },\n send(item: unknown) {\n const input = item as { type?: string; data?: string; cols?: number; rows?: number; sequence?: number }\n void post(input.type === 'write'\n ? { action: 'input', sessionId, id, data: input.data ?? '', sequence: input.sequence ?? 0 }\n : { action: 'resize', sessionId, id, cols: input.cols ?? 80, rows: input.rows ?? 24, sequence: input.sequence ?? 0 })\n },\n end() {},\n dispose() { controller.abort() },\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["payload"]
7
- }