@deepseek-ai/dsh-web-app 0.2.1-alpha.1 → 0.2.1-alpha.3

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
@@ -2,11 +2,10 @@ import { t as parsePublicUrl } from "./public-url-D_dMK-yI.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { spawn } from "node:child_process";
4
4
  import { dirname, join } from "node:path";
5
- import { networkInterfaces } from "node:os";
6
- import { fileURLToPath } from "node:url";
7
5
  import z from "@deepseek-ai/schemastery";
8
- import { addHarnessSourceSection, auditStartupEntries } from "@deepseek-ai/dsh-app-boot";
6
+ import { auditStartupEntries } from "@deepseek-ai/dsh-app-boot";
9
7
  import * as FrontendStatic from "@deepseek-ai/dsh-host-frontend-static";
8
+ import { bindUrlHost, isLoopbackHost } from "@deepseek-ai/dsh-host-webserver";
10
9
  import { launchEnvironmentOf, launchedThroughSsh } from "@deepseek-ai/dsh-launch-environment";
11
10
  import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess";
12
11
  //#region lib/types/index.js
@@ -16,34 +15,26 @@ import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess";
16
15
  * manifest field). The plugin owns the browser-surface glue: it resolves
17
16
  * the built frontend dist (workspace knowledge of this bundle, never user
18
17
  * config), mounts the `frontend-static` fallback owner over it, registers the
19
- * harness-source and web-surface prompt sections, the bash-visible web runtime
20
- * variable, the process-token URL line, and the default-browser handoff. An
21
- * advertised `publicUrl` replaces the published root — the loopback URL
22
- * otherwise. App command-line values arrive through the `webStartup` service
23
- * expressions in the bundle patch.
18
+ * bash-visible web runtime variable, the process-token URL line, and the
19
+ * default-browser handoff, and provides the `webApp` service. An advertised
20
+ * `publicUrl` replaces the published bind-address URL. Non-loopback
21
+ * HTTP binds trigger an exposure warning. App command-line values arrive
22
+ * through the `webStartup` service expressions in the bundle patch.
24
23
  * @module @deepseek-ai/dsh-web-app
25
24
  */
26
25
  /** Stable Cordis plugin name. */
27
26
  const name = "web-app";
28
- /** This dsh installation's root, from either this package's source or built entry. */
29
- const SOURCE_ROOT = fileURLToPath(new URL("../../../..", import.meta.url));
30
27
  const ANNOUNCED_ROOTS = /* @__PURE__ */ new WeakSet();
31
- /** Runtime service that releases Web rows after bind-dependent values resolve. */
32
- const WEB_RUNTIME_SERVICE = "webRuntime";
33
28
  /** Services required before the web runtime can mount. */
34
29
  const inject = ["webServer"];
35
30
  const Config = z.object({
36
31
  openBrowser: z.boolean().default(true),
37
32
  printUrl: z.boolean().default(true),
38
33
  surfaceContext: z.boolean().default(true),
39
- publicUrl: z.transform(z.string(), (value) => parsePublicUrl(value).href),
40
- trustedHosts: z.array(String).default([])
34
+ publicUrl: z.transform(z.string(), (value) => parsePublicUrl(value).href)
41
35
  });
42
36
  /** Environment variable naming the advertised URL of this Web GUI. */
43
37
  const DSH_WEB_URL = "DSH_WEB_URL";
44
- const LOOPBACK_HOST = "127.0.0.1";
45
- /** The webserver schema's all-interfaces bind literal. */
46
- const ALL_INTERFACES_HOST = "0.0.0.0";
47
38
  const BROWSER_OPENER_MODULE = import.meta.resolve("open");
48
39
  const BROWSER_OPENER_PROGRAM = `
49
40
  try {
@@ -74,37 +65,6 @@ try {
74
65
  }
75
66
  `;
76
67
  /**
77
- * Resolve one LAN-trust snapshot from the active server bind.
78
- *
79
- * Derived entries are port-less IP literals: DNS rebinding needs an
80
- * attacker-controlled name, while an IP-literal Host is safe on any port and
81
- * an OS-assigned port is unknowable before bind.
82
- * @param bindHost - the active webserver bind host.
83
- * @param extra - explicit `--trusted-host` values, in argument order.
84
- * @returns the LAN display addresses and invocation-derived fence authorities.
85
- */
86
- function resolveLanTrust(bindHost, extra) {
87
- const lanAddresses = bindHost === ALL_INTERFACES_HOST ? Object.values(networkInterfaces()).flat().filter((iface) => iface !== void 0 && iface.family === "IPv4" && !iface.internal).map((iface) => iface.address) : [];
88
- return {
89
- lanAddresses,
90
- trustedHosts: [...lanAddresses, ...extra]
91
- };
92
- }
93
- /** Model-visible orientation and acceptance boundary for sessions created through `dsh web`. */
94
- function webSurfacePrompt(webUrl) {
95
- return `You are interacting with the user through the DeepSeek Harness Web GUI at ${webUrl}. When the user refers to "this page", "this GUI", or "this app" without naming another target, they mean this GUI. The browser provides no implicit DOM, route, or screenshot context. The client-plugin HMR receiver is active, but client-plugin changes reload without a refresh only while \`pnpm run dev:web\` is also running from this same checkout to rebuild their bundles; verify that watcher before promising automatic updates. Every other change — the apps/web shell and plain packages — requires rebuilding the affected Web artifacts and verifying this existing URL after a page refresh. Starting another server does not update this GUI. The apps/web Vite entry builds the shell but is not a standalone application because only dsh web injects window.__DSH_BOOT__. Do not start a replacement server unless the user asks; if one is needed, use a managed background job and verify its exact URL.`;
96
- }
97
- /** Resolve the canonical loopback URL from the active Web server. */
98
- function localWebUrl(ctx) {
99
- const port = ctx.get("webServer")?.port;
100
- if (port === void 0) throw new Error("web-app: webServer service missing while resolving Web runtime");
101
- return `http://${LOOPBACK_HOST}:${String(port)}`;
102
- }
103
- function appRootUrl(ctx, publicUrl) {
104
- if (publicUrl !== void 0) return publicUrl;
105
- return localWebUrl(ctx);
106
- }
107
- /**
108
68
  * Dist location is workspace knowledge of this bundle: anchored on the
109
69
  * frontend package manifest, not configured. Existence is a request-time
110
70
  * concern — the fallback owner reads files per request, so a composition
@@ -171,44 +131,35 @@ const internals = {
171
131
  openBrowser
172
132
  };
173
133
  /**
174
- * Mount the Web runtime: dist serving, surface prompt, the bash runtime
175
- * variable, the URL line, and the default-browser handoff.
134
+ * Mount the Web runtime: dist serving, the bash runtime variable, the URL
135
+ * line, and the default-browser handoff.
176
136
  * @param ctx - plugin context carrying the webServer service.
177
137
  * @param config - validated {@link Config}.
178
138
  */
179
139
  function apply(ctx, config) {
180
- const runtime = resolveLanTrust(ctx.webServer.host, config.trustedHosts);
181
140
  const publicUrl = config.publicUrl ?? void 0;
141
+ const boundHost = ctx.webServer.host;
142
+ if (publicUrl === void 0 && bindUrlHost(boundHost) === void 0) throw new Error(`web-app: bind address ${JSON.stringify(boundHost)} carries an interface zone id, which no URL can express; pass --public-url with the root browsers actually reach it through`);
143
+ if (ctx.webServer.protocol === "http:" && !isLoopbackHost(boundHost)) console.warn(`dsh web: listening on ${boundHost} over plain HTTP; restrict this port to a trusted proxy or network`);
182
144
  const handoffBrowser = config.openBrowser && !launchedThroughSsh(launchEnvironmentOf(ctx));
183
- ctx.provide(WEB_RUNTIME_SERVICE, runtime);
145
+ /** The advertised root: the canonical `publicUrl`, or the listener's own origin. */
146
+ const appRootUrl = () => publicUrl ?? ctx.webServer.getOrigin();
147
+ ctx.provide("webApp", { getAdvertisedUrl: appRootUrl });
184
148
  ctx.plugin(FrontendStatic, { distIndex: internals.resolveDistIndex() });
185
- if (config.surfaceContext) {
186
- ctx.inject(["systemPrompt"], (promptCtx) => {
187
- addHarnessSourceSection(promptCtx, SOURCE_ROOT);
188
- promptCtx.systemPrompt.section({
189
- name: "app:web-surface",
190
- order: promptCtx.systemPrompt.getSectionOrder("WEB_SURFACE"),
191
- text: () => webSurfacePrompt(appRootUrl(promptCtx, publicUrl))
192
- });
193
- });
194
- ctx.inject(["shellEnv"], (runtimeCtx) => {
195
- runtimeCtx.shellEnv.register({
196
- name: "web-runtime",
197
- variables: { [DSH_WEB_URL]: { description: "Advertised URL of the DeepSeek Harness Web GUI serving this session." } },
198
- resolve: () => ({ [DSH_WEB_URL]: appRootUrl(runtimeCtx, publicUrl) })
199
- });
149
+ if (config.surfaceContext) ctx.inject(["shellEnv"], (runtimeCtx) => {
150
+ runtimeCtx.shellEnv.register({
151
+ name: "web-runtime",
152
+ variables: { [DSH_WEB_URL]: { description: "Advertised URL of the DeepSeek Harness Web GUI serving this session." } },
153
+ resolve: () => ({ [DSH_WEB_URL]: appRootUrl() })
200
154
  });
201
- }
155
+ });
202
156
  if (config.printUrl || handoffBrowser) ctx.inject(["connection"], (connectionCtx) => {
203
157
  const announceReady = () => {
204
158
  if (ANNOUNCED_ROOTS.has(connectionCtx.root)) return;
205
- const webUrl = appRootUrl(connectionCtx, publicUrl);
159
+ const webUrl = appRootUrl();
206
160
  const authenticatedUrl = connectionCtx.connection.authenticatedUrl(webUrl);
207
- const lanCandidate = runtime.lanAddresses[0];
208
- const port = connectionCtx.webServer.port;
209
- const lanUrl = lanCandidate === void 0 ? void 0 : connectionCtx.connection.authenticatedUrl(`http://${lanCandidate}:${String(port)}`);
210
161
  ANNOUNCED_ROOTS.add(connectionCtx.root);
211
- if (config.printUrl) console.log(`dsh web: ${authenticatedUrl}${lanUrl === void 0 ? "" : ` (LAN: ${lanUrl})`}`);
162
+ if (config.printUrl) console.log(`dsh web: ${authenticatedUrl}`);
212
163
  if (handoffBrowser) {
213
164
  console.log("dsh web: opening the default browser; pass --no-open to disable");
214
165
  internals.openBrowser(authenticatedUrl).catch((error) => {
@@ -226,4 +177,4 @@ function apply(ctx, config) {
226
177
  });
227
178
  }
228
179
  //#endregion
229
- export { Config, apply, inject, internals, name, resolveLanTrust };
180
+ export { Config, apply, inject, internals, name };
package/lib/startup.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { t as parsePublicUrl } from "./public-url-D_dMK-yI.js";
2
+ import { isWildcardHost } from "@deepseek-ai/dsh-host-webserver";
2
3
  import { Command } from "commander";
3
4
  import { parseCmdline } from "@deepseek-ai/dsh-cmdline";
4
5
  //#region lib/types/startup.js
5
6
  /**
6
7
  * The web app's command-line provider: it parses the `dsh --profile web` flag
7
- * family (`--host`, `--port`, `--public-url`, `--trusted-host`, `--no-open`)
8
+ * family (`--host`, `--port`, `--public-url`, `--trusted-host`, `--no-open`, TLS)
8
9
  * and its `--help` text, then provides the immutable values as
9
10
  * {@link WEB_STARTUP_SERVICE}. Ordinary rows inject that service before
10
11
  * reading it from lazy config.
@@ -21,28 +22,37 @@ const WEB_STARTUP_SERVICE = "webStartup";
21
22
  * @returns a fresh program, so one process can parse more than once (tests).
22
23
  */
23
24
  function webCommand() {
24
- return new Command().name("dsh --profile web").description("Serve the DeepSeek Harness browser UI.").helpOption("-h, --help", "show this help").option("--host <host>", "bind host").option("--no-open", "do not open the Web UI in the default browser").option("--port <port>", "listen port; pass 0 to let the OS pick a free one").option("--public-url <url>", "advertise this HTTP(S) root in the printed, opened, web-surface, and DSH_WEB_URL forms; grants no trust").option("--trusted-host <authority...>", "extra authority the /api browser-trust fence accepts (host or host:port; repeatable)").addHelpText("after", `
25
+ return new Command().name("dsh --profile web").description("Serve the DeepSeek Harness browser UI.").helpOption("-h, --help", "show this help").option("--host <host>", "bind address: one concrete IPv4 or IPv6 literal of a local interface; wildcard addresses are rejected").option("--no-open", "do not open the Web UI in the default browser").option("--port <port>", "listen port; pass 0 to let the OS pick a free one").option("--public-url <url>", "advertise this HTTP(S) root in the printed, opened, runtime-context, and DSH_WEB_URL forms; grants no trust").option("--trusted-host <authority...>", "extra authority the /api browser-trust fence accepts (host or host:port; repeatable)").option("--tls-cert <file>", "PEM server certificate chain; requires --tls-key").option("--tls-key <file>", "unencrypted PEM private key; requires --tls-cert").addHelpText("after", `
25
26
  Examples:
26
27
  dsh --profile web serve on the composed host and port
27
28
  dsh --profile web --no-open serve without opening a browser
28
29
  dsh --profile web --port 8080 serve on another port
30
+ dsh --profile web --host 10.0.0.7 bind one local interface address
29
31
  dsh --profile web --public-url https://app.example/ui/ --trusted-host app.example
30
32
  advertise a prefix-stripping HTTPS proxy entry and admit its authority
33
+ dsh --profile web --tls-cert ./server-chain.pem --tls-key ./server-key.pem
34
+ serve HTTPS with an existing certificate and key
31
35
  `);
32
36
  }
33
37
  /**
34
38
  * Parse and provide the Web invocation as an ordinary Cordis service. The
35
- * command's action publishes the flags this invocation named; `--host 0.0.0.0`,
36
- * a non-numeric `--port`, or a malformed `--public-url` is a usage error, so on
37
- * rejection (and on `--help`) nothing is provided.
39
+ * command's action publishes the flags this invocation named. Invalid flags,
40
+ * including an unpaired TLS certificate or key, are usage errors; rejection
41
+ * and `--help` provide no service.
38
42
  * @param ctx - plugin context carrying the command line.
39
43
  */
40
44
  function apply(ctx) {
41
45
  const program = webCommand();
42
46
  program.action(() => {
43
47
  const options = program.opts();
44
- if (options.host === "0.0.0.0") program.error("error: --host 0.0.0.0 is intentionally not supported yet for safety: it would expose remote code execution to the network; use 127.0.0.1 instead");
48
+ if (options.host !== void 0 && isWildcardHost(options.host)) program.error(`error: --host ${options.host} is an unspecified (wildcard) address, which is not supported: binding every interface would expose remote code execution to the network; bind one concrete IPv4 or IPv6 address instead`);
45
49
  if (options.port !== void 0 && !/^\d+$/.test(options.port)) program.error(`error: --port must be a number, got ${JSON.stringify(options.port)}`);
50
+ let tls;
51
+ if (options.tlsCert !== void 0 && options.tlsKey !== void 0) tls = {
52
+ certFile: options.tlsCert,
53
+ keyFile: options.tlsKey
54
+ };
55
+ else if (options.tlsCert !== void 0 || options.tlsKey !== void 0) program.error("error: --tls-cert and --tls-key must be supplied together");
46
56
  if (options.publicUrl !== void 0) try {
47
57
  parsePublicUrl(options.publicUrl, "--public-url");
48
58
  } catch (error) {
@@ -53,6 +63,7 @@ function apply(ctx) {
53
63
  ...options.host !== void 0 && { host: options.host },
54
64
  ...options.port !== void 0 && { port: Number(options.port) },
55
65
  ...options.publicUrl !== void 0 && { publicUrl: options.publicUrl },
66
+ ...tls !== void 0 && { tls },
56
67
  trustedHosts: options.trustedHost ?? []
57
68
  });
58
69
  });
@@ -4,15 +4,29 @@
4
4
  * manifest field). The plugin owns the browser-surface glue: it resolves
5
5
  * the built frontend dist (workspace knowledge of this bundle, never user
6
6
  * config), mounts the `frontend-static` fallback owner over it, registers the
7
- * harness-source and web-surface prompt sections, the bash-visible web runtime
8
- * variable, the process-token URL line, and the default-browser handoff. An
9
- * advertised `publicUrl` replaces the published root — the loopback URL
10
- * otherwise. App command-line values arrive through the `webStartup` service
11
- * expressions in the bundle patch.
7
+ * bash-visible web runtime variable, the process-token URL line, and the
8
+ * default-browser handoff, and provides the `webApp` service. An advertised
9
+ * `publicUrl` replaces the published bind-address URL. Non-loopback
10
+ * HTTP binds trigger an exposure warning. App command-line values arrive
11
+ * through the `webStartup` service expressions in the bundle patch.
12
12
  * @module @deepseek-ai/dsh-web-app
13
13
  */
14
14
  import type { Context } from '@deepseek-ai/cordis';
15
15
  import z from '@deepseek-ai/schemastery';
16
+ /** The Web GUI this process serves, provided by the `web-runtime` row. */
17
+ export interface WebApp {
18
+ /**
19
+ * The URL this GUI advertises, also used for `DSH_WEB_URL` and the startup URL line.
20
+ * @returns the configured `publicUrl`, otherwise the listener origin.
21
+ */
22
+ getAdvertisedUrl(): string;
23
+ }
24
+ declare module '@deepseek-ai/cordis' {
25
+ interface Context {
26
+ /** The Web GUI this process serves; absent when no Web runtime is mounted. */
27
+ webApp: WebApp;
28
+ }
29
+ }
16
30
  /** Stable Cordis plugin name. */
17
31
  export declare const name = "web-app";
18
32
  /** Services required before the web runtime can mount. */
@@ -24,50 +38,28 @@ export interface Config {
24
38
  /** Print the URL line on activation; a non-interactive layer can turn it off. */
25
39
  printUrl: boolean;
26
40
  /**
27
- * Register the model-visible surface context (the `app:web-surface` prompt
28
- * section and the `DSH_WEB_URL` bash variable). A one-shot non-interactive
29
- * layer can turn it off when its user is not in the GUI, so the
30
- * orientation text would be false.
41
+ * Register the `DSH_WEB_URL` bash variable naming this GUI. A one-shot
42
+ * non-interactive layer can turn it off when its user is not in the GUI.
31
43
  */
32
44
  surfaceContext: boolean;
33
45
  /**
34
46
  * Canonical HTTP(S) root to advertise in the printed and opened URL,
35
- * `DSH_WEB_URL`, and the web-surface orientation, e.g.
36
- * `https://app.example/ui/`, normalized to end in `/`. Advertisement only;
37
- * see [public deployments](../README.md#public-deployments). Absent or YAML
38
- * `null` advertises the loopback URL.
47
+ * `DSH_WEB_URL`, and `webApp.getAdvertisedUrl()`, which supplies the Web GUI
48
+ * URL in agent runtime context, e.g. `https://app.example/ui/`, normalized to end in `/`. Configures no routing
49
+ * or authentication; see [public deployments](../README.md#public-deployments).
50
+ * Absent or YAML `null` advertises the bind-address URL.
39
51
  */
40
52
  publicUrl?: string;
41
- /** Explicit `--trusted-host` authorities from this invocation. */
42
- trustedHosts: string[];
43
53
  }
44
54
  export declare const Config: z<Config>;
45
- /** Bind-dependent Web values shared by the trust fence and URL display. */
46
- export interface WebRuntimeValues {
47
- /** LAN IPv4 literals sampled once when the server binds all interfaces. */
48
- lanAddresses: string[];
49
- /** LAN literals followed by explicit invocation authorities. */
50
- trustedHosts: string[];
51
- }
52
- /**
53
- * Resolve one LAN-trust snapshot from the active server bind.
54
- *
55
- * Derived entries are port-less IP literals: DNS rebinding needs an
56
- * attacker-controlled name, while an IP-literal Host is safe on any port and
57
- * an OS-assigned port is unknowable before bind.
58
- * @param bindHost - the active webserver bind host.
59
- * @param extra - explicit `--trusted-host` values, in argument order.
60
- * @returns the LAN display addresses and invocation-derived fence authorities.
61
- */
62
- export declare function resolveLanTrust(bindHost: string, extra: readonly string[]): WebRuntimeValues;
63
55
  /** Test hooks for the built dist and native browser handoff; production never mutates them. */
64
56
  export declare const internals: {
65
57
  resolveDistIndex: () => string;
66
58
  openBrowser: (url: string) => Promise<void>;
67
59
  };
68
60
  /**
69
- * Mount the Web runtime: dist serving, surface prompt, the bash runtime
70
- * variable, the URL line, and the default-browser handoff.
61
+ * Mount the Web runtime: dist serving, the bash runtime variable, the URL
62
+ * line, and the default-browser handoff.
71
63
  * @param ctx - plugin context carrying the webServer service.
72
64
  * @param config - validated {@link Config}.
73
65
  */
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * The web app's command-line provider: it parses the `dsh --profile web` flag
3
- * family (`--host`, `--port`, `--public-url`, `--trusted-host`, `--no-open`)
3
+ * family (`--host`, `--port`, `--public-url`, `--trusted-host`, `--no-open`, TLS)
4
4
  * and its `--help` text, then provides the immutable values as
5
5
  * {@link WEB_STARTUP_SERVICE}. Ordinary rows inject that service before
6
6
  * reading it from lazy config.
7
7
  * @module @deepseek-ai/dsh-web-app/startup
8
8
  */
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
+ import { type TlsConfig } from '@deepseek-ai/dsh-host-webserver';
10
11
  /** Stable Cordis plugin name. */
11
12
  export declare const name = "web-startup";
12
13
  /** Services required before the flags can be resolved. */
@@ -28,12 +29,14 @@ export interface WebStartupValues {
28
29
  publicUrl?: string;
29
30
  /** Explicit `--trusted-host` authorities, in argument order. */
30
31
  trustedHosts: string[];
32
+ /** Server certificate chain and unencrypted private key supplied together. */
33
+ tls?: TlsConfig;
31
34
  }
32
35
  /**
33
36
  * Parse and provide the Web invocation as an ordinary Cordis service. The
34
- * command's action publishes the flags this invocation named; `--host 0.0.0.0`,
35
- * a non-numeric `--port`, or a malformed `--public-url` is a usage error, so on
36
- * rejection (and on `--help`) nothing is provided.
37
+ * command's action publishes the flags this invocation named. Invalid flags,
38
+ * including an unpaired TLS certificate or key, are usage errors; rejection
39
+ * and `--help` provide no service.
37
40
  * @param ctx - plugin context carrying the command line.
38
41
  */
39
42
  export declare function apply(ctx: Context): void;
package/locale/en.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "meta": {
3
+ "title": "web-app"
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "meta": {
3
+ "title": "web-app/startup"
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "meta": {
3
+ "title": "Web 启动配置"
4
+ }
5
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Web 应用运行支持",
4
+ "description": "提供前端页面资源,并连接 Web 应用所需的运行配置。"
5
+ }
6
+ }