Merge commit '2f717c0467d7174d7fe0011c4a787450f7328a78' into codex/product-subagent-one-shot-background

This commit is contained in:
pku-xht
2026-08-13 18:54:56 +08:00
324 changed files with 1894 additions and 66686 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@deepseek-ai/dsh",
"description": "dsh CLI: profile boot, plugin management, and the browser UI alias",
"version": "0.0.1-rc.5",
"version": "0.1.0-rc.3",
"publishConfig": {
"access": "restricted"
},

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 apps/cli/reference/README.md
README.md: 0b23db293c304807b5959f5eae9a2faf868caa62
README.zh.md: d812c7cd0d1efaa69b135925f3e922668539781b
README.md: 0be64fdfc0ad4e81d23f25a26881fa89f37565b0
README.zh.md: 649bf15df814abf4875fed794c2e76c494bfcc25

View File

@@ -61,7 +61,7 @@ dsh web --dump-config
dsh web --help
```
The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default. Binding all interfaces also trusts the machine's discovered LAN IP literals; `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence.
The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default. The CLI intentionally does not support `--host 0.0.0.0` yet and exits with a usage error; `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence.
Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain — `SIGTERM` is a supervisor's ordinary stop request and exits 0 on every surface, `SIGINT` reports 130; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed.

View File

@@ -61,7 +61,7 @@ dsh web --dump-config
dsh web --help
```
生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`绑定所有网络接口时,还会信任机器自动发现的 LAN IP 字面量`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。
生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`CLI 目前有意不支持 `--host 0.0.0.0`,并会以用法错误退出`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。
进程关闭时,插件树最多有 5 秒完成 dispose。首次收到 `SIGINT``SIGTERM` 时会开始优雅排空:`SIGTERM` 是监督进程发出的常规停止请求,在所有运行模式下都以 0 退出;`SIGINT` 则报告 130。第二次收到信号时会立即强制退出。如果一次性运行在正常结束时已经卡在 dispose 阶段,第一次按下 `Ctrl+C` 就会直接升级为强制退出,而不会被忽略。

View File

@@ -36,8 +36,8 @@ describe('parseDshArgs', () => {
.toEqual({ mode: 'profile', profile: 'tui', patches: [], args: ['--resume', 'abc'] })
expect(parse(['--profile', 'web', '-h']))
.toEqual({ mode: 'profile', profile: 'web', patches: [], args: ['-h'] })
expect(parse(['web', '--host', '0.0.0.0', '--port', '8080', '--dev']))
.toEqual({ mode: 'profile', profile: 'web', patches: [], args: ['--host', '0.0.0.0', '--port', '8080', '--dev'] })
expect(parse(['web', '--host', '127.0.0.1', '--port', '8080', '--dev']))
.toEqual({ mode: 'profile', profile: 'web', patches: [], args: ['--host', '127.0.0.1', '--port', '8080', '--dev'] })
expect(parse(['--profile', 'headless', 'run', 'the', 'tests']))
.toEqual({ mode: 'profile', profile: 'headless', patches: [], args: ['run', 'the', 'tests'] })
// Launcher flags placed after that boundary belong to the app too.

View File

@@ -339,6 +339,15 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
expect(web.stdout).toContain('--port <port>')
expect(web.stdout).not.toContain('dsh web: http://')
const wildcardHost = await runBuiltBin(['web', '--host', '0.0.0.0'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',
})
expect(wildcardHost.code).toBe(1)
expect(wildcardHost.stdout).toBe('')
expect(wildcardHost.stderr).toContain('--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')
expect(wildcardHost.stderr).not.toContain('dsh web: http://')
const headlessHelp = await runBuiltBin(['--profile', 'headless', '--help'], {
DSH_HOME: home,
DSH_TELEMETRY_DISABLED: '1',

View File

@@ -1,7 +1,7 @@
{
"name": "@deepseek-ai/dsh-web-frontend",
"description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web",
"version": "0.0.1-rc.5",
"version": "0.1.0-rc.3",
"publishConfig": {
"access": "restricted"
},

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 apps/web/tests/README.md
README.md: 4d7236ef69189845dbc2847289d497fc54c94ae9
README.zh.md: 21c2f2ce7ccc8fe5314d766c06235453aa292031
README.md: acb0c300bafe221f6a92f0168908bebf965377b9
README.zh.md: fd3d950a106375bb038b205415407b4a31bd2b32

View File

@@ -30,7 +30,8 @@ artifact it produces itself.
When a scenario needs a Client-owned constant or pure function, mirror it here
instead, next to the commented-out import that names the source module. A drift
then surfaces as a missed selector or a stale mirrored value — a loud failure,
never a silent pass.
never a silent pass. `scaffold.ts` follows this rule for the welcome-notice
namespace, acknowledgement field, version, and asserted Chinese copy.
Two kinds of Client import stand. `assembled-boot.ts` drives the shell itself, so
it imports `AppWebEntry` from `@deepseek-ai/dsh-client-web` and the boot-manifest

View File

@@ -24,7 +24,7 @@ Client face而该 face 必须等 Host tsdown 生成 `@deepseek-ai/dsh-goal/re
当某个场景需要 Client 持有的常量或纯函数时,改为在此处镜像一份,并紧挨着一条注释掉的
import 点明源模块。这样漂移会表现为选择器未命中或镜像值过期——是响亮的失败,绝不会是静默
通过。
通过。`scaffold.ts` 按此规则镜像欢迎声明的 namespace、确认字段、版本和被断言的中文文案。
有两类 Client import 是长期成立的。`assembled-boot.ts` 驱动 shell 本身,因此它从
`@deepseek-ai/dsh-client-web` import `AppWebEntry`、从

View File

@@ -26,7 +26,7 @@ describe('web e2e: goal bar clear convergence', () => {
let tripwire: ReturnType<typeof watchConsole>
beforeAll(async () => {
scaffold = await launchWebScaffold({ extraOverlayPath: OVERLAY })
scaffold = await launchWebScaffold({ extraOverlayPath: OVERLAY, welcomeNoticePending: true })
browser = await chromium.launch()
page = await newEnglishPage(browser)
tripwire = watchConsole(page)

View File

@@ -1,6 +1,7 @@
// Keyless browser e2e: the shipped DeepSeek adapter stays mounted while its
// credential is absent, onboarding routes to the real Models editor, and its
// write lands in an isolated harness home without a reload or model call.
// credential is absent, both ordered steps share the shipped modal chrome,
// and the inline key write lands in an isolated harness home without a reload
// or model call.
import { randomBytes } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
@@ -8,13 +9,17 @@ import { join } from 'node:path'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import {
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
WELCOME_NOTICE_ACK_FIELD, WELCOME_NOTICE_COPY, WELCOME_NOTICE_SETTINGS_NAMESPACE,
WELCOME_NOTICE_VERSION,
} from './scaffold.ts'
import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/onboarding-deepseek-config', import.meta.url))
const WELCOME_EXPECTED = join(SNAPSHOT_DIR, 'welcome.expected.md')
const MISSING_EXPECTED = join(SNAPSHOT_DIR, 'missing.expected.md')
const MODELS_EXPECTED = join(SNAPSHOT_DIR, 'models.expected.md')
const MODE = webSnapshotMode()
@@ -27,7 +32,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
const browserConsole: string[] = []
beforeAll(async () => {
scaffold = await launchWebScaffold({ deepSeekMissingCredential: true })
scaffold = await launchWebScaffold({ deepSeekMissingCredential: true, welcomeNoticePending: true })
browser = await chromium.launch()
// The scenario asserts the shipped Chinese copy, so the browser asks for it.
page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: ZH_BROWSER_LOCALE })
@@ -44,53 +49,40 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
it('stores a key write-only and observes configured state without restarting', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-onboarding-deepseek-config'))
const credentialStep = page.getByRole('region', { name: '添加一个 API Key 开始使用' })
await credentialStep.waitFor({ timeout: 15_000 })
const welcome = page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title })
await welcome.waitFor({ timeout: 15_000 })
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(true)
for (const paragraph of WELCOME_NOTICE_COPY.zh.body.split('\n\n')) {
expect(await welcome.getByText(paragraph, { exact: true }).count()).toBe(1)
}
expect(await welcome.getByRole('button').allTextContents()).toEqual([
WELCOME_NOTICE_COPY.zh.continueLabel,
])
const welcomeAria = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(WELCOME_EXPECTED, welcomeAria, MODE)
const mask = page.locator('[class*="onboardingMask"]')
expect(await mask.count()).toBe(1)
const maskStyles = await mask.evaluate((mask) => {
const style = getComputedStyle(mask)
const rect = mask.getBoundingClientRect()
return {
position: style.position,
left: style.left,
right: style.right,
top: style.top,
bottom: style.bottom,
background: style.backgroundColor,
backdropFilter: style.backdropFilter,
rect: { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom },
}
})
expect(maskStyles).toEqual({
position: 'absolute',
left: '0px',
right: '0px',
top: '80px',
bottom: '0px',
background: 'rgba(0, 0, 0, 0.24)',
backdropFilter: 'blur(2px)',
rect: { left: 0, top: 80, right: 1440, bottom: 960 },
})
// Observation is not acknowledgement: the exact version is persisted
// only by the explicit action, so a reload still presents this dialog.
const firstReloadWarnings = tripwire.warnings.length
await page.reload({ waitUntil: 'load' })
acknowledgeReloadConnectionLoss(tripwire, firstReloadWarnings)
await welcome.waitFor({ timeout: 15_000 })
expect(await credentialStep.getByRole('textbox').count()).toBe(0)
const initial = await captureStableAria(page, '[role="region"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(MISSING_EXPECTED, initial, MODE)
await welcome.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel }).click()
await welcome.waitFor({ state: 'detached', timeout: 15_000 })
await credentialStep.getByRole('button', { name: '前往配置' }).click()
await credentialStep.waitFor({ state: 'detached', timeout: 15_000 })
const settings = page.getByRole('dialog', { name: '设置' })
await settings.waitFor({ timeout: 10_000 })
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(false)
const keyInput = settings.getByLabel('API 密钥', { exact: true })
const credentialStep = page.getByRole('dialog', { name: '添加一个 API Key 开始使用' })
await credentialStep.waitFor({ timeout: 15_000 })
const keyInput = credentialStep.getByLabel('API 密钥', { exact: true })
await keyInput.waitFor({ timeout: 10_000 })
const initial = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(MISSING_EXPECTED, initial, MODE)
const secret = `dsh_onboarding_${randomBytes(12).toString('hex')}`
await keyInput.fill(secret)
await settings.getByRole('button', { name: '保存', exact: true }).click()
await keyInput.waitFor({ state: 'detached', timeout: 15_000 })
await credentialStep.getByRole('button', { name: '保存并继续' }).click()
await credentialStep.waitFor({ state: 'detached', timeout: 15_000 })
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(false)
const stored = await readFile(join(scaffold.harnessHome, '.credentials.yaml'), 'utf8')
expect(stored.includes(`DEEPSEEK_API_KEY: ${secret}`)).toBe(true)
@@ -98,8 +90,15 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
expect((await page.locator('body').ariaSnapshot()).includes(secret)).toBe(false)
expect(browserConsole.some(line => line.includes(secret))).toBe(false)
// The same open Models surface reuses the refreshed join and exposes the
const acknowledgedSettings = await readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8')
expect(acknowledgedSettings).toContain(`${WELCOME_NOTICE_ACK_FIELD}: ${WELCOME_NOTICE_VERSION}`)
// The ordinary Models surface reuses the refreshed join and exposes the
// configured write-only placeholder without a reload.
await page.getByRole('button', { name: '设置', exact: true }).click()
const settings = page.getByRole('dialog', { name: '设置' })
await settings.waitFor({ timeout: 10_000 })
await settings.getByRole('button', { name: '模型' }).click()
const deepSeekRow = settings.getByText('DeepSeek', { exact: true }).first()
await deepSeekRow.waitFor({ timeout: 10_000 })
await deepSeekRow.locator('xpath=ancestor::li').getByRole('button', { name: '编辑' }).click()
@@ -114,7 +113,21 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
await page.reload({ waitUntil: 'load' })
acknowledgeReloadConnectionLoss(tripwire, secondReloadWarnings)
await page.waitForSelector('[class*="frame"]', { timeout: 15_000 })
expect(await page.getByRole('region', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
expect(await page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title }).count()).toBe(0)
expect(await page.getByRole('dialog', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
// An old acknowledgement means materially revised copy: welcome returns,
// while the already-configured provider step remains complete.
await scaffold.ctx.settings.mutate(settingsNamespace(WELCOME_NOTICE_SETTINGS_NAMESPACE), [{
op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: 'previous-copy-version',
}])
const thirdReloadWarnings = tripwire.warnings.length
await page.reload({ waitUntil: 'load' })
acknowledgeReloadConnectionLoss(tripwire, thirdReloadWarnings)
await welcome.waitFor({ timeout: 15_000 })
await welcome.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel }).click()
await welcome.waitFor({ state: 'detached', timeout: 15_000 })
expect(await page.getByRole('dialog', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
expect((await page.content()).includes(secret)).toBe(false)
expect((await page.locator('body').ariaSnapshot()).includes(secret)).toBe(false)
@@ -125,10 +138,9 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
it('never paints the takeover chrome on a configured reload, even with the settings join held open', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-onboarding-configured-reload'))
// Regression pin for the reload white flash: the credential step is
// satisfied (credential configured), yet it must LOAD its
// private join before it can decide not to show. The chrome lives inside
// the step (OnboardingSurface), so the deciding window paints and blocks
// Regression pin for the reload flash: both steps are satisfied, yet each
// must load private facts before deciding not to show. Dialog chrome lives
// inside each visible branch, so the deciding window paints and blocks
// nothing. Holding settings.describe widens that window from loopback
// RTT scale to a deterministic hundreds of milliseconds, removing all
// timing dependence from the sampler assertions below.
@@ -141,7 +153,10 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
const sightings: string[] = []
;(window as unknown as { __takeoverSightings: string[] }).__takeoverSightings = sightings
setInterval(() => {
if (document.querySelector('[class*="onboardingStage"], [class*="onboardingMask"]') !== null) {
if (document.querySelector(
'[role="dialog"][aria-label="内测声明"], '
+ '[role="dialog"][aria-label="添加一个 API Key 开始使用"]',
) !== null) {
sightings.push('chrome')
}
if (document.getElementById('root')?.inert === true) sightings.push('inert')
@@ -171,7 +186,8 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
acknowledgeReloadConnectionLoss(tripwire, warningsBefore)
expect(await page.evaluate(() =>
(window as unknown as { __takeoverSightings: string[] }).__takeoverSightings)).toEqual([])
expect(await page.locator('[class*="onboardingStage"]').count()).toBe(0)
expect(await page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title }).count()).toBe(0)
expect(await page.getByRole('dialog', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
expect(tripwire.pageErrors).toEqual([])
}, 60_000)
@@ -228,7 +244,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
it('keeps the fixture inventory closed', async () => {
await assertFixtureInventory(
SNAPSHOT_DIR,
['missing.expected.md', 'models.expected.md'],
['welcome.expected.md', 'missing.expected.md', 'models.expected.md'],
)
})
})

View File

@@ -44,14 +44,17 @@ describe.skipIf(MODE === 'record')('web e2e: another usable provider ends first-
it('closes the setup card without discarding the add card beside it', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-onboarding-setup-card-cancel'))
const credentialStep = page.getByRole('region', { name: CREDENTIAL_STEP })
const credentialStep = page.getByRole('dialog', { name: CREDENTIAL_STEP })
await credentialStep.waitFor({ timeout: 15_000 })
await credentialStep.getByRole('button', { name: '前往配置' }).click()
await credentialStep.getByRole('button', { name: '稍后配置' }).click()
await credentialStep.waitFor({ state: 'detached', timeout: 15_000 })
await page.getByRole('button', { name: '设置', exact: true }).click()
const settings = page.getByRole('dialog', { name: '设置' })
await settings.waitFor({ timeout: 10_000 })
// Nothing is reachable yet, so DeepSeek presents itself as its open card.
// The onboarding step no longer navigates into Settings on dismissal, so
// enter the Models section explicitly before exercising its normal cards.
await settings.getByRole('button', { name: '模型' }).click()
const setupKey = settings.getByRole('textbox', { name: 'API 密钥', exact: true })
await setupKey.waitFor({ timeout: 10_000 })
@@ -66,15 +69,14 @@ describe.skipIf(MODE === 'record')('web e2e: another usable provider ends first-
{ timeout: 10_000 },
).toBe(2)
// Cancelling the setup card is the regression: it used to leave itself open
// and close the add card, discarding that draft.
// Cancelling the setup card must not close the independent add-provider
// draft beside it.
await settings.getByRole('button', { name: '取消', exact: true }).first().click()
expect(await settings.getByLabel('提供方').count()).toBe(1)
await expect.poll(
async () => settings.getByRole('textbox', { name: 'API 密钥', exact: true }).count(),
{ timeout: 10_000 },
).toBe(1)
// DeepSeek is now an ordinary row: a missing-key dot and an Edit button.
await settings.getByRole('button', { name: '编辑 DeepSeek (deepseek-official)' }).waitFor({ timeout: 10_000 })
const dismissed = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(DISMISSED_EXPECTED, dismissed, MODE)
@@ -104,10 +106,9 @@ describe.skipIf(MODE === 'record')('web e2e: another usable provider ends first-
// The regression: the step read only the official route's credential, so a
// fully configured user was taken over on every blank session.
await expect.poll(
async () => page.getByRole('region', { name: CREDENTIAL_STEP }).count(),
async () => page.getByRole('dialog', { name: CREDENTIAL_STEP }).count(),
{ timeout: 10_000 },
).toBe(0)
expect(await page.locator('[class*="onboardingStage"]').count()).toBe(0)
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(false)
// The Models page agrees: DeepSeek stays a row rather than reopening its

View File

@@ -0,0 +1,60 @@
// Trusted non-loopback Web access cannot call the loopback-only settings API;
// the notice therefore advances for this browser process and returns on reload.
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import {
acknowledgeReloadConnectionLoss, launchWebScaffold, watchConsole, webSnapshotMode,
WELCOME_NOTICE_COPY,
type WebScaffold,
} from './scaffold.ts'
import { ZH_BROWSER_LOCALE } from './support.ts'
const MODE = webSnapshotMode()
describe.skipIf(MODE === 'record')('web e2e: remote welcome notice', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
beforeAll(async () => {
scaffold = await launchWebScaffold({
remoteAuthority: 'remote.localhost',
welcomeNoticePending: true,
})
browser = await chromium.launch()
page = await browser.newPage({
viewport: { width: 1440, height: 960 },
locale: ZH_BROWSER_LOCALE,
})
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('#root', { timeout: 30_000 })
}, 120_000)
afterAll(async () => {
await browser?.close()
await scaffold?.close()
})
it('advances process-locally and presents the notice again after reload', async () => {
const welcome = page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title })
await welcome.waitFor({ timeout: 15_000 })
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(true)
await welcome.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel }).click()
await welcome.waitFor({ state: 'detached', timeout: 15_000 })
await expect.poll(
() => page.locator('#root').evaluate(root => (root as HTMLElement).inert),
{ timeout: 15_000 },
).toBe(false)
const reloadWarnings = tripwire.warnings.length
await page.reload({ waitUntil: 'load' })
acknowledgeReloadConnectionLoss(tripwire, reloadWarnings)
await welcome.waitFor({ timeout: 15_000 })
expect(tripwire.warnings).toEqual([])
expect(tripwire.pageErrors).toEqual([])
}, 60_000)
})

View File

@@ -41,6 +41,7 @@ import {
loadOverlayPatches,
} from '@deepseek-ai/dsh-app-boot'
import { dshHomePath } from '@deepseek-ai/dsh-home-paths'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
import type {
LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk,
@@ -62,6 +63,25 @@ import type {} from '@deepseek-ai/dsh-agent'
import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
import { REPO_ROOT, requireDist } from './support.ts'
// Host-side web e2e cannot import a browser package: doing so would pull that
// package's complete TS project into this graph. Mirrored from
// packages/client/ui-settings-models/src/onboarding-copy.ts; drift makes the
// default pre-acknowledgement stop suppressing the notice and fails loudly.
// import {
// WELCOME_NOTICE_ACK_FIELD, WELCOME_NOTICE_SETTINGS_NAMESPACE,
// WELCOME_NOTICE_VERSION, WELCOME_NOTICE_COPY,
// } from '@deepseek-ai/dsh-client-ui-settings-models'
export const WELCOME_NOTICE_SETTINGS_NAMESPACE = 'ui-onboarding'
export const WELCOME_NOTICE_ACK_FIELD = 'welcomeNoticeVersion'
export const WELCOME_NOTICE_VERSION = '2026-08-13.1'
export const WELCOME_NOTICE_COPY = {
zh: {
title: '内测声明',
body: 'DeepSeek Harness 目前的 0.1 版本仍处在面向 Harness 开发者进行测试的阶段,还有许多地方需要持续改进和打磨,希望听取广大开发者的反馈建议。预计 DeepSeek Harness 的核心插件以及基础 API 都会在接下来的一段时间内快速迭代、持续演化。\n\n我们期待与全球开发者一起在开源、开放、可复用、可组合的基础设施之上共同探索智能上限。欢迎全球 Harness 开发者加入 DSH 插件生态。',
continueLabel: '继续',
},
} as const
/** Snapshot mode for the lane, from $DSH_SNAPSHOT (same vocabulary as the other snapshot suites). */
export type WebSnapshotMode = 'replay' | 'record' | 'refresh'
@@ -213,6 +233,8 @@ export interface LaunchOptions {
* keyless first-run configuration lane; the default disables the adapter.
*/
deepSeekMissingCredential?: boolean
/** Leave the current welcome notice pending; ordinary scenarios pre-acknowledge it before browser boot. */
welcomeNoticePending?: boolean
/**
* Patch the shipped DeepSeek search row to a deterministic endpoint and
* credential reference. Browser search scenarios keep the real provider and
@@ -510,6 +532,11 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
})
await ctx.loader.await()
assertEntriesLoaded(ctx, 'web e2e scaffold')
if (options.welcomeNoticePending !== true) {
await ctx.settings.mutate(settingsNamespace(WELCOME_NOTICE_SETTINGS_NAMESPACE), [{
op: 'set', path: [WELCOME_NOTICE_ACK_FIELD], value: WELCOME_NOTICE_VERSION,
}])
}
const boundPort = ctx.get('webServer')?.port
if (boundPort === undefined) {
throw new Error('web e2e scaffold: webServer service missing after settled boot')

View File

@@ -2,6 +2,6 @@
- treeitem "Ungrouped" [expanded]:
- img
- text: Ungrouped
- treeitem "Use the read tool twice 1min"
- treeitem "Use the read tool twice (1) now"
- treeitem "Use the read tool twice (2) now" [selected]
- treeitem "Use the read tool twice (1) now"
- treeitem "Use the read tool twice 1min"

View File

@@ -1,5 +1,8 @@
- region "添加一个 API Key 开始使用":
- dialog "添加一个 API Key 开始使用":
- heading "添加一个 API Key 开始使用" [level=2]
- paragraph: 配置 DeepSeek 官方模型,即可开始使用。
- text: API 密钥
- textbox "API 密钥":
- /placeholder: 输入 API 密钥
- button "稍后配置"
- button "前往配置"
- button "保存并继续" [disabled]

View File

@@ -0,0 +1,5 @@
- dialog "内测声明":
- heading "内测声明" [level=2]
- paragraph: DeepSeek Harness 目前的 0.1 版本仍处在面向 Harness 开发者进行测试的阶段,还有许多地方需要持续改进和打磨,希望听取广大开发者的反馈建议。预计 DeepSeek Harness 的核心插件以及基础 API 都会在接下来的一段时间内快速迭代、持续演化。
- paragraph: 我们期待与全球开发者一起,在开源、开放、可复用、可组合的基础设施之上,共同探索智能上限。欢迎全球 Harness 开发者加入 DSH 插件生态。
- button "继续"

View File

@@ -2,5 +2,5 @@
- treeitem "workspace" [expanded]:
- img
- text: workspace
- treeitem "New Session" [selected]
- treeitem "1 subagent running Delegate a background job. now"
- treeitem "New Session" [selected]

View File

@@ -2,5 +2,5 @@
- treeitem "workspace" [expanded]:
- img
- text: workspace
- treeitem "Ask a research subagent to now"
- treeitem "Explain event sourcing in one (1) now" [selected]
- treeitem "Ask a research subagent to now"