Merge remote-tracking branch 'origin/master' into worktree/code-diff-card

This commit is contained in:
creatixchu committed 2026-09-11 16:47:45 +08:00
commit 3c632ebf66
318 files changed
+9061 -3253

No files matched your search

+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/bundle/web-app/README.md
README.md: aea694942173a856861d00a69f15b451cc930976
README.zh.md: 4dab93ff03071bff6783ae89b0397216b86a8c05
README.md: 84630f1d9142b1e2300baa0cde0513acdb602da9
README.zh.md: 0488c74f433b0ef1d7e6b1677b7f50fd21a198a2
+5 -1
View File
@@ -36,6 +36,10 @@ dsh --profile web --no-open --port 8080
After startup you see a `dsh web:` line whose root URL carries a fresh process token. Unless `--no-open` or an SSH session suppresses it, the default browser opens that URL, receives a signed cookie, and redirects to the clean root page. You know it worked when the page loads and you can chat with the agent. Two failures to expect: if the frontend is not built, startup stops with a build hint (`pnpm run build` in a checkout); if the browser cannot be opened, a credential-free diagnostic prints to stderr while the server keeps running — open the printed startup URL yourself.
**Settings → Models** displays **DeepSeek**, using `DEEPSEEK_API_KEY`. The default is `deepseek-official` / `deepseek-flash` (DeepSeek-V41-Flash). The [DeepSeek plugin](../../llm/llm-deepseek/README.md#choose-a-protocol) defaults to Chat Completions; set `protocol: messages` in Cordis YAML to switch. Web has no protocol selector.
Saved model selections override the composition default. Both protocols share `deepseek-official` and `llm-deepseek` settings, so switching preserves model selections and credential references. Endpoint overrides retain their values; the settings card lets users supply a compatible API address.
### Configuration
Most users never set these; the command-line flags feed the four settings below — `--host`, `--port`, and `--trusted-host` come from the invocation, and `--no-open` turns the browser handoff off for that invocation:
@@ -77,7 +81,7 @@ A patch replaces the targeted row's whole `config`, so each web row restates eve
### Readiness
The URL line and browser handoff are readiness signals: supervisors RPC as soon as they observe the line, and a browser requests the page as soon as it opens, so both run only after the Loader tree settles and Connection authentication is available — or immediately in a hand-built tree without a Loader. A tree disposed mid-boot announces nothing.
The URL line and browser handoff are readiness signals: supervisors RPC as soon as they observe the line, and a browser requests the page as soon as it opens, so both run only after the Loader tree settles, the required-startup audit passes, and Connection authentication is available — or immediately in a hand-built tree without a Loader. Optional plugin failures do not suppress readiness; a required startup failure or a tree disposed mid-boot announces nothing.
### LAN trust sampling
+5 -1
View File
@@ -36,6 +36,10 @@ dsh --profile web --no-open --port 8080
启动后你会看到 `dsh web:` 行,其根 URL 携带新的进程 token。除非 `--no-open` 或 SSH 会话抑制,否则默认浏览器会打开该 URL、取得签名 cookie,再重定向到不含认证参数的根页面。页面加载且你可以与 agent 对话,就说明成功了。两种可预期的失败:前端未构建时,启动会以构建提示停止(checkout 中运行 `pnpm run build`);浏览器无法打开时,stderr 会打印不含凭据的诊断,但服务器会继续运行——请自行打开已打印的启动 URL。
**设置 → 模型**显示 **DeepSeek**,使用 `DEEPSEEK_API_KEY`。默认模型为 `deepseek-official` / `deepseek-flash`(DeepSeek-V41-Flash)。[DeepSeek 插件](../../llm/llm-deepseek/README.zh.md#choose-a-protocol)默认使用 Chat Completions;在 Cordis YAML 中设置 `protocol: messages` 可切换协议。Web 不提供协议选择器。
已保存的模型选择优先于组合默认值。两种协议共用 `deepseek-official` 与 `llm-deepseek` 设置,因此切换协议不改变模型选择或复制凭据。端点覆盖保持原值;设置卡片允许用户填写兼容的 API 地址。
### 配置
大多数用户不需要设置这些;命令行 flag 会提供给下面四个设置——`--host`、`--port` 与 `--trusted-host` 来自本次调用,`--no-open` 仅对本次调用关闭浏览器交接:
@@ -77,7 +81,7 @@ patch 会替换目标行的整个 `config`,因此每个 Web 行都重述自己
### 就绪宣告
URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发起 RPC,浏览器一打开就请求页面,因此两者只在 Loader 配置树结算且 Connection 认证可用后运行——在没有 Loader 的手工构建树中则立即运行。启动中途被释放的树不会宣告任何内容。
URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发起 RPC,浏览器一打开就请求页面,因此两者只在 Loader 配置树结算、通过 required 启动检查且 Connection 认证可用后运行——在没有 Loader 的手工构建树中则立即运行。可选插件失败不会阻止就绪宣告;required 启动失败或启动中途被释放的树不会宣告任何内容。
### LAN 信任采样
+6 -4
View File
@@ -18,7 +18,7 @@ import { networkInterfaces } from 'node:os'
import { fileURLToPath } from 'node:url'
import type { Context } from '@deepseek-ai/cordis'
import z from '@deepseek-ai/schemastery'
import { addHarnessSourceSection } from '@deepseek-ai/dsh-app-boot'
import { addHarnessSourceSection, auditStartupEntries } from '@deepseek-ai/dsh-app-boot'
import type {} from '@deepseek-ai/dsh-client-connection'
import * as FrontendStatic from '@deepseek-ai/dsh-host-frontend-static'
import { launchedThroughSsh, launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
@@ -284,15 +284,17 @@ export function apply(ctx: Context, config: Config): void {
const settled = connectionCtx.get('loader')?.await()
if (settled === undefined) announceReady()
else {
void settled.then(() => {
void settled.then(async () => {
await auditStartupEntries(connectionCtx.root, 'dsh web', () => {})
// The tree can be disposed while the boot was in flight (early
// SIGTERM); a URL line or browser tab for a dead server would only
// mislead, and reading torn-down services would turn a clean shutdown
// into a crash.
if (connectionCtx.get('webServer') !== undefined
&& connectionCtx.get('connection') !== undefined) announceReady()
// Loader reports a failed boot; this row only stays quiet.
}, () => {})
}).catch(() => {
// Boot owns the failure diagnostic; readiness remains unpublished.
})
}
})
}
+37 -2
View File
@@ -11,8 +11,10 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { PassThrough } from 'node:stream'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import * as AppBoot from '@deepseek-ai/dsh-app-boot'
import { createLaunchEnvironmentSnapshot, DSH_LAUNCH_ENVIRONMENT_KEY } from '@deepseek-ai/dsh-launch-environment'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import type { WebServer } from '@deepseek-ai/dsh-host-webserver'
@@ -100,7 +102,7 @@ function provideConnection(ctx: Context): void {
/** A fake Loader whose settlement the test controls (the URL line waits on it). */
function provideLoader(ctx: Context, settle: () => Promise<void> = async () => {}): void {
ctx.provide('loader', { await: settle } as never)
ctx.provide('loader', { await: settle, entries: () => [] } as never)
}
interface BashContribution {
@@ -313,6 +315,39 @@ describe('web-app runtime glue', () => {
await torn.fiber.dispose()
})
it.each([
{ id: 'webserver', announces: false },
{ id: 'modules', announces: false },
{ id: 'connection', announces: false },
{ id: 'optional-tool', announces: true },
])('announces readiness=$announces after the $id sibling fails', async ({ id, announces }) => {
stageDist()
const ctx = new Context()
onTestFinished(() => ctx.fiber.dispose())
ctx.baseUrl = 'file:///'
ctx.provide('webServer', fakeHttpServer().server)
provideConnection(ctx)
await ctx.plugin(Loader)
ctx.loader.builtins.failure = () => { throw new Error('sibling rejected') }
await ctx.loader.root.update([{ id, name: 'cordis:failure' }])
await ctx.loader.await()
await expect(ctx.loader.resolve(id).fiber?.await()).rejects.toThrow('sibling rejected')
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const openBrowser = vi.fn(async () => {})
internals.openBrowser = openBrowser
const audit = vi.spyOn(AppBoot, 'auditStartupEntries')
apply(ctx, new Config({ openBrowser: true, printUrl: true, surfaceContext: false, trustedHosts: [] }))
await vi.waitFor(() => { expect(audit).toHaveBeenCalledOnce() })
await Promise.allSettled(audit.mock.results.map(result => result.value as Promise<void>))
if (announces) {
expect(log).toHaveBeenCalledWith('dsh web: http://127.0.0.1:4567/?token=test-token')
expect(openBrowser).toHaveBeenCalledWith('http://127.0.0.1:4567/?token=test-token')
} else {
expect(log).not.toHaveBeenCalled()
expect(openBrowser).not.toHaveBeenCalled()
}
})
it('fails loud when the prompt section resolves against a portless webserver', async () => {
stageDist()
const ctx = new Context()