mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
fix(web): address review round on harness dev context
- WebServer.port reads number | undefined instead of masking the unbound listener with an assertion; tests that dial it narrow the bound port. - LegacyPrompt stays module-private; the web-app type import is a devDependency only, since the emitted declarations never reference it. - Copy: --public-url help names runtime context again, the public deployments guide, web-app JSDoc, Config.publicUrl consumers, the Agent Note matching rule, and three Known Limitations in the package README.
This commit is contained in:
1 parent
ba1a9356db
commit
2b229d4c0e
23 files changed
+79
-62
No files matched your search
+6
-6
@@ -12,20 +12,20 @@
|
||||
en: 5222f00fd05d3801
|
||||
zh: fcfa430a0fa5588c
|
||||
/agent-note-process-specific-checkout-path-and-web-url-move-to-runtime-context/decision/ownership:
|
||||
en: 102fd34719ddac0c
|
||||
zh: 2d10130fe05b3ed6
|
||||
en: c23d5c27f1f160f0
|
||||
zh: 5c2464666f1e6c29
|
||||
/agent-note-process-specific-checkout-path-and-web-url-move-to-runtime-context/decision/new-sessions:
|
||||
en: 1ee144c39905bb89
|
||||
zh: a6669d7ad394e41c
|
||||
/agent-note-process-specific-checkout-path-and-web-url-move-to-runtime-context/decision/earlier-format-sessions:
|
||||
en: ee29d4cfa8175116
|
||||
zh: 65cebbfe07afe81c
|
||||
en: 27c11e7ced286898
|
||||
zh: 944d051494b1498d
|
||||
/agent-note-process-specific-checkout-path-and-web-url-move-to-runtime-context/alternatives-considered:
|
||||
en: c324412be4034c40
|
||||
zh: 923ad612fc884be1
|
||||
/agent-note-process-specific-checkout-path-and-web-url-move-to-runtime-context/consequences:
|
||||
en: dc4c15a88ac09de0
|
||||
zh: 0d65b4b3f2471d5c
|
||||
en: 8aa41ff08b848916
|
||||
zh: bc1c1647ca0eb86a
|
||||
/agent-note-process-specific-checkout-path-and-web-url-move-to-runtime-context/testing:
|
||||
en: 9667d1749bdf1547
|
||||
zh: 598dc39120c91228
|
||||
+3
-3
@@ -24,7 +24,7 @@ Both values become runtime context and the system prompt keeps only value-free g
|
||||
|
||||
| Package | Responsibility |
|
||||
|---|---|
|
||||
| `@deepseek-ai/dsh-harness-dev-context` (`packages/context/harness-dev-context`) | Both sections, both contexts, and the earlier-format projection; all module-private, exporting only the plugin protocol (`name`, `inject`, `apply`) and the projection state type `LegacyPrompt` |
|
||||
| `@deepseek-ai/dsh-harness-dev-context` (`packages/context/harness-dev-context`) | Both sections, both contexts, and the earlier-format projection; all module-private, exporting only the plugin protocol (`name`, `inject`, `apply`) |
|
||||
| `@deepseek-ai/dsh-host-webserver` | `WebServer.getOrigin()` (the bind address's URL form with protocol and bound port) and `bindUrlHost(host)` |
|
||||
| `@deepseek-ai/dsh-web-app` | The `web-runtime` row provides `webApp`, whose `getAdvertisedUrl()` returns the configured `publicUrl`, otherwise the listener origin, the same value as `DSH_WEB_URL` and the startup URL; the bundle patch mounts the `harness-dev-context` row |
|
||||
| `@deepseek-ai/dsh-system-prompt` | `CONTEXT_ORDERS.HARNESS_SOURCE = 200` and `WEB_SURFACE = 210` |
|
||||
@@ -49,7 +49,7 @@ The plugin registers the host-only session projection `harnessDevLegacyPrompt`,
|
||||
| Earlier template | Matching rule |
|
||||
|---|---|
|
||||
| `harness:source` | Starts with `The DeepSeek Harness implementation checkout is at `, ends with the earlier fixed guidance, no line break between |
|
||||
| `app:web-surface` | Starts with `You are interacting with the user through the DeepSeek Harness Web GUI at `, ends with `. ` plus the current fixed guidance, no line break between |
|
||||
| `app:web-surface` | Starts with `You are interacting with the user through the DeepSeek Harness Web GUI at `, ends with `. ` plus the guidance as earlier releases rendered it, frozen as a literal, no line break between |
|
||||
|
||||
Both section text providers read the projection state of `AssembleContext.agent.session`: with an earlier paragraph they return it verbatim (`interpolate: false`), otherwise the current guidance. Diagnostic assemblies without an agent use the current guidance. `sessionProjections` is a required injection and the plugin registers its own unit, so the state is readable whenever an agent exists.
|
||||
|
||||
@@ -85,7 +85,7 @@ The projection computes only on each committed `system/message`, adding no synch
|
||||
- Only these two paragraphs are protected. When other prompt text changes between releases, routes without in-history updates still rewrite the head.
|
||||
- A release without this fix rewrites the head of a new-format Session with the earlier template on resume; both A/B checkouts need this fix before they alternate.
|
||||
- app-boot no longer exports `addHarnessSourceSection` or `HARNESS_SOURCE_SECTION`; web-app's `surfaceContext` controls only `DSH_WEB_URL`, and disabling the `harness-dev-context` row turns both contexts off.
|
||||
- Runtime context in recorded Web e2e Sessions gains two lines, and the test scaffold must normalize the URL and checkout path to placeholders.
|
||||
- Runtime context in recorded Web e2e Sessions gains two lines; the test scaffold normalizes the URL and checkout path to placeholders.
|
||||
|
||||
## Testing
|
||||
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ Status: implemented
|
||||
|
||||
| 包 | 职责 |
|
||||
|---|---|
|
||||
| `@deepseek-ai/dsh-harness-dev-context`(`packages/context/harness-dev-context`) | 两个 section、两个 context、旧格式投影;全部模块私有,只导出插件协议(`name`、`inject`、`apply`)与投影状态类型 `LegacyPrompt` |
|
||||
| `@deepseek-ai/dsh-harness-dev-context`(`packages/context/harness-dev-context`) | 两个 section、两个 context、旧格式投影;全部模块私有,只导出插件协议(`name`、`inject`、`apply`) |
|
||||
| `@deepseek-ai/dsh-host-webserver` | `WebServer.getOrigin()`(绑定地址的 URL 形式加协议与实际端口)与 `bindUrlHost(host)` |
|
||||
| `@deepseek-ai/dsh-web-app` | `web-runtime` 行提供 `webApp`,其 `getAdvertisedUrl()` 返回配置的 `publicUrl`,否则返回监听器 origin,与 `DSH_WEB_URL` 和启动 URL 同值;bundle patch 挂载 `harness-dev-context` 行 |
|
||||
| `@deepseek-ai/dsh-system-prompt` | `CONTEXT_ORDERS.HARNESS_SOURCE = 200`、`WEB_SURFACE = 210` |
|
||||
@@ -49,7 +49,7 @@ Status: implemented
|
||||
| 旧模板 | 匹配规则 |
|
||||
|---|---|
|
||||
| `harness:source` | `The DeepSeek Harness implementation checkout is at ` 开头、旧版固定说明结尾、中间不含换行 |
|
||||
| `app:web-surface` | `You are interacting with the user through the DeepSeek Harness Web GUI at ` 开头、`. ` 加现行固定说明结尾、中间不含换行 |
|
||||
| `app:web-surface` | `You are interacting with the user through the DeepSeek Harness Web GUI at ` 开头、`. ` 加早期版本渲染的说明结尾(冻结为字面量)、中间不含换行 |
|
||||
|
||||
两个 section 的文本提供函数读取 `AssembleContext.agent.session` 的投影状态:有旧段落时原样返回该段落(`interpolate: false`),否则返回新说明。没有 agent 的诊断性组装使用新说明。`sessionProjections` 是插件的必需注入,投影单元由插件自己注册,因此存在 agent 时状态必然可读。
|
||||
|
||||
@@ -85,7 +85,7 @@ Status: implemented
|
||||
- 只保护这两段。其他提示文本在版本之间变化时,不支持 in-history 的路由仍会改写头节点。
|
||||
- 不含本修复的旧版本恢复新格式会话时会按旧模板改写头节点;A/B 两个 checkout 都需包含本修复后再交替运行。
|
||||
- app-boot 不再导出 `addHarnessSourceSection` 与 `HARNESS_SOURCE_SECTION`;web-app 的 `surfaceContext` 只控制 `DSH_WEB_URL`,关闭这两项上下文改为禁用 `harness-dev-context` 行。
|
||||
- Web e2e 录制会话里的运行时上下文多出两行,测试脚手架需把 URL 与 checkout 路径规范化为占位符。
|
||||
- Web e2e 录制会话里的运行时上下文多出两行;测试脚手架把 URL 与 checkout 路径规范化为占位符。
|
||||
|
||||
## 测试
|
||||
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ Options:
|
||||
--port <port> listen port; pass 0 to let the OS pick a free
|
||||
one
|
||||
--public-url <url> advertise this HTTP(S) root in the printed,
|
||||
opened, web-surface, and DSH_WEB_URL forms;
|
||||
opened, runtime-context, and DSH_WEB_URL forms;
|
||||
grants no trust
|
||||
--trusted-host <authority...> extra authority the /api browser-trust fence
|
||||
accepts (host or host:port; repeatable)
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ Options:
|
||||
--port <port> listen port; pass 0 to let the OS pick a free
|
||||
one
|
||||
--public-url <url> advertise this HTTP(S) root in the printed,
|
||||
opened, web-surface, and DSH_WEB_URL forms;
|
||||
opened, runtime-context, and DSH_WEB_URL forms;
|
||||
grants no trust
|
||||
--trusted-host <authority...> extra authority the /api browser-trust fence
|
||||
accepts (host or host:port; repeatable)
|
||||
|
||||
@@ -4281,9 +4281,9 @@ export interface Config {
|
||||
*/
|
||||
surfaceContext: boolean
|
||||
/**
|
||||
* Canonical HTTP(S) root to advertise in the printed and opened URL and
|
||||
* `DSH_WEB_URL`, e.g.
|
||||
* `https://app.example/ui/`, normalized to end in `/`. Configures no routing
|
||||
* Canonical HTTP(S) root to advertise in the printed and opened URL,
|
||||
* `DSH_WEB_URL`, and `webApp.getAdvertisedUrl()`, which supplies the Web GUI
|
||||
* URL in agent runtime context, e.g. `https://app.example/ui/`, normalized to end in `/`. Configures no routing
|
||||
* or authentication; see [public deployments](../README.md#public-deployments).
|
||||
* Absent or YAML `null` advertises the bind-address URL.
|
||||
*/
|
||||
|
||||
@@ -4283,9 +4283,9 @@ export interface Config {
|
||||
*/
|
||||
surfaceContext: boolean
|
||||
/**
|
||||
* Canonical HTTP(S) root to advertise in the printed and opened URL and
|
||||
* `DSH_WEB_URL`, e.g.
|
||||
* `https://app.example/ui/`, normalized to end in `/`. Configures no routing
|
||||
* Canonical HTTP(S) root to advertise in the printed and opened URL,
|
||||
* `DSH_WEB_URL`, and `webApp.getAdvertisedUrl()`, which supplies the Web GUI
|
||||
* URL in agent runtime context, e.g. `https://app.example/ui/`, normalized to end in `/`. Configures no routing
|
||||
* or authentication; see [public deployments](../README.md#public-deployments).
|
||||
* Absent or YAML `null` advertises the bind-address URL.
|
||||
*/
|
||||
|
||||
@@ -637,6 +637,10 @@ flowchart TD
|
||||
pkg_goal --> pkg_session_projection
|
||||
pkg_goal --> pkg_typert_protocol
|
||||
pkg_file_reference --> pkg_agent
|
||||
pkg_harness_dev_context --> pkg_agent
|
||||
pkg_harness_dev_context --> pkg_session
|
||||
pkg_harness_dev_context --> pkg_session_projection
|
||||
pkg_harness_dev_context --> pkg_system_prompt
|
||||
pkg_time_context --> pkg_agent
|
||||
pkg_time_context --> pkg_llm
|
||||
pkg_time_context --> pkg_session
|
||||
@@ -1174,11 +1178,6 @@ flowchart TD
|
||||
pkg_compaction_tool_result_pruner --> pkg_llm
|
||||
pkg_compaction_tool_result_pruner --> pkg_session
|
||||
pkg_compaction_tool_result_pruner --> pkg_token_meter
|
||||
pkg_harness_dev_context --> pkg_agent
|
||||
pkg_harness_dev_context --> pkg_session
|
||||
pkg_harness_dev_context --> pkg_session_projection
|
||||
pkg_harness_dev_context --> pkg_system_prompt
|
||||
pkg_harness_dev_context --> pkg_web_app
|
||||
pkg_experimental_browser_use_runtime --> pkg_agent
|
||||
pkg_experimental_browser_use_runtime --> pkg_browser_use
|
||||
pkg_experimental_browser_use_runtime --> pkg_mcp_client
|
||||
@@ -1638,6 +1637,7 @@ flowchart TD
|
||||
| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) |
|
||||
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) |
|
||||
| [`harness-dev-context`](../packages/context/harness-dev-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
|
||||
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) |
|
||||
| [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) |
|
||||
@@ -1738,7 +1738,6 @@ flowchart TD
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`harness-dev-context`](../packages/context/harness-dev-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`web-app`](../packages/bundle/web-app) |
|
||||
| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | `experimental` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subprocess`](../packages/subprocess/subprocess), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-questions`](../packages/interaction/user-questions), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`tool-session-query`](../packages/experimental/tool-session-query) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
|
||||
@@ -639,6 +639,10 @@ flowchart TD
|
||||
pkg_goal --> pkg_session_projection
|
||||
pkg_goal --> pkg_typert_protocol
|
||||
pkg_file_reference --> pkg_agent
|
||||
pkg_harness_dev_context --> pkg_agent
|
||||
pkg_harness_dev_context --> pkg_session
|
||||
pkg_harness_dev_context --> pkg_session_projection
|
||||
pkg_harness_dev_context --> pkg_system_prompt
|
||||
pkg_time_context --> pkg_agent
|
||||
pkg_time_context --> pkg_llm
|
||||
pkg_time_context --> pkg_session
|
||||
@@ -1176,11 +1180,6 @@ flowchart TD
|
||||
pkg_compaction_tool_result_pruner --> pkg_llm
|
||||
pkg_compaction_tool_result_pruner --> pkg_session
|
||||
pkg_compaction_tool_result_pruner --> pkg_token_meter
|
||||
pkg_harness_dev_context --> pkg_agent
|
||||
pkg_harness_dev_context --> pkg_session
|
||||
pkg_harness_dev_context --> pkg_session_projection
|
||||
pkg_harness_dev_context --> pkg_system_prompt
|
||||
pkg_harness_dev_context --> pkg_web_app
|
||||
pkg_experimental_browser_use_runtime --> pkg_agent
|
||||
pkg_experimental_browser_use_runtime --> pkg_browser_use
|
||||
pkg_experimental_browser_use_runtime --> pkg_mcp_client
|
||||
@@ -1640,6 +1639,7 @@ flowchart TD
|
||||
| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) |
|
||||
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
|
||||
| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) |
|
||||
| [`harness-dev-context`](../packages/context/harness-dev-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
|
||||
| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) |
|
||||
| [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) |
|
||||
@@ -1740,7 +1740,6 @@ flowchart TD
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`harness-dev-context`](../packages/context/harness-dev-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`web-app`](../packages/bundle/web-app) |
|
||||
| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | `experimental` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subprocess`](../packages/subprocess/subprocess), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-questions`](../packages/interaction/user-questions), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`tool-session-query`](../packages/experimental/tool-session-query) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
en: 867565aa675f3fb3
|
||||
zh: c60606b5e5bdb778
|
||||
/publish-the-web-ui-behind-a-reverse-proxy/what-public-url-advertises:
|
||||
en: 576292b93ccf299a
|
||||
zh: ded5adc55396869c
|
||||
en: 924b28b211478f01
|
||||
zh: b098d5a53a29c883
|
||||
/publish-the-web-ui-behind-a-reverse-proxy/what-the-proxy-must-do:
|
||||
en: 2f93051763125365
|
||||
zh: 1dd699cb84ecf034
|
||||
|
||||
@@ -10,7 +10,7 @@ dsh --profile web --public-url https://app.example/ui/ --trusted-host app.exampl
|
||||
|
||||
## What `--public-url` advertises
|
||||
|
||||
`--public-url` accepts an `http://` or `https://` root with an optional mount prefix and normalizes it to end in `/`. It supplies the printed and opened startup URL, `DSH_WEB_URL`, and the web-surface orientation. The webserver keeps serving origin-root routes and never learns the mount. The `publicUrl` config field publishes the same advertisement.
|
||||
`--public-url` accepts an `http://` or `https://` root with an optional mount prefix and normalizes it to end in `/`. It supplies the printed and opened startup URL, `DSH_WEB_URL`, and the Web GUI URL in the agent's runtime context. The webserver keeps serving origin-root routes and never learns the mount. The `publicUrl` config field publishes the same advertisement.
|
||||
|
||||
## What the proxy must do
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ dsh --profile web --public-url https://app.example/ui/ --trusted-host app.exampl
|
||||
|
||||
## `--public-url` 公告什么
|
||||
|
||||
`--public-url` 接受可带挂载前缀的 `http://` 或 `https://` 根,并把它归一化为以 `/` 结尾。它提供打印与打开的启动 URL、`DSH_WEB_URL` 与 web 表层定位。webserver 继续提供 origin-root 路由,且从不了解挂载。`publicUrl` 配置字段发布同样的公告。
|
||||
`--public-url` 接受可带挂载前缀的 `http://` 或 `https://` 根,并把它归一化为以 `/` 结尾。它提供打印与打开的启动 URL、`DSH_WEB_URL`,以及 agent 运行时上下文中的 Web GUI URL。webserver 继续提供 origin-root 路由,且从不了解挂载。`publicUrl` 配置字段发布同样的公告。
|
||||
|
||||
## 代理必须做什么
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* manifest field). The plugin owns the browser-surface glue: it resolves
|
||||
* the built frontend dist (workspace knowledge of this bundle, never user
|
||||
* config), mounts the `frontend-static` fallback owner over it, registers the
|
||||
* bash-visible web runtime
|
||||
* variable, the process-token URL line, and the default-browser handoff. An
|
||||
* advertised `publicUrl` replaces the published bind-address URL. Non-loopback
|
||||
* bash-visible web runtime variable, the process-token URL line, and the
|
||||
* default-browser handoff, and provides the `webApp` service. An advertised
|
||||
* `publicUrl` replaces the published bind-address URL. Non-loopback
|
||||
* HTTP binds trigger an exposure warning. App command-line values arrive
|
||||
* through the `webStartup` service expressions in the bundle patch.
|
||||
* @module @deepseek-ai/dsh-web-app
|
||||
@@ -63,9 +63,9 @@ export interface Config {
|
||||
*/
|
||||
surfaceContext: boolean
|
||||
/**
|
||||
* Canonical HTTP(S) root to advertise in the printed and opened URL and
|
||||
* `DSH_WEB_URL`, e.g.
|
||||
* `https://app.example/ui/`, normalized to end in `/`. Configures no routing
|
||||
* Canonical HTTP(S) root to advertise in the printed and opened URL,
|
||||
* `DSH_WEB_URL`, and `webApp.getAdvertisedUrl()`, which supplies the Web GUI
|
||||
* URL in agent runtime context, e.g. `https://app.example/ui/`, normalized to end in `/`. Configures no routing
|
||||
* or authentication; see [public deployments](../README.md#public-deployments).
|
||||
* Absent or YAML `null` advertises the bind-address URL.
|
||||
*/
|
||||
|
||||
@@ -64,7 +64,7 @@ function webCommand(): Command {
|
||||
.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, web-surface, and DSH_WEB_URL forms; grants no trust')
|
||||
.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')
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
en: e8f89a14bd36dae0
|
||||
zh: bd4ec43e07e234c9
|
||||
/deepseek-ai-dsh-harness-dev-context/known-limitations-and-deferred-work:
|
||||
en: 80e76d36432ab935
|
||||
zh: 76864858d2fbc150
|
||||
en: 558945e2f537db5b
|
||||
zh: f584fcde6275c2da
|
||||
/deepseek-ai-dsh-harness-dev-context/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: e88aeb617ffb74ea
|
||||
@@ -117,6 +117,9 @@ These limits describe what the stability guarantee covers. They are current pack
|
||||
- **Two paragraphs only** — other system-prompt text that changes between releases still rewrites the first system message on routes without in-history prompt updates.
|
||||
- **Earlier values stay** — an earlier-format Session keeps its old checkout path and URL in the system prompt for its whole lifetime; only the later runtime-context lines are current.
|
||||
- **Mixed releases** — a release without this package rewrites the first system message of a Session started with it, so every checkout that serves a shared `DSH_HOME` needs this package before they alternate.
|
||||
- **Optional values** — the two runtime-context lines are optional, so a custom persona that sets `includeRuntimeContext: false` without `complete: true` keeps the sections that point at values its requests never carry. The shipped `minimal` persona sets both.
|
||||
- **Sessions without the sections** — a Session created while `surfaceContext: false` hid both paragraphs has neither in its log, so the first resumed request adds the new sections and rewrites the first system message on routes without in-history prompt updates.
|
||||
- **One earlier wording** — the projection recognizes the paragraphs as the release immediately before this package rendered them; a Session holding an older wording gets the current guidance.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -117,6 +117,9 @@ Current DeepSeek Harness Web GUI URL: "<advertised URL>".
|
||||
- **只覆盖两段**——版本之间变化的其他系统提示词文本,在不支持 in-history 提示词更新的路由上仍会改写第一条 system 消息。
|
||||
- **早期值保留**——早期格式会话在整个生命周期内都在系统提示词里保留旧 checkout 路径与 URL;只有之后的运行时上下文行是当前值。
|
||||
- **混用版本**——不含本包的版本会改写以本包启动的会话的第一条 system 消息,因此共用同一 `DSH_HOME` 的每个 checkout 都需要先包含本包,才能交替运行。
|
||||
- **可选值**——两行运行时上下文是可选的,因此设置 `includeRuntimeContext: false` 但未设置 `complete: true` 的自定义 persona 会保留指向其请求从不携带之值的段落。随附的 `minimal` persona 两者都设置。
|
||||
- **没有段落的会话**——在 `surfaceContext: false` 隐藏两段时创建的会话,日志中两段都没有,因此恢复后的第一个请求会加入新段落,并在不支持 in-history 提示词更新的路由上改写第一条 system 消息。
|
||||
- **只认一版早期措辞**——投影按本包之前紧邻的版本渲染的段落识别;保存更早措辞的会话会得到当前说明。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
"@deepseek-ai/dsh-agent": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:*",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:*",
|
||||
"@deepseek-ai/dsh-web-app": "workspace:*"
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
|
||||
@@ -83,7 +83,7 @@ const LEGACY_PROMPT = 'harnessDevLegacyPrompt'
|
||||
const legacyPromptSchema = zod.object({ source: zod.string().nullable(), web: zod.string().nullable() })
|
||||
|
||||
/** Value-bearing paragraphs of the latest nonempty system prompt a Session committed; `null` where it has none. */
|
||||
export type LegacyPrompt = zod.infer<typeof legacyPromptSchema>
|
||||
type LegacyPrompt = zod.infer<typeof legacyPromptSchema>
|
||||
|
||||
declare module '@deepseek-ai/dsh-session-projection/types' {
|
||||
interface SessionProjectionStateMap {
|
||||
|
||||
@@ -624,9 +624,11 @@ it('closes the failed Web authorization tab without redirecting and keeps the sh
|
||||
|
||||
it('completes login through a local TCP forward using the browser port rather than the Host port', async () => {
|
||||
const f = await fixture()
|
||||
const hostPort = f.ctx.webServer.port
|
||||
if (hostPort === undefined) throw new Error('account test: the Host listener has not bound a port')
|
||||
const sockets = new Set<Socket>()
|
||||
const forward = createTcpServer((socket) => {
|
||||
const upstream = connect(f.ctx.webServer.port, '127.0.0.1')
|
||||
const upstream = connect(hostPort, '127.0.0.1')
|
||||
for (const connection of [socket, upstream]) {
|
||||
sockets.add(connection)
|
||||
connection.on('close', () => { sockets.delete(connection) })
|
||||
|
||||
@@ -84,7 +84,12 @@ async function loadComposition(): Promise<Context> {
|
||||
}
|
||||
|
||||
/** GET (by default) one path against the running server; returns status, content-type, and the body. */
|
||||
async function request(port: number, path: string, init?: RequestInit): Promise<{ status: number; type: string | null; body: string }> {
|
||||
async function request(
|
||||
port: number | undefined,
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): Promise<{ status: number; type: string | null; body: string }> {
|
||||
if (port === undefined) throw new Error('frontend-static test: the listener has not bound a port')
|
||||
const response = await fetch(`http://127.0.0.1:${String(port)}${path}`, init)
|
||||
return {
|
||||
status: response.status,
|
||||
|
||||
@@ -323,9 +323,9 @@ export class WebServer extends Service {
|
||||
this.gzip = resolved.compression === 'gzip' ? createGzipMiddleware(resolved) : undefined
|
||||
}
|
||||
|
||||
/** The listening port (the OS-assigned value when config.port is 0). */
|
||||
get port(): number {
|
||||
return this.listenedPort as number
|
||||
/** The listening port (the OS-assigned value when config.port is 0), or `undefined` before `listen` binds. */
|
||||
get port(): number | undefined {
|
||||
return this.listenedPort
|
||||
}
|
||||
|
||||
/** The configured bind address (one concrete local interface). */
|
||||
|
||||
@@ -144,18 +144,25 @@ function webserverFiber(context: Context) {
|
||||
return [...context.loader.entries()].find(entry => entry.options.name === '@deepseek-ai/dsh-host-webserver')?.fiber
|
||||
}
|
||||
|
||||
/** The port a started listener bound; a test that reads it before `listen` fails here. */
|
||||
function boundPort(port: number | undefined): number {
|
||||
if (port === undefined) throw new Error('webserver test: the listener has not bound a port')
|
||||
return port
|
||||
}
|
||||
|
||||
/** GET (by default) one path against the running server; returns status plus a body prefix. */
|
||||
async function request(
|
||||
port: number,
|
||||
port: number | undefined,
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
): Promise<{ status: number; body: string; headers: Headers }> {
|
||||
const response = await fetch(`http://127.0.0.1:${String(port)}${path}`, init)
|
||||
const response = await fetch(`http://127.0.0.1:${String(boundPort(port))}${path}`, init)
|
||||
return { status: response.status, body: (await response.text()).slice(0, 80), headers: response.headers }
|
||||
}
|
||||
|
||||
/** Open one raw upgrade request and return after the handler writes its response. */
|
||||
async function upgrade(port: number, path: string): Promise<ReturnType<typeof connect>> {
|
||||
async function upgrade(listenerPort: number | undefined, path: string): Promise<ReturnType<typeof connect>> {
|
||||
const port = boundPort(listenerPort)
|
||||
const socket = connect(port, '127.0.0.1')
|
||||
await once(socket, 'connect')
|
||||
const response = once(socket, 'data')
|
||||
@@ -177,7 +184,7 @@ async function upgrade(port: number, path: string): Promise<ReturnType<typeof co
|
||||
* it trusts nothing, so it only succeeds if the listener really speaks TLS.
|
||||
*/
|
||||
function tlsRequest(
|
||||
port: number,
|
||||
port: number | undefined,
|
||||
path: string,
|
||||
ca?: string,
|
||||
agent?: Agent,
|
||||
@@ -185,7 +192,7 @@ function tlsRequest(
|
||||
const { promise, resolve, reject } = Promise.withResolvers<{ status: number; body: string }>()
|
||||
const request = httpsRequest({
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
port: boundPort(port),
|
||||
path,
|
||||
...(ca === undefined ? {} : { ca }),
|
||||
...(agent === undefined ? {} : { agent }),
|
||||
@@ -201,11 +208,11 @@ function tlsRequest(
|
||||
}
|
||||
|
||||
/** Open one upgrade request over TLS and return after the handler answers 101. */
|
||||
function tlsUpgrade(port: number, path: string, ca: string): Promise<{ status: number; socket: Duplex }> {
|
||||
function tlsUpgrade(port: number | undefined, path: string, ca: string): Promise<{ status: number; socket: Duplex }> {
|
||||
const { promise, resolve, reject } = Promise.withResolvers<{ status: number; socket: Duplex }>()
|
||||
const request = httpsRequest({
|
||||
host: '127.0.0.1',
|
||||
port,
|
||||
port: boundPort(port),
|
||||
path,
|
||||
ca,
|
||||
headers: { Connection: 'Upgrade', Upgrade: 'dsh-test' },
|
||||
@@ -487,7 +494,7 @@ describe('real Loader composition', () => {
|
||||
socket.destroy(new Error('test upgrade transport failure'))
|
||||
},
|
||||
})
|
||||
const failedUpgrade = connect(port, '127.0.0.1')
|
||||
const failedUpgrade = connect(boundPort(port), '127.0.0.1')
|
||||
failedUpgrade.on('error', () => { /* The server-side reset is the fixture outcome. */ })
|
||||
await once(failedUpgrade, 'connect')
|
||||
const failedUpgradeClosed = once(failedUpgrade, 'close')
|
||||
@@ -710,7 +717,7 @@ describe('real Loader composition', () => {
|
||||
expect(await tlsRequest(server.port, '/probe', fixture.ca, agent)).toEqual({ status: 200, body: 'EXACT' })
|
||||
await idleReady
|
||||
// A TCP connection without a ClientHello is absent from closeAllConnections().
|
||||
const handshaking = connect(server.port, '127.0.0.1')
|
||||
const handshaking = connect(boundPort(server.port), '127.0.0.1')
|
||||
observeClient(handshaking)
|
||||
await once(handshaking, 'connect')
|
||||
|
||||
|
||||
Reference in new issue
Block a user