Merge remote-tracking branch 'origin/master' into feat/send-unify

This commit is contained in:
Turtle
2026-07-24 16:52:46 +08:00
192 changed files with 6925 additions and 2517 deletions

View File

@@ -1,9 +1,10 @@
/**
* Pins the client-bundle purity gate (tsdown preset resolveId classifier):
* a bare-name import of a module-table package must rewrite to its /client
* external form (inlining it duplicates runtime identity — the P0
/* leak that is not an
* inline-safe wire layer must fail the build loudly.
* Pins the client-bundle purity gate (tsdown preset resolveId classifier),
* the build-time mirror of the module-edge rules: platform module-table
* entries stay external, inline-safe wire layers inline, and every other
* @deepseek-ai value import — including a bare plugin-package name and a
* cross-plugin /client subpath — must fail the build loudly (cross-plugin
* collaboration goes through cordis services, never module imports).
*/
import { describe, expect, it } from 'vitest'
import { CLIENT_EXTERNALS, clientBundle } from '../packages/client/tsdown.client.ts'
@@ -23,22 +24,16 @@ function purityResolveId(): ResolveId {
describe('client bundle purity gate', () => {
const resolveId = purityResolveId()
it('leaves table entries and non-scoped specifiers alone', () => {
it('leaves platform table entries and non-scoped specifiers alone', () => {
expect(resolveId('@deepseek-ai/dsh-client-ui-slots')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-client-runtime/client')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-client-web-react')).toBeNull()
expect(resolveId('@deepseek-ai/dsh-client-ui-primitives')).toBeNull()
expect(resolveId('react')).toBeNull()
expect(resolveId('zod')).toBeNull()
})
it('rewrites a bare table-package name to its external /client form (duplicate-instance prevention)', () => {
expect(resolveId('@deepseek-ai/dsh-client-connection')).toEqual({
id: '@deepseek-ai/dsh-client-connection/client',
external: true,
})
expect(resolveId('@deepseek-ai/dsh-client-ui-layout')).toEqual({
id: '@deepseek-ai/dsh-client-ui-layout/client',
external: true,
})
it('rejects retired table entries (web-react/store left the 8-entry seed)', () => {
expect(() => resolveId('@deepseek-ai/dsh-client-web-react/store')).toThrow(/purity/)
})
it('lets inline-safe wire layers inline', () => {
@@ -52,9 +47,16 @@ describe('client bundle purity gate', () => {
expect(() => resolveId('@deepseek-ai/dsh-client-web')).toThrow(/purity/)
})
it('every /client external has no bare-name twin in the table (the rewrite assumption)', () => {
for (const entry of CLIENT_EXTERNALS) {
if (entry.endsWith('/client')) expect(CLIENT_EXTERNALS).not.toContain(entry.slice(0, -'/client'.length))
}
it('throws on cross-plugin value imports — bare plugin names and /client subpaths alike (the rewrite arm is gone)', () => {
expect(() => resolveId('@deepseek-ai/dsh-client-connection')).toThrow(/purity/)
expect(() => resolveId('@deepseek-ai/dsh-client-runtime')).toThrow(/purity/)
expect(() => resolveId('@deepseek-ai/dsh-client-ui-layout/client')).toThrow(/purity/)
})
it('carries exactly one documented temporary exemption: runtime/client (store engine pending rehoming)', () => {
expect(resolveId('@deepseek-ai/dsh-client-runtime/client')).toBeNull()
const dshClientChannels = CLIENT_EXTERNALS.filter(
entry => entry.startsWith('@deepseek-ai/') && entry.endsWith('/client'))
expect(dshClientChannels).toEqual(['@deepseek-ai/dsh-client-runtime/client'])
})
})

85
scripts/dev-web.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* Watch-build for client-plugin HMR: runs every dshClient plugin package
* through the tsdown JS API in watch mode. Reload signaling is not this
* script's business — the host webserver stat-polls the bundles it serves and
* broadcasts `rebuilt` frames itself (`dsh web --dev`), so any process that
* rewrites `lib/client.js` files triggers reloads; this script is merely the
* convenient way to keep them all rebuilt on source change.
*
* Usage: `pnpm exec tsx scripts/dev-web.ts [--poll[=ms]]`. Requires the
* packages' node halves built once (`tsc -b tsconfig.build.json`): the lib
* config's entries are tsc output. `--poll` switches the source-file watcher
* to polling (default 500ms): network mounts (weka) deliver no inotify
* events, so native watching sees the initial build only and never a source
* change.
*
* Each package keeps its own tsdown.config.ts untouched: this script layers
* `watch` through API-level inline config (tsdown workspace mode fills inline
* keys under each package's file config, and no package config defines it).
*/
import { readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { build } from 'tsdown'
const repoRoot = fileURLToPath(new URL('..', import.meta.url))
/**
* Discover the watch workspace by declaration: every packages/<group>/<name>
* whose package.json carries `dshClient` with platform "web" is a client
* plugin bundle emitter. Scanned once at startup — a package added while
* watching means restarting this script.
* @returns workspace-relative plugin package directories.
*/
function discoverPluginDirs(): string[] {
const dirs: string[] = []
for (const group of readdirSync(join(repoRoot, 'packages'), { withFileTypes: true })) {
if (!group.isDirectory()) continue
for (const pkg of readdirSync(join(repoRoot, 'packages', group.name), { withFileTypes: true })) {
if (!pkg.isDirectory()) continue
let manifest: { dshClient?: { platform?: unknown } }
try {
manifest = JSON.parse(
readFileSync(join(repoRoot, 'packages', group.name, pkg.name, 'package.json'), 'utf8'),
) as { dshClient?: { platform?: unknown } }
} catch {
continue // no package.json (support dirs, scratch): not a workspace package
}
if (manifest.dshClient?.platform === 'web') dirs.push(`packages/${group.name}/${pkg.name}`)
}
}
return dirs
}
const PLUGIN_DIRS = discoverPluginDirs()
if (PLUGIN_DIRS.length === 0) {
console.error('dev-web: no dshClient (platform "web") packages found under packages/')
process.exit(1)
}
const args = process.argv.slice(2)
const pollArg = args.find(a => a === '--poll' || a.startsWith('--poll='))
if (args.some(a => a !== pollArg)) {
console.error('dev-web: usage: tsx scripts/dev-web.ts [--poll[=ms]]')
process.exit(1)
}
const pollInterval = pollArg === undefined ? undefined : Number(pollArg.split('=')[1] ?? '500')
if (pollInterval !== undefined && (!Number.isInteger(pollInterval) || pollInterval <= 0)) {
console.error(`dev-web: invalid --poll interval "${pollArg ?? ''}"`)
process.exit(1)
}
await build({
cwd: repoRoot,
workspace: PLUGIN_DIRS,
watch: true,
// Rolldown watch options ride through inputOptions (tsdown has no watcher
// tuning of its own); polling is opt-in for network mounts without inotify.
...pollInterval !== undefined
? { inputOptions: { watch: { watcher: { usePolling: true, pollInterval } } } }
: {},
})
console.log(
`dev-web: watching ${String(PLUGIN_DIRS.length)} dshClient plugin packages`
+ `${pollInterval !== undefined ? ` (polling ${String(pollInterval)}ms)` : ''}:\n ${PLUGIN_DIRS.join('\n ')}`,
)

View File

@@ -0,0 +1,23 @@
<translation>
---
layout: doc
---
# 快照说明
agent智能体执行一个步骤。
</translation>
<review>
- 无修正
</review>
<final>
---
layout: doc
---
# 快照说明
agent智能体执行一个步骤。
</final>

View File

@@ -0,0 +1,7 @@
---
layout: doc
---
# Snapshot note
The agent performs one step.

View File

@@ -54,6 +54,7 @@ export const LINK_MAP: Record<string, string> = {
SessionEvent: 'core.md',
SessionId: 'core.md',
SessionStartSource: 'core.md',
SessionLogSnapshot: 'session-query.md',
SessionSurfaceSnapshot: 'session-query.md',
ApprovalOutcome: 'approval.md',
ApprovalPolicy: 'approval.md',

View File

@@ -379,9 +379,9 @@ function coverageGate(): Gate {
})
}
// The snapshot suite boots the example bins in `lib` mode (built artifact under plain Node,
// plugins via real exports) — CI and check-all already build, so they exercise what ships rather
// than the tsx/source path dev uses. It therefore waits on `build`.
// Example and package snapshots boot their bins in `lib` mode (built artifacts under plain Node,
// plugins via real exports); repository-script snapshots execute their real source entry path.
// CI and check-all already build before either class runs, so the suite waits on `build`.
function snapshotGate(): Gate {
return pnpmScript('snapshot', 'test:snapshot', {
env: { DSH_EXAMPLE_MODE: 'lib' },

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@
".agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md",
".agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md",
".agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md",
".agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md",
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
".agents/notes/implemented/process/2026-07-19-web-styling-system.md",
"README.md",

View File

@@ -0,0 +1,32 @@
/** Runnable keyless snapshot for the assembled translation request and consumed response. */
import { execFile } from 'node:child_process'
import { access, mkdir, writeFile } from 'node:fs/promises'
import { dirname, join, resolve } from 'node:path'
import { promisify } from 'node:util'
import { describe, expect, it } from 'vitest'
const execFileAsync = promisify(execFile)
const root = resolve(import.meta.dirname, '..')
const expected = join(root, 'scripts/snapshots/translation-prompt-v4/request-response.expected.json')
const refreshing = process.env.DSH_SNAPSHOT === 'record' || process.env.DSH_SNAPSHOT === 'refresh'
describe('translation prompt runnable snapshot', () => {
it('assembles the reviewed examples and consumes a recorded new-pair response', async () => {
const { stdout, stderr } = await execFileAsync(process.execPath, [
join(root, 'scripts/verify-translation-prompt.ts'),
'--snapshot',
], { cwd: root, maxBuffer: 4 * 1024 * 1024 })
expect(stderr).toBe('')
expect(() => {
JSON.parse(stdout)
}).not.toThrow()
if (refreshing) {
await mkdir(dirname(expected), { recursive: true })
await writeFile(expected, stdout)
} else {
await access(expected)
}
await expect(stdout).toMatchFileSnapshot(expected)
})
})

View File

@@ -1,76 +1,200 @@
/** Regression tests for the executable translation prompt contract. */
/** Unit tests for the prompt-v4 renderer and three-section response parser. */
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
consumeTranslationResponse,
parseTranslationResponse,
renderTranslationPrompt,
renderTranslationRequest,
renderTranslationResponse,
} from './translation-prompt.ts'
const document = `# Wrapper
## 模板正文
\`\`\`\`text
{{source_lang}} to {{target_lang}}
{{translation_rules}}
{{terminology}}
[English]({{source_filename}}) | [中文]({{source_filename_zh}})
\`\`\`\`
`
const root = resolve(import.meta.dirname, '..')
const document = readFileSync(join(root, 'docs/i18n/translation-prompt.md'), 'utf8')
const terminology = '| English | 中文 |\n|---|---|\n| agent | agent |'
describe('translation prompt rendering', () => {
it('renders every supported placeholder without recursively rewriting injected rules', () => {
const rendered = renderTranslationPrompt(document, {
it('renders both directions with every placeholder resolved', () => {
const en = renderTranslationPrompt(document, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })
expect(en).toContain('from English to Chinese')
expect(en).toContain(terminology)
expect(en).not.toContain('{{')
expect(en).toContain('plain source stays plain (必须)')
expect(en).toContain('When the target language is English, use the "English" column without a Chinese gloss')
expect(en).toContain('for a Chinese target, use an established Chinese rendering')
expect(en).toContain('for an English target, use the established English technical term')
expect(en).toContain('does an English target use established English terminology')
expect(en).toContain('The parser removes exactly one framing escape')
const zh = renderTranslationPrompt(document, { sourceLanguage: 'Chinese', sourceFilename: 'guide.zh.md', terminology })
expect(zh).toContain('from Chinese to English')
})
it('rejects a template with unknown or missing placeholders', () => {
const alien = document.replaceAll('{{terminology}}', '{{terms_prompt}}')
expect(() => renderTranslationPrompt(alien, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })).toThrow(/unsupported placeholder/)
const missing = document.replaceAll('{{terminology}}', '')
expect(() => renderTranslationPrompt(missing, { sourceLanguage: 'English', sourceFilename: 'guide.md', terminology })).toThrow(/required placeholder/)
})
it('rejects unmatched placeholder delimiters', () => {
for (const delimiter of ['{{', '}}']) {
const malformed = document.replace('Your task is to translate', `Your task ${delimiter} is to translate`)
expect(() => renderTranslationPrompt(malformed, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
terminology,
})).toThrow(/malformed placeholder syntax/)
}
})
it('assembles bare few-shot turns before the real source document', () => {
const request = renderTranslationRequest(document, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
translationRules: 'A literal {{source_lang}} in injected rules.',
terminology: '| English | 中文 |',
sourceDocument: '# Guide\n\nNew source.',
terminology,
examples: [{ english: '# Example\n\nEnglish.', chinese: '# 示例\n\n中文。' }],
})
expect(rendered).toContain('English to Chinese')
expect(rendered).toContain('A literal {{source_lang}} in injected rules.')
expect(rendered).toContain('[English](guide.md) | [中文](guide.zh.md)')
})
expect(request.targetFilename).toBe('guide.zh.md')
expect(request.messages.map(message => message.role)).toEqual(['system', 'user', 'assistant', 'user'])
expect(request.messages.slice(1).map(message => message.content)).toEqual([
'# Example\n\nEnglish.',
'# 示例\n\n中文。',
'# Guide\n\nNew source.',
])
it('rejects a filename whose suffix contradicts the source language', () => {
expect(() => renderTranslationPrompt(document, {
const reverse = renderTranslationRequest(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.md',
translationRules: 'rules',
terminology: 'terms',
})).toThrow('does not match source language Chinese')
})
it('rejects malformed template placeholders before injecting rule contents', () => {
expect(() => renderTranslationPrompt(document.replace('{{source_lang}}', '{{source-lang}}'), {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
translationRules: 'A literal {{source_lang}} in injected rules.',
terminology: '| English | 中文 |',
})).toThrow('template contains malformed placeholder syntax')
sourceFilename: 'guide.zh.md',
sourceDocument: '# 指南\n\n新源文。',
terminology,
examples: [{ english: '# Example\n\nEnglish.', chinese: '# 示例\n\n中文。' }],
})
expect(reverse.targetFilename).toBe('guide.md')
expect(reverse.messages.slice(1).map(message => message.content)).toEqual([
'# 示例\n\n中文。',
'# Example\n\nEnglish.',
'# 指南\n\n新源文。',
])
})
})
describe('translation response XML', () => {
it('round-trips Markdown and the CDATA terminator', () => {
const response = {
translation: '# Draft\n\nA ]]> marker.',
review: '- [Tone] Fixed.',
final: '# Final\n\nA ]]> marker.',
}
describe('translation response sections', () => {
it('round-trips Markdown bodies', () => {
const response = { translation: '# 标题\n\n正文 **加粗**。', review: '- [Tone] 修正一处。\n- 无修正', final: '# 标题\n\n定稿。' }
expect(parseTranslationResponse(renderTranslationResponse(response))).toEqual(response)
})
it('rejects missing, reordered, nested, attributed, or non-CDATA children', () => {
expect(() => parseTranslationResponse('<dsh-translation-response version="1"/>')).toThrow('translation, review, and final')
expect(() => parseTranslationResponse('<dsh-translation-response version="1"><review><![CDATA[x]]></review></dsh-translation-response>'))
.toThrow('expected translation, got review')
expect(() => parseTranslationResponse(renderTranslationResponse({ translation: 'x', review: 'y', final: 'z' })
.replace('<translation><![CDATA[x]]></translation>', '<translation><b><![CDATA[x]]></b></translation>')))
.toThrow('nested element b is not allowed')
expect(() => parseTranslationResponse(renderTranslationResponse({ translation: 'x', review: 'y', final: 'z' }).replace('<review>', '<review lang="en">')))
.toThrow('review must not have attributes')
expect(() => parseTranslationResponse(renderTranslationResponse({ translation: 'x', review: 'y', final: 'z' }).replace('<![CDATA[x]]>', 'x')))
.toThrow('all response field content must be inside CDATA')
it('tolerates a fenced xml wrapper around the whole response', () => {
const fenced = '```xml\n<translation>\nA\n</translation>\n\n<review>\n- 无修正\n</review>\n\n<final>\nA\n</final>\n```'
expect(parseTranslationResponse(fenced).final).toBe('A')
})
it('keeps an inline close tag inside prose from terminating the section', () => {
const doc = { translation: 'the wire format uses </translation> as its close tag', review: '- 无修正', final: 'F' }
expect(parseTranslationResponse(renderTranslationResponse(doc))).toEqual(doc)
})
it('round-trips wrapper-tag lines inside Markdown bodies', () => {
const doc = {
translation: '```xml\n</translation>\n```',
review: '- [Structure] Preserved `<final>` on its own line.',
final: 'literal delimiters\n</final>\n\\</final>',
}
const rendered = renderTranslationResponse(doc)
expect(parseTranslationResponse(rendered)).toEqual(doc)
expect(() => parseTranslationResponse(rendered.replace('\\</translation>', '</translation>'))).toThrow(/duplicate <translation>/)
})
it('rejects a duplicate section appearing before final', () => {
const early = '<translation>\nA\n</translation>\n<translation>\nB\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>'
expect(() => parseTranslationResponse(early)).toThrow(/duplicate <translation>/)
})
it('rejects missing, unterminated, or duplicated sections', () => {
expect(() => parseTranslationResponse('<translation>\nA\n</translation>')).toThrow(/missing or unterminated <review>/)
expect(() => parseTranslationResponse('<translation>\nA')).toThrow(/missing or unterminated <translation>/)
const dup = '<translation>\nA\n</translation>\n<review>\nR\n</review>\n<final>\nF\n</final>\n<final>\nG\n</final>'
expect(() => parseTranslationResponse(dup)).toThrow(/duplicate <final>/)
expect(() => parseTranslationResponse(`${renderTranslationResponse({ translation: 'A', review: 'R', final: 'F' })}\nstray`))
.toThrow(/content is not allowed outside/)
})
it('inserts or corrects the target switcher after parsing a new-pair response', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: '# 指南\n\nEnglish | [中文](guide.zh.md)\n\n定稿。',
})
expect(consumeTranslationResponse(response, { sourceLanguage: 'English', sourceFilename: 'guide.md' }).final).toBe([
'# 指南',
'',
'[English](guide.md) | 中文',
'',
'定稿。',
'',
].join('\n'))
})
it('preserves YAML frontmatter before inserting the target switcher', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: [
'---',
'layout: home',
'---',
'',
'# 指南',
'',
'定稿。',
].join('\n'),
})
expect(consumeTranslationResponse(response, { sourceLanguage: 'English', sourceFilename: 'guide.md' }).final).toBe([
'---',
'layout: home',
'---',
'',
'# 指南',
'',
'[English](guide.md) | 中文',
'',
'定稿。',
'',
].join('\n'))
})
it('rejects unterminated YAML frontmatter before the target H1', () => {
const response = renderTranslationResponse({
translation: '# 指南\n\n初稿。',
review: '- 无修正',
final: '---\nlayout: home\n\n# 指南\n\n定稿。',
})
expect(() => consumeTranslationResponse(response, {
sourceLanguage: 'English',
sourceFilename: 'guide.md',
})).toThrow(/unterminated YAML frontmatter/)
})
it('rejects a source filename that contradicts the translation direction', () => {
expect(() => renderTranslationPrompt(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.md',
terminology,
})).toThrow(/does not match source language Chinese/)
})
it('inserts the English target switcher for a Chinese source', () => {
const response = renderTranslationResponse({
translation: '# Guide\n\nDraft.',
review: '- [None] No corrections.',
final: '# Guide\n\nFinal.',
})
expect(consumeTranslationResponse(response, {
sourceLanguage: 'Chinese',
sourceFilename: 'guide.zh.md',
}).final).toContain('\n\nEnglish | [中文](guide.zh.md)\n\n')
})
})

View File

@@ -1,20 +1,18 @@
/**
* Executable renderer and strict response parser for the committed
* documentation-translation prompt contract.
* Executable renderer and response parser for the committed
* documentation-translation prompt contract (prompt-v4).
*
* The v4 contract: three placeholders (`source_lang`, `target_lang`,
* `terminology`), whole-document translation, and a three-section response
* (`<translation>`, `<review>`, `<final>` in order, bare XML tags with raw
* Markdown bodies). The pipeline retains filename context outside the model
* request and corrects the final language switcher after parsing.
*/
import { basename } from 'node:path'
import { SaxesParser } from 'saxes'
/** Placeholder names supported by the committed translation prompt. */
export const TRANSLATION_PROMPT_PLACEHOLDERS = [
'source_lang',
'target_lang',
'translation_rules',
'terminology',
'source_filename',
'source_filename_zh',
] as const
export const TRANSLATION_PROMPT_PLACEHOLDERS = ['source_lang', 'target_lang', 'terminology'] as const
type TranslationPromptPlaceholder = (typeof TRANSLATION_PROMPT_PLACEHOLDERS)[number]
@@ -26,13 +24,35 @@ export interface TranslationPromptInput {
sourceLanguage: TranslationLanguage
/** Source basename, including `.md` or `.zh.md`. */
sourceFilename: string
/** Complete current `translation-rules.md` contents. */
translationRules: string
/** Complete current `terminology.md` contents. */
terminology: string
}
/** Parsed contents of the three-element XML response. */
/** One reviewed whole-document example available in both directions. */
export interface TranslationExample {
english: string
chinese: string
}
/** Inputs for one complete model request. */
export interface TranslationRequestInput extends TranslationPromptInput {
sourceDocument: string
examples: TranslationExample[]
}
/** One model message in the provider-neutral translation request. */
interface TranslationMessage {
role: 'system' | 'user' | 'assistant'
content: string
}
/** Fully assembled request plus the filename that receives the final body. */
export interface TranslationRequest {
targetFilename: string
messages: TranslationMessage[]
}
/** Parsed contents of the three-section response. */
export interface TranslationResponse {
translation: string
review: string
@@ -42,7 +62,35 @@ export interface TranslationResponse {
const PLACEHOLDER = /{{([a-z_]+)}}/g
const TEMPLATE_OPEN = '## 模板正文\n\n````text\n'
const TEMPLATE_CLOSE = '\n````'
const RESPONSE_CHILDREN = ['translation', 'review', 'final'] as const
const RESPONSE_SECTIONS = ['translation', 'review', 'final'] as const
const RESPONSE_DELIMITERS = new Set(RESPONSE_SECTIONS.flatMap(section => [`<${section}>`, `</${section}>`]))
const LANGUAGE_SWITCHER = /^(?:English \| \[中文\]\(.+\)|\[English\]\(.+\) \| 中文)$/
interface TranslationFiles {
targetFilename: string
targetSwitcher: string
}
function translationFiles(input: Pick<TranslationPromptInput, 'sourceFilename' | 'sourceLanguage'>): TranslationFiles {
if (basename(input.sourceFilename) !== input.sourceFilename) {
throw new Error(`translation prompt: sourceFilename must be a basename; got ${JSON.stringify(input.sourceFilename)}`)
}
const sourceIsChinese = input.sourceFilename.endsWith('.zh.md')
const sourceIsEnglish = input.sourceFilename.endsWith('.md') && !sourceIsChinese
if (input.sourceLanguage === 'Chinese' ? !sourceIsChinese : !sourceIsEnglish) {
throw new Error(`translation prompt: ${input.sourceFilename} does not match source language ${input.sourceLanguage}`)
}
if (sourceIsChinese) {
return {
targetFilename: input.sourceFilename.replace(/\.zh\.md$/, '.md'),
targetSwitcher: `English | [中文](${input.sourceFilename})`,
}
}
return {
targetFilename: input.sourceFilename.replace(/\.md$/, '.zh.md'),
targetSwitcher: `[English](${input.sourceFilename}) | 中文`,
}
}
/** Extract the machine-consumed text fence from `translation-prompt.md`. */
function extractTranslationPrompt(document: string): string {
@@ -61,25 +109,14 @@ export function documentedTranslationPromptPlaceholders(document: string): strin
return [...document.slice(0, preambleEnd).matchAll(/^\| `{{([a-z_]+)}}` \|/gm)].map(match => match[1] ?? '')
}
/** Render one system prompt from the checked-in template and canonical rules. */
/** Render one system prompt from the checked-in template. */
export function renderTranslationPrompt(document: string, input: TranslationPromptInput): string {
if (basename(input.sourceFilename) !== input.sourceFilename) {
throw new Error(`translation prompt: sourceFilename must be a basename; got ${JSON.stringify(input.sourceFilename)}`)
}
const sourceIsChinese = input.sourceFilename.endsWith('.zh.md')
if (input.sourceLanguage === 'Chinese' ? !sourceIsChinese : sourceIsChinese || !input.sourceFilename.endsWith('.md')) {
throw new Error(`translation prompt: ${input.sourceFilename} does not match source language ${input.sourceLanguage}`)
}
translationFiles(input)
const targetLanguage: TranslationLanguage = input.sourceLanguage === 'English' ? 'Chinese' : 'English'
const sourceFilenameZh = sourceIsChinese ? input.sourceFilename : input.sourceFilename.replace(/\.md$/, '.zh.md')
const values: Record<TranslationPromptPlaceholder, string> = {
source_lang: input.sourceLanguage,
target_lang: targetLanguage,
translation_rules: input.translationRules,
terminology: input.terminology,
source_filename: input.sourceFilename,
source_filename_zh: sourceFilenameZh,
}
const template = extractTranslationPrompt(document)
const placeholderFreeTemplate = template.replace(PLACEHOLDER, '')
@@ -95,77 +132,128 @@ export function renderTranslationPrompt(document: string, input: TranslationProm
return template.replace(PLACEHOLDER, (_token, name: string) => values[name as TranslationPromptPlaceholder])
}
/** Escape one value so it remains byte-identical inside an XML CDATA field. */
function escapeTranslationCdata(value: string): string {
return value.replaceAll(']]>', ']]]]><![CDATA[>')
/**
* Assemble the calibrated system prompt, reviewed bare-text examples, and source document.
*
* @param document - Checked-in translation prompt asset.
* @param input - Direction, filename, terminology, examples, and source document.
* @returns Provider-neutral messages and the target basename.
*/
export function renderTranslationRequest(document: string, input: TranslationRequestInput): TranslationRequest {
const files = translationFiles(input)
const sourceKey = input.sourceLanguage === 'English' ? 'english' : 'chinese'
const targetKey = input.sourceLanguage === 'English' ? 'chinese' : 'english'
const messages: TranslationMessage[] = [{ role: 'system', content: renderTranslationPrompt(document, input) }]
for (const example of input.examples) {
messages.push(
{ role: 'user', content: example[sourceKey] },
{ role: 'assistant', content: example[targetKey] },
)
}
messages.push({ role: 'user', content: input.sourceDocument })
return { targetFilename: files.targetFilename, messages }
}
/** Serialize a response using the exact XML wire contract in the prompt. */
function escapeResponseBody(value: string): string {
return value.split('\n').map((line) => {
const delimiter = line.replace(/^\\+/, '')
return RESPONSE_DELIMITERS.has(delimiter) ? `\\${line}` : line
}).join('\n')
}
function unescapeResponseBody(value: string): string {
return value.split('\n').map((line) => {
if (!line.startsWith('\\')) return line
const candidate = line.slice(1)
return RESPONSE_DELIMITERS.has(candidate.replace(/^\\+/, '')) ? candidate : line
}).join('\n')
}
/** Serialize a response in the exact escaped three-section shape the prompt requests. */
export function renderTranslationResponse(response: TranslationResponse): string {
return [
'<dsh-translation-response version="1">',
`<translation><![CDATA[${escapeTranslationCdata(response.translation)}]]></translation>`,
`<review><![CDATA[${escapeTranslationCdata(response.review)}]]></review>`,
`<final><![CDATA[${escapeTranslationCdata(response.final)}]]></final>`,
'</dsh-translation-response>',
].join('\n')
return RESPONSE_SECTIONS.map(section => `<${section}>\n${escapeResponseBody(response[section])}\n</${section}>`).join('\n\n')
}
/** Parse and validate the exact XML response shape emitted by the model. */
export function parseTranslationResponse(xml: string): TranslationResponse {
const values: TranslationResponse = { translation: '', review: '', final: '' }
const stack: string[] = []
const cdataFields = new Set<string>()
let rootSeen = false
let childIndex = 0
const fail = (message: string): never => {
throw new Error(`translation response: ${message}`)
}
const parser = new SaxesParser({ xmlns: false })
/**
* Parse the three-section response. Sections must each appear exactly once
* and in order; escaped delimiter lines in Markdown bodies are restored.
* A fenced ```xml wrapper around the whole response is tolerated, matching
* the shape some models echo back from the prompt's own example.
*/
export function parseTranslationResponse(text: string): TranslationResponse {
let body = text.trim()
const fenced = /^```(?:xml)?\n([\s\S]*?)\n```$/.exec(body)
if (fenced?.[1] !== undefined) body = fenced[1].trim()
parser.on('opentag', (tag) => {
if (stack.length === 0) {
if (rootSeen) fail('contains more than one root element')
if (tag.name !== 'dsh-translation-response') fail(`expected dsh-translation-response root, got ${tag.name}`)
const attributes = Object.keys(tag.attributes)
if (attributes.length !== 1 || tag.attributes.version !== '1') fail('root must have only version="1"')
rootSeen = true
} else if (stack.length === 1) {
const expected = RESPONSE_CHILDREN[childIndex]
if (tag.name !== expected) fail(`expected ${expected ?? 'no more children'}, got ${tag.name}`)
if (Object.keys(tag.attributes).length !== 0) fail(`${tag.name} must not have attributes`)
childIndex++
} else {
fail(`nested element ${tag.name} is not allowed`)
const values: Partial<Record<(typeof RESPONSE_SECTIONS)[number], string>> = {}
const lines = body.split('\n')
let previousCloseEnd = 0
for (const [index, section] of RESPONSE_SECTIONS.entries()) {
const open = `<${section}>`
const close = `</${section}>`
const openCount = lines.filter(line => line === open).length
const closeCount = lines.filter(line => line === close).length
if (openCount === 0 || closeCount === 0) {
throw new Error(`translation response: missing or unterminated <${section}> section`)
}
stack.push(tag.name)
})
parser.on('text', (value) => {
if (stack.length <= 1 && value.trim() === '') return
fail('all response field content must be inside CDATA')
})
parser.on('cdata', (value) => {
const field = stack.at(-1)
if (field === undefined || !RESPONSE_CHILDREN.includes(field as (typeof RESPONSE_CHILDREN)[number])) {
fail('CDATA is allowed only inside translation, review, or final')
}
const key = field as (typeof RESPONSE_CHILDREN)[number]
values[key] += value
cdataFields.add(key)
})
parser.on('closetag', (tag) => {
const expected = stack.pop()
if (expected !== tag.name) fail(`closing ${tag.name} does not match ${expected ?? 'nothing'}`)
})
parser.on('comment', () => fail('comments are not allowed'))
parser.on('doctype', () => fail('doctypes are not allowed'))
parser.on('processinginstruction', () => fail('processing instructions are not allowed'))
parser.on('error', error => fail(`invalid XML: ${error.message}`))
parser.write(xml).close()
if (openCount > 1 || closeCount > 1) throw new Error(`translation response: duplicate <${section}> section`)
if (childIndex !== RESPONSE_CHILDREN.length) fail('translation, review, and final must each appear exactly once and in order')
for (const field of RESPONSE_CHILDREN) {
if (!cdataFields.has(field)) fail(`${field} must contain a CDATA section`)
const openStart = body.search(new RegExp(`^<${section}>$`, 'm'))
const closeStart = body.search(new RegExp(`^</${section}>$`, 'm'))
const separator = body.slice(previousCloseEnd, openStart)
if (closeStart < openStart || (index === 0 ? separator !== '' : !/^\n+$/.test(separator))) {
throw new Error('translation response: sections must appear in translation, review, final order')
}
let contentStart = openStart + open.length
if (body[contentStart] === '\n') contentStart++
let contentEnd = closeStart
if (body[contentEnd - 1] === '\n') contentEnd--
values[section] = unescapeResponseBody(body.slice(contentStart, contentEnd))
previousCloseEnd = closeStart + close.length
}
return values
if (previousCloseEnd !== body.length) throw new Error('translation response: content is not allowed outside response sections')
return values as TranslationResponse
}
function correctLanguageSwitcher(markdown: string, switcher: string): string {
const lines = markdown.replaceAll('\r\n', '\n').split('\n')
while (lines.at(-1) === '') lines.pop()
let headingIndex = 0
if (lines[0] === '---') {
const frontmatterEnd = lines.indexOf('---', 1)
if (frontmatterEnd === -1) throw new Error('translation response: final document has unterminated YAML frontmatter')
headingIndex = frontmatterEnd + 1
while (lines[headingIndex] === '') headingIndex++
}
if (!/^#\s+\S/.test(lines[headingIndex] ?? '')) {
throw new Error('translation response: final document must start with an H1 heading')
}
let contentStart = headingIndex + 1
while (lines[contentStart] === '') contentStart++
if (LANGUAGE_SWITCHER.test(lines[contentStart] ?? '')) contentStart++
while (lines[contentStart] === '') contentStart++
const output = [...lines.slice(0, headingIndex), lines[headingIndex] as string, '', switcher]
const content = lines.slice(contentStart)
if (content.length > 0) output.push('', ...content)
return `${output.join('\n')}\n`
}
/**
* Parse a model response and make its consumed final document target-path correct.
*
* @param text - Raw three-section model response.
* @param input - Source direction and basename retained by the pipeline.
* @returns Parsed response whose `final` body has the canonical target switcher.
*/
export function consumeTranslationResponse(
text: string,
input: Pick<TranslationPromptInput, 'sourceFilename' | 'sourceLanguage'>,
): TranslationResponse {
const parsed = parseTranslationResponse(text)
const files = translationFiles(input)
return { ...parsed, final: correctLanguageSwitcher(parsed.final, files.targetSwitcher) }
}

View File

@@ -409,6 +409,11 @@
"symbol": "SessionRecord",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.md",
"symbol": "SessionLogSnapshot",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.md",
"symbol": "SessionSurfaceSnapshot",

View File

@@ -45,6 +45,8 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/bash/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers no model surface.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/web-react': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },

View File

@@ -3,11 +3,14 @@
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import {
consumeTranslationResponse,
documentedTranslationPromptPlaceholders,
parseTranslationResponse,
renderTranslationPrompt,
renderTranslationRequest,
renderTranslationResponse,
TRANSLATION_PROMPT_PLACEHOLDERS,
type TranslationExample,
} from './translation-prompt.ts'
const root = resolve(import.meta.dirname, '..')
@@ -17,38 +20,76 @@ function read(path: string): string {
}
try {
const mode = process.argv[2]
if (mode !== undefined && mode !== '--snapshot') throw new Error(`unsupported argument ${JSON.stringify(mode)}`)
const document = read('docs/i18n/translation-prompt.md')
const translationRules = read('docs/i18n/translation-rules.md')
const terminology = read('docs/i18n/terminology.md')
const examplePaths = [
['README.md', 'README.zh.md'],
['docs/development.md', 'docs/development.zh.md'],
['docs/i18n/README.md', 'docs/i18n/README.zh.md'],
['docs/i18n/translation-rules.md', 'docs/i18n/translation-rules.zh.md'],
[
'.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md',
'.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md',
],
] as const
const examples: TranslationExample[] = examplePaths.map(([english, chinese]) => ({
english: read(english),
chinese: read(chinese),
}))
const sourceDocument = read('scripts/fixtures/translation-prompt/snapshot-note.md')
const recordedResponse = read('scripts/fixtures/translation-prompt/response.txt')
const documented = documentedTranslationPromptPlaceholders(document)
if (documented.join('\n') !== TRANSLATION_PROMPT_PLACEHOLDERS.join('\n')) {
throw new Error(`placeholder table must list exactly: ${TRANSLATION_PROMPT_PLACEHOLDERS.join(', ')}`)
}
const englishSource = renderTranslationPrompt(document, {
sourceLanguage: 'English',
sourceFilename: 'example.md',
translationRules,
terminology,
})
const englishInput = { sourceLanguage: 'English' as const, sourceFilename: 'snapshot-note.md', terminology }
const englishSource = renderTranslationPrompt(document, englishInput)
const chineseSource = renderTranslationPrompt(document, {
sourceLanguage: 'Chinese',
sourceFilename: 'example.zh.md',
translationRules,
sourceFilename: 'snapshot-note.zh.md',
terminology,
})
if (!englishSource.includes('[English](example.md) | 中文')) throw new Error('English-source render does not carry the Chinese switcher instruction')
if (!chineseSource.includes('English | [中文](example.zh.md)')) throw new Error('Chinese-source render does not carry the English switcher instruction')
if (englishSource.includes('{{') || chineseSource.includes('{{')) throw new Error('rendered prompt contains an unresolved placeholder')
if (!englishSource.includes('from English to Chinese')) throw new Error('English-source render does not translate into Chinese')
if (!chineseSource.includes('from Chinese to English')) throw new Error('Chinese-source render does not translate into English')
const example = /```xml\n([\s\S]*?)\n```/.exec(englishSource)?.[1]
if (example === undefined) throw new Error('rendered prompt has no XML response example')
if (example === undefined) throw new Error('rendered prompt has no three-section response example')
parseTranslationResponse(example)
const roundTrip = { translation: 'first ]]> pass', review: '- [None] No corrections.', final: 'final ]]> text' }
const roundTrip = { translation: 'first pass\n\nwith **markdown**', review: '- 无修正', final: 'final text' }
const parsed = parseTranslationResponse(renderTranslationResponse(roundTrip))
if (JSON.stringify(parsed) !== JSON.stringify(roundTrip)) throw new Error('CDATA split rule does not round-trip response content')
if (JSON.stringify(parsed) !== JSON.stringify(roundTrip)) throw new Error('three-section response does not round-trip')
console.log('verify-translation-prompt: both directions render and the XML response contract parses.')
const request = renderTranslationRequest(document, { ...englishInput, sourceDocument, examples })
if (request.targetFilename !== 'snapshot-note.zh.md') throw new Error('English request resolves the wrong target filename')
const expectedRoles = ['system', ...examples.flatMap(() => ['user', 'assistant']), 'user']
if (request.messages.map(message => message.role).join('\n') !== expectedRoles.join('\n')) {
throw new Error('reviewed examples are not assembled as system, example pairs, then source')
}
const consumed = consumeTranslationResponse(recordedResponse, englishInput)
const expectedFinalPrefix = [
'---',
'layout: doc',
'---',
'',
'# 快照说明',
'',
'[English](snapshot-note.md) | 中文',
'',
].join('\n')
if (!consumed.final.startsWith(expectedFinalPrefix)) {
throw new Error('recorded frontmatter response does not preserve metadata and receive the canonical target switcher')
}
if (mode === '--snapshot') {
process.stdout.write(`${JSON.stringify({ request, response: consumed }, null, 2)}\n`)
} else {
console.log('verify-translation-prompt: both directions render, reviewed examples assemble, and the consumed response is target-path correct.')
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
console.error(`verify-translation-prompt: ${message}`)