Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input

This commit is contained in:
creatixchu
2026-07-31 18:23:02 +08:00
31 changed files with 803 additions and 15 deletions

View File

@@ -0,0 +1,84 @@
import { writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import type { AgentHandle } from '@deepseek-ai/dsh-agent'
import { CallId } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import { launchWebScaffold, type WebScaffold } from './scaffold.ts'
const CORE_WEB_OVERLAY = fileURLToPath(new URL('../../cli/config/core-web.cordis.yml', import.meta.url))
describe('core Web profile', () => {
let scaffold: WebScaffold
let agentHandle: AgentHandle
beforeAll(async () => {
scaffold = await launchWebScaffold({
extraOverlayPath: CORE_WEB_OVERLAY,
toolsMode: 'native',
})
agentHandle = await scaffold.ctx.agents.create({
sessionId: SessionId('core-web-profile-smoke'),
meta: { cwd: scaffold.workspaceCwd },
agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
})
})
afterAll(async () => {
const failures: unknown[] = []
await agentHandle?.dispose().catch((error: unknown) => failures.push(error))
await scaffold?.close().catch((error: unknown) => failures.push(error))
if (failures.length === 1) throw failures[0]
if (failures.length > 1) throw new AggregateError(failures, 'core Web profile smoke teardown failed')
})
it('boots and executes both tools through the shipped Web composition', async () => {
const seedPath = join(scaffold.workspaceCwd, 'profile-smoke.txt')
await writeFile(seedPath, 'CORE_WEB_EDITOR_OK\n')
const signal = new AbortController().signal
const bash = await scaffold.ctx.tools.execute({
signal,
callId: CallId('core-web-bash-smoke'),
name: 'bash',
arguments: { command: "printf 'CORE_WEB_BASH_OK\\n'" },
agent: agentHandle.agent,
})
const editor = await scaffold.ctx.tools.execute({
signal,
callId: CallId('core-web-editor-smoke'),
name: 'str_replace_editor',
arguments: { command: 'view', path: seedPath },
agent: agentHandle.agent,
})
const text = (result: typeof bash): string => result.content
.filter(block => block.type === 'text')
.map(block => block.text)
.join('')
.replaceAll(scaffold.workspaceCwd, '{{cwd}}')
.trimEnd()
expect({
tools: scaffold.ctx.tools.schemas().map(tool => tool.name),
bash: text(bash),
editor: text(editor),
}).toMatchInlineSnapshot(`
{
"bash": "CORE_WEB_BASH_OK",
"editor": "Here's the content of {{cwd}}/profile-smoke.txt with line numbers (which has a total of 2 lines):
1 CORE_WEB_EDITOR_OK
2",
"tools": [
"bash",
"str_replace_editor",
],
}
`)
const entries = [...scaffold.ctx.loader.entries()]
expect(entries.find(entry => entry.options.id === 'persistent-bash')?.fiber).toBeDefined()
expect(entries.find(entry => entry.options.id === 'pty-local')?.fiber).toBeDefined()
expect(entries.find(entry => entry.options.id === 'str-replace-editor')?.fiber).toBeDefined()
})
})

View File

@@ -106,6 +106,12 @@ export interface WebScaffold {
/** Options for {@link launchWebScaffold}. */
export interface LaunchOptions {
/**
* Optional product overlay applied after the shipped Web surface and before
* the scaffold's hermetic test patches, matching AppCLIEntry's `--config`
* ordering.
*/
extraOverlayPath?: string
/**
* Replay fixture (session.jsonl) served by the inserted dsh-llm-replay row
* in replay/refresh modes; ignored in record mode (the real adapter
@@ -215,8 +221,12 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// snapshot overlay use, applied over the SAME shipped tree (a patch id that
// stops matching a row fails the boot sweep loudly instead of drifting).
const surfacePatches = loadOverlayPatches('web e2e scaffold', WEB_OVERLAY_PATH)
const extraOverlayPatches = options.extraOverlayPath === undefined
? []
: loadOverlayPatches('web e2e scaffold', options.extraOverlayPath)
const patches: PatchOptions[] = [
...surfacePatches,
...extraOverlayPatches,
{ id: 'session-persistence-jsonl', config: { root: persistenceRoot } },
{ id: 'session-query-sqlite', config: { path: ':memory:', openAt: 'first-search' } },
// storage-json's yml root is anchored to the real $DSH_HOME; pin the row

View File

@@ -24,6 +24,7 @@
"exclude": [
"tests/scaffold.ts",
"tests/scaffold-hermetic.e2e.ts",
"tests/core-web-profile.snapshot.ts",
"tests/live-interactions.e2e.ts",
"tests/question-composer.e2e.ts",
"tests/approval-composer.e2e.ts",