Merge remote-tracking branch 'origin/master' into worktree/2848-image-token-pressure

# Conflicts:
#	apps/cli/tests/profiles/acp/image-compaction.cordis.snapshot.yml
#	apps/cli/tests/profiles/acp/image-compaction.cordis.yml
#	apps/cli/tests/profiles/acp/tests/snapshots/image-compaction/input.json
#	apps/cli/tests/profiles/acp/tests/snapshots/image-compaction/session.jsonl
#	apps/cli/tests/profiles/acp/tests/snapshots/image-compaction/stdout.expected.jsonl
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	examples/acp-agent/tests/acp.snapshot.ts
#	packages/llm/token-meter/README.i18n.yaml
#	packages/llm/token-meter/README.md
#	packages/llm/token-meter/README.zh.md
#	packages/llm/token-meter/src/index.ts
#	packages/llm/token-meter/src/surface-fold.ts
This commit is contained in:
creatixchu
2026-08-25 11:15:35 +08:00
1861 changed files with 26322 additions and 11496 deletions
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { resolveLinuxNodePtyAddon } from './build-exe-for-python-sdk-native-pty.ts'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './build-exe-for-python-sdk-native-pty.ts'
const roots: string[] = []
@@ -35,6 +35,24 @@ describe('resolveLinuxNodePtyAddon', () => {
})
})
describe('resolveWindowsNodePtyAddons', () => {
it('requires both ConPTY addons from the x64 prebuild', () => {
const root = temporaryPackage()
const conpty = createAddon(root, 'prebuilds', 'win32-x64', 'conpty.node')
const consoleList = createAddon(root, 'prebuilds', 'win32-x64', 'conpty_console_list.node')
expect(resolveWindowsNodePtyAddons(root, 'x64')).toEqual([conpty, consoleList])
})
it('names every missing Windows addon', () => {
const root = temporaryPackage()
expect(() => resolveWindowsNodePtyAddons(root, 'x64')).toThrow(
`Windows node-pty addons are missing: ${join(root, 'prebuilds', 'win32-x64', 'conpty.node')}, ${join(root, 'prebuilds', 'win32-x64', 'conpty_console_list.node')}`,
)
})
})
function temporaryPackage(): string {
const root = mkdtempSync(join(tmpdir(), 'dsh-node-pty-addon-'))
roots.push(root)
@@ -21,3 +21,25 @@ export function resolveLinuxNodePtyAddon(
`build-exe-for-python-sdk: node-pty addon is absent from both ${built} and ${prebuilt}.`,
)
}
/**
* Require both node-pty addons used by the Windows ConPTY backend.
* @param packageDirectory - staged node-pty package directory.
* @param arch - Windows target architecture.
* @returns the existing addon paths in load order.
*/
export function resolveWindowsNodePtyAddons(
packageDirectory: string,
arch: 'x64',
): string[] {
const directory = join(packageDirectory, 'prebuilds', `win32-${arch}`)
const addons = [
join(directory, 'conpty.node'),
join(directory, 'conpty_console_list.node'),
]
const missing = addons.filter(path => !existsSync(path))
if (missing.length > 0) {
throw new Error(`build-exe-for-python-sdk: Windows node-pty addons are missing: ${missing.join(', ')}.`)
}
return addons
}
+81
View File
@@ -0,0 +1,81 @@
import { spawnSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
const root = resolve(import.meta.dirname, '..')
const script = resolve(root, 'scripts/build-exe-for-python-sdk.ts')
const temporaryDirectories: string[] = []
afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
rmSync(directory, { recursive: true, force: true })
}
})
function run(env: NodeJS.ProcessEnv, ...args: string[]) {
return spawnSync(process.execPath, ['--import', 'tsx/esm', script, ...args], {
cwd: root,
encoding: 'utf8',
env: isolatedPnpmEnvironment(env),
})
}
describe('Python runtime executable builder CLI', () => {
it('runs pnpm through its JavaScript entrypoint without a command shell', () => {
const result = run(
{ npm_execpath: 'C:\\tools\\pnpm.cjs' },
'--skip-build',
'--dry-run',
'--targets=node24-macos-arm64',
)
expect(result.status).toBe(0)
expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs run verify-runtime-closure`)
expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs --filter dsh-python-runtime-closure deploy`)
expect(result.stdout).toContain(`${process.execPath} C:\\tools\\pnpm.cjs dlx @yao-pkg/pkg@6.21.0`)
expect(result.stdout).not.toMatch(/pnpm\.cmd/i)
})
it('resolves the pnpm package behind a Windows command shim', () => {
const setup = mkdtempSync(join(tmpdir(), 'dsh-pnpm-home-'))
temporaryDirectories.push(setup)
const home = join(setup, 'node_modules', '.bin')
const entrypoint = join(setup, 'node_modules', 'pnpm', 'bin', 'pnpm.mjs')
mkdirSync(home, { recursive: true })
mkdirSync(dirname(entrypoint), { recursive: true })
writeFileSync(entrypoint, '')
const result = run(
{ npm_execpath: 'C:\\tools\\pnpm.cmd', PNPM_HOME: home },
'--skip-build',
'--dry-run',
'--targets=node24-macos-arm64',
)
expect(result.status).toBe(0)
expect(result.stdout).toContain(`${process.execPath} ${entrypoint} run verify-runtime-closure`)
expect(result.stdout).not.toMatch(/pnpm\.cmd/i)
})
it('rejects a Windows arm64 product before any build step', () => {
const result = run(
{ npm_execpath: 'C:\\tools\\pnpm.cjs' },
'--skip-build',
'--dry-run',
'--targets=node24-win-arm64',
)
expect(result.status).not.toBe(0)
expect(result.stderr).toContain('Windows supports x64 only')
expect(result.stdout).toBe('')
})
})
function isolatedPnpmEnvironment(overrides: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const environment = Object.fromEntries(
Object.entries(process.env).filter(([key]) => !['npm_execpath', 'pnpm_home'].includes(key.toLowerCase())),
)
return { ...environment, ...overrides }
}
+72 -20
View File
@@ -9,9 +9,9 @@
import { spawn } from 'node:child_process'
import { existsSync, statSync } from 'node:fs'
import { chmod, copyFile, cp, lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { basename, dirname, join, resolve, sep } from 'node:path'
import { basename, dirname, extname, join, resolve, sep } from 'node:path'
import { parseArgs } from 'node:util'
import { resolveLinuxNodePtyAddon } from './build-exe-for-python-sdk-native-pty.ts'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './build-exe-for-python-sdk-native-pty.ts'
const root = resolve(import.meta.dirname, '..')
@@ -63,7 +63,7 @@ const ASSET_GLOBS = [
'node_modules/@deepseek-ai/dsh-skill-badge/assets/**/*',
]
const PLATFORMS = ['linux', 'macos'] as const
const PLATFORMS = ['linux', 'macos', 'win'] as const
const ARCHES = ['x64', 'arm64'] as const
type Platform = (typeof PLATFORMS)[number]
type Arch = (typeof ARCHES)[number]
@@ -83,10 +83,7 @@ class Target {
private constructor(
/** pkg Node range (`node<major>`). */
readonly nodeRange: string,
/**
* pkg platform tag. Windows is a documented non-goal
* (.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
*/
/** pkg platform tag. */
readonly platform: Platform,
/** pkg CPU tag. */
readonly arch: Arch,
@@ -117,6 +114,9 @@ class Target {
if (!isArch(arch)) {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)}: arch must be one of ${ARCHES.join(', ')}, got ${JSON.stringify(arch)}.`)
}
if (platform === 'win' && arch !== 'x64') {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)}: Windows supports x64 only.`)
}
return new Target(nodeRange, platform, arch)
}
@@ -125,7 +125,13 @@ class Target {
* @returns the host target; throws on an unsupported host platform or arch.
*/
static host(): Target {
const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'linux' ? 'linux' : undefined
const platform = process.platform === 'darwin'
? 'macos'
: process.platform === 'linux'
? 'linux'
: process.platform === 'win32'
? 'win'
: undefined
if (platform === undefined) {
throw new Error(`build-exe-for-python-sdk: unsupported host platform ${process.platform}; pass --targets explicitly.`)
}
@@ -133,6 +139,9 @@ class Target {
if (arch === undefined) {
throw new Error(`build-exe-for-python-sdk: unsupported host arch ${process.arch}; pass --targets explicitly.`)
}
if (platform === 'win' && arch !== 'x64') {
throw new Error('build-exe-for-python-sdk: Windows supports x64 only; use an x64 Node process.')
}
return new Target(DEFAULT_NODE_RANGE, platform, arch)
}
}
@@ -200,7 +209,7 @@ class BuildCli {
return [
'Usage: pnpm exec tsx scripts/build-exe-for-python-sdk.ts [flags]',
'',
' --targets=<t1,t2,...> pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64.',
' --targets=<t1,t2,...> pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64.',
' Default: the host platform only (on node24).',
' --skip-build skip `pnpm run build` (lib/ artifacts must already exist).',
' --dry-run print every command and config patch without executing.',
@@ -212,8 +221,27 @@ class BuildCli {
}
}
function pnpmBin(): string {
return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
function pnpmInvocation(args: string[]): [command: string, args: string[]] {
const entrypoint = process.env.npm_execpath?.trim()
if (entrypoint !== undefined && entrypoint !== '') {
const extension = extname(entrypoint).toLowerCase()
if (extension === '.js' || extension === '.cjs' || extension === '.mjs') {
return [process.execPath, [entrypoint, ...args]]
}
if (extension !== '.cmd') return [entrypoint, args]
}
const home = process.env.PNPM_HOME?.trim()
if (home !== undefined && home !== '') {
const packageBin = resolve(home, '..', 'pnpm', 'bin')
for (const filename of ['pnpm.mjs', 'pnpm.cjs']) {
const candidate = resolve(packageBin, filename)
if (existsSync(candidate)) return [process.execPath, [candidate, ...args]]
}
}
if (process.platform === 'win32') {
throw new Error('build-exe-for-python-sdk: pnpm must expose a JavaScript entrypoint through npm_execpath or PNPM_HOME on Windows.')
}
return ['pnpm', args]
}
/**
@@ -241,7 +269,7 @@ class SingleExeBuild {
/** Verify the closure before compiling or packaging. */
async verifyClosure(): Promise<void> {
await this.run('runtime dependency closure', pnpmBin(), ['run', 'verify-runtime-closure'])
await this.runPnpm('runtime dependency closure', ['run', 'verify-runtime-closure'])
}
/** Build all package artifacts unless `--skip-build` was passed. */
@@ -250,7 +278,7 @@ class SingleExeBuild {
console.log('build-exe-for-python-sdk: skipping pnpm run build (--skip-build)')
return
}
await this.run('build', pnpmBin(), ['run', 'build'])
await this.runPnpm('build', ['run', 'build'])
}
/** Clear and deploy the runtime closure into the node carrier. */
@@ -260,7 +288,7 @@ class SingleExeBuild {
}
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] rm -rf ${this.staging}`)
else await rm(this.staging, { recursive: true, force: true })
await this.run('deploy', pnpmBin(), [
await this.runPnpm('deploy', [
'--filter',
DEPLOY_ROOT_PACKAGE,
'deploy',
@@ -393,10 +421,11 @@ class SingleExeBuild {
* @returns the executable and ripgrep sidecar paths, plus the macOS spawn helper path when required.
*/
async pack(target: Target): Promise<string[]> {
const product = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
const productBase = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
const product = target.platform === 'win' ? `${productBase}.exe` : productBase
await this.prepareNativePty(target)
if (!this.cli.dryRun) await mkdir(this.outDir, { recursive: true })
await this.run(`pkg ${target.spec}`, pnpmBin(), [
await this.runPnpm(`pkg ${target.spec}`, [
'dlx',
PKG_SPEC,
this.staging,
@@ -424,16 +453,19 @@ class SingleExeBuild {
/** Copy the target ripgrep binary beside the executable so Node can spawn it outside pkg's virtual filesystem. */
private async copyRipgrepSidecar(target: Target, product: string): Promise<string> {
const platform = target.platform === 'macos' ? 'darwin' : target.platform
const platform = target.platform === 'macos' ? 'darwin' : target.platform === 'win' ? 'win32' : target.platform
const executable = target.platform === 'win' ? 'rg.exe' : 'rg'
const source = join(
this.staging,
'node_modules',
'@vscode',
`ripgrep-${platform}-${target.arch}`,
'bin',
'rg',
executable,
)
const destination = `${product}-rg`
const destination = target.platform === 'win'
? `${product.slice(0, -'.exe'.length)}-rg.exe`
: `${product}-rg`
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${destination}`)
return destination
@@ -455,7 +487,6 @@ class SingleExeBuild {
const stagedBuild = join(this.staging, 'node_modules', 'node-pty', 'build')
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] rm -rf ${stagedBuild}`)
else await rm(stagedBuild, { recursive: true, force: true })
if (target.platform !== 'linux') return
const packageDirectory = join(
root,
'packages',
@@ -464,6 +495,21 @@ class SingleExeBuild {
'node_modules',
'node-pty',
)
if (target.platform === 'win') {
if (target.arch !== 'x64') {
throw new Error('build-exe-for-python-sdk: Windows supports x64 only.')
}
const host = Target.host()
if (target.platform !== host.platform || target.arch !== host.arch) {
throw new Error(
'build-exe-for-python-sdk: build the Windows runtime under x64 Node on its target host; '
+ `target ${target.platform}-${target.arch} does not match host ${host.platform}-${host.arch}.`,
)
}
resolveWindowsNodePtyAddons(join(this.staging, 'node_modules', 'node-pty'), target.arch)
return
}
if (target.platform !== 'linux') return
const destination = join(stagedBuild, 'Release', 'pty.node')
const source = resolveLinuxNodePtyAddon(packageDirectory, target.arch)
if (this.cli.dryRun) {
@@ -553,6 +599,12 @@ class SingleExeBuild {
})
})
}
/** Run pnpm through its JavaScript entrypoint when the caller supplies one. */
private async runPnpm(label: string, args: string[]): Promise<void> {
const [command, invocationArgs] = pnpmInvocation(args)
await this.run(label, command, invocationArgs)
}
}
async function main(): Promise<void> {
+15 -7
View File
@@ -47,9 +47,12 @@ def load_platforms(path: Path = PLATFORM_MANIFEST) -> dict[str, tuple[str, str]]
PLATFORMS = load_platforms()
def runtime_suffixes(executable_name: str) -> tuple[str, ...]:
suffixes = ("", "-rg")
return (*suffixes, "-spawn-helper") if "-macos-" in executable_name else suffixes
def runtime_filenames(executable_name: str) -> tuple[str, ...]:
"""Return the exact platform payload names for one runtime executable."""
if executable_name.endswith(".exe"):
return (executable_name, f"{executable_name.removesuffix('.exe')}-rg.exe")
names = (executable_name, f"{executable_name}-rg")
return (*names, f"{executable_name}-spawn-helper") if "-macos-" in executable_name else names
def main() -> None:
@@ -205,13 +208,18 @@ def stage_sdk(destination: Path, version: str) -> None:
def stage_runtime(destination: Path, version: str, executable: Path, executable_name: str) -> None:
if executable.name != executable_name:
raise ValueError(
f"runtime executable must be named {executable_name}, got {executable.name}"
)
copy_package(ROOT / "python" / "sdk-runtime", destination)
stage_license_files(destination, include_notices=True)
rewrite_version(destination / "pyproject.toml", version)
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)
for suffix in runtime_suffixes(executable_name):
shutil.copy2(Path(f"{executable}{suffix}"), runtime_dir / f"{executable_name}{suffix}")
source_directory = executable.parent
for filename in runtime_filenames(executable_name):
shutil.copy2(source_directory / filename, runtime_dir / filename)
def verify_wheel(
@@ -250,13 +258,13 @@ def verify_wheel(
]
if package == "runtime":
assert platform is not None
expected_files = [f"{platform[1]}{suffix}" for suffix in runtime_suffixes(platform[1])]
expected_files = sorted(runtime_filenames(platform[1]))
found_files = sorted(Path(name).name for name in runtime_files)
if found_files != expected_files:
raise RuntimeError(f"{wheel} runtime payload must be {expected_files}, found {found_files}")
for runtime_file in runtime_files:
mode = archive.getinfo(runtime_file).external_attr >> 16
if mode & stat.S_IXUSR == 0:
if platform[0] != "win_amd64" and mode & stat.S_IXUSR == 0:
raise RuntimeError(f"{wheel} runtime executable lost its executable bit: {runtime_file}")
elif runtime_files:
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {runtime_files}")
+6 -7
View File
@@ -6,8 +6,9 @@ import { resolve } from 'node:path'
import { parseArgs } from 'node:util'
import {
CLIENT_BUILD_RECORD_PATH,
CLIENT_BUILD_PROFILE_SELECTOR,
clientBuildProcessEnvironment,
repositoryCommitHash,
repositoryClientBuildEnvironment,
resolveClientBuildEnvironment,
writeClientBuildRecord,
} from './client-build-environment.ts'
@@ -34,12 +35,10 @@ function main(): void {
allowPositionals: false,
})
const root = resolve(import.meta.dirname, '..')
const parentEnvironment = {
...process.env,
DSH_CLIENT_COMMIT_HASH: repositoryCommitHash(root, process.env),
}
const clientEnvironment = resolveClientBuildEnvironment(parentEnvironment, values.profile)
const buildEnvironment = clientBuildProcessEnvironment(parentEnvironment, clientEnvironment)
const repositoryEnvironment = repositoryClientBuildEnvironment(root, process.env)
const profile = values.profile ?? process.env[CLIENT_BUILD_PROFILE_SELECTOR]
const clientEnvironment = resolveClientBuildEnvironment(repositoryEnvironment, profile)
const buildEnvironment = clientBuildProcessEnvironment(process.env, clientEnvironment)
rmSync(resolve(root, CLIENT_BUILD_RECORD_PATH), { force: true })
runScript('build:lib', buildEnvironment)
+56 -21
View File
@@ -228,7 +228,7 @@ describe('CI workflow', () => {
name: 'python runtime / release-shaped matrix',
uses: './.github/workflows/build-exe-for-python-sdk.yml',
with: {
targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64',
targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64',
ci: true,
},
secrets: {
@@ -320,7 +320,7 @@ describe('Python release workflows', () => {
expect(build).toMatchObject({
uses: './.github/workflows/build-exe-for-python-sdk.yml',
with: {
targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64',
targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-win-x64',
release: true,
},
})
@@ -390,11 +390,19 @@ describe('Python release workflows', () => {
const manylinuxAddon = buildSteps.find(step => isRecord(step) && step.name === 'Rebuild Linux node-pty against manylinux 2.28')
const macosCheck = buildSteps.find(step => isRecord(step) && step.name === 'Check macOS deployment target')
const manylinuxSmoke = buildSteps.find(step => isRecord(step) && step.name === 'Run wheel in a manylinux 2.28 container')
const installedKeyless = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel keyless black-box tests')
const realApiPreflight = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test')
const installedRealApi = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test')
if (!isRecord(installedKeyless) || !isRecord(realApiPreflight) || !isRecord(installedRealApi)) {
throw new TypeError('Python wheel builder must define installed-wheel keyless and real API steps')
const cleanVenvPosix = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv (POSIX)')
const cleanVenvWindows = buildSteps.find(step => isRecord(step) && step.name === 'Install local SDK and runtime wheels into a clean venv (Windows)')
const installedKeylessPosix = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel keyless black-box tests (POSIX)')
const installedKeylessWindows = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel keyless black-box tests (Windows)')
const realApiPreflightPosix = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test (POSIX)')
const realApiPreflightWindows = buildSteps.find(step => isRecord(step) && step.name === 'Preflight installed-wheel real API test (Windows)')
const installedRealApiPosix = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test (POSIX)')
const installedRealApiWindows = buildSteps.find(step => isRecord(step) && step.name === 'Run installed-wheel real API black-box test (Windows)')
if (!isRecord(cleanVenvPosix) || !isRecord(cleanVenvWindows)
|| !isRecord(installedKeylessPosix) || !isRecord(installedKeylessWindows)
|| !isRecord(realApiPreflightPosix) || !isRecord(realApiPreflightWindows)
|| !isRecord(installedRealApiPosix) || !isRecord(installedRealApiWindows)) {
throw new TypeError('Python wheel builder must define native POSIX and Windows installed-wheel steps')
}
expect(call.inputs).toHaveProperty('targets')
expect(call.inputs).toMatchObject({
@@ -407,47 +415,56 @@ describe('Python release workflows', () => {
expect(workflow.concurrency).toMatchObject({
group: 'build-single-exe-${{ github.workflow }}-${{ github.ref }}',
})
expect(build.defaults).toBeUndefined()
expect(plan.if).toContain('inputs.ci')
expect(plan.if).toContain('inputs.release')
expect(JSON.stringify(plan.steps)).toContain('pep440_version')
const workflowJson = JSON.stringify(workflow)
expect(workflowJson).toContain('macosx_14_0_arm64')
expect(workflowJson).toContain('win_amd64')
expect(workflowJson).toContain('node24-win-x64')
expect(workflowJson).toContain('windows-2025')
expect(workflowJson).toContain('dist-python/$SDK_WHEEL')
expect(workflowJson).toContain('dist-python/$RUNTIME_WHEEL')
expect(workflowJson).toContain('/work/dist-python/$SDK_WHEEL')
expect(workflowJson).toContain('/work/dist-python/$RUNTIME_WHEEL')
expect(workflowJson).not.toContain('--find-links dist-python')
expect(workflowJson).not.toContain('--find-links /work/dist-python')
expect(workflowJson).not.toContain('cygpath')
expect(manylinuxAddon).toMatchObject({ if: "runner.os == 'Linux'" })
expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_x86_64')
expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_aarch64')
expect(JSON.stringify(manylinuxAddon)).toContain('npm_config_build_from_source=true pnpm run install')
expect(JSON.stringify(manylinuxAddon)).toContain('$HOME/setup-pnpm:$HOME/setup-pnpm:ro')
expect(JSON.stringify(manylinuxAddon)).toContain('pnpm_setup_root')
expect(JSON.stringify(manylinuxAddon)).toContain('$pnpm_setup_root:$pnpm_setup_root:ro')
expect(JSON.stringify(manylinuxAddon)).toContain('node-pty-glibc-versions.txt')
expect(JSON.stringify(manylinuxAddon)).toContain('le 2.28')
expect(macosCheck).toMatchObject({ if: "runner.os == 'macOS'" })
expect(JSON.stringify(macosCheck)).toContain('scripts/check-macos-deployment-target.py')
expect(JSON.stringify(macosCheck)).toContain('$EXE-spawn-helper')
expect(JSON.stringify(installedKeyless)).toContain('--scenario all')
expect(JSON.stringify(installedKeyless)).toContain('--installed-wheel')
expect(JSON.stringify(installedKeyless)).toContain('env -u PYTHONPATH')
expect(JSON.stringify(installedKeyless)).toContain('-u DSH_RUNTIME_MODE')
expect(realApiPreflight).toMatchObject({
expect(JSON.stringify(installedKeylessPosix)).toContain('--scenario all')
expect(JSON.stringify(installedKeylessPosix)).toContain('env -u PYTHONPATH')
expect(JSON.stringify(installedKeylessWindows)).toContain('--scenario all --installed-wheel')
expect(installedKeylessWindows).toMatchObject({ if: "runner.os == 'Windows'", shell: 'pwsh' })
expect(cleanVenvWindows).toMatchObject({ if: "runner.os == 'Windows'", shell: 'pwsh' })
expect(JSON.stringify(cleanVenvWindows)).toContain('Scripts\\\\python.exe')
expect(realApiPreflightPosix).toMatchObject({
env: { DEEPSEEK_API_KEY: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}' },
})
expect(String(realApiPreflight.if)).toContain('inputs.ci')
expect(String(realApiPreflight.if)).toContain('head.repo.fork')
expect(String(realApiPreflight.if)).toContain('dependabot[bot]')
expect(installedRealApi).toMatchObject({
expect(String(realApiPreflightPosix.if)).toContain('inputs.ci')
expect(String(realApiPreflightPosix.if)).toContain('head.repo.fork')
expect(String(realApiPreflightPosix.if)).toContain('dependabot[bot]')
expect(realApiPreflightWindows).toMatchObject({ shell: 'pwsh' })
expect(installedRealApiPosix).toMatchObject({
env: {
DEEPSEEK_API_KEY: '${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}',
DEEPSEEK_BASE_URL: 'https://api.deepseek.com',
},
})
expect(installedRealApi.if).toBe(realApiPreflight.if)
expect(JSON.stringify(installedRealApi)).toContain('--scenario sdk-live')
expect(JSON.stringify(installedRealApi)).toContain('--installed-wheel')
expect(JSON.stringify(installedRealApi)).toContain('-u DSH_RUNTIME_MODE')
expect(JSON.stringify(installedRealApiPosix)).toContain('--scenario sdk-live')
expect(JSON.stringify(installedRealApiPosix)).toContain('-u DSH_RUNTIME_MODE')
expect(installedRealApiWindows).toMatchObject({ shell: 'pwsh' })
expect(JSON.stringify(installedRealApiWindows)).toContain('--scenario sdk-live --installed-wheel')
expect(manylinuxSmoke).toMatchObject({ if: "runner.os == 'Linux'" })
expect(JSON.stringify(manylinuxSmoke)).toContain('-e DSH_TELEMETRY_DISABLED')
})
@@ -469,6 +486,24 @@ describe('Python release workflows', () => {
expect(macosCheck).toContain('scripts/check-macos-deployment-target.py')
expect(macosCheck).toContain('"$EXE" "$EXE-spawn-helper"')
})
it('builds and black-box tests the Windows x64 wheel in GitLab', () => {
const workflow = loadWorkflow('.gitlab-ci.yml')
const windows = workflow['runtime-windows-x64']
const publish = workflow['publish-python']
if (!isRecord(windows) || !Array.isArray(windows.before_script) || !Array.isArray(windows.script)
|| !isRecord(publish) || !Array.isArray(publish.needs)) {
throw new TypeError('GitLab CI must define the Windows runtime and aggregate publication jobs')
}
expect(windows.tags).toEqual(['windows-x64'])
expect(windows.variables).toMatchObject({ PKG_TARGET: 'node24-win-x64', PLATFORM: 'win-x64' })
expect(JSON.stringify(windows.before_script)).toContain('.ci-python\\\\Scripts')
expect(JSON.stringify(windows.before_script)).toContain('[IO.Path]::PathSeparator')
expect(JSON.stringify(windows.script)).toContain('win_amd64.whl')
expect(JSON.stringify(windows.script)).toContain('--scenario all --installed-wheel')
expect(publish.needs).toContainEqual({ job: 'runtime-windows-x64', artifacts: true })
})
})
describe('Issue lifecycle workflow', () => {
@@ -1,3 +1,4 @@
import { execFileSync } from 'node:child_process'
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
@@ -7,8 +8,12 @@ import {
assertClientBuildEnvironment,
clientBuildEnvironmentDefines,
clientBuildProcessEnvironment,
officialClientBuildEnvironment,
readClientBuildRecord,
repositoryClientBuildEnvironment,
repositoryCommitHash,
repositoryGitDirty,
repositoryVersion,
resolveClientBuildEnvironment,
writeClientBuildRecord,
} from './client-build-environment.ts'
@@ -51,12 +56,34 @@ function buildFixture(environment: Record<string, string>): string {
return fixtureRoot
}
function git(root: string, args: readonly string[]): string {
return execFileSync('git', [...args], {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
}).trim()
}
function repositoryFixture(version = '1.2.3-rc.4'): string {
const fixtureRoot = mkdtempSync(join(tmpdir(), 'dsh-client-build-repository-'))
roots.push(fixtureRoot)
write(join(fixtureRoot, 'package.json'), `${JSON.stringify({ version })}\n`)
write(join(fixtureRoot, 'tracked.txt'), 'committed\n')
git(fixtureRoot, ['init'])
git(fixtureRoot, ['config', 'user.name', 'DSH test'])
git(fixtureRoot, ['config', 'user.email', 'dsh-test@example.invalid'])
git(fixtureRoot, ['add', 'package.json', 'tracked.txt'])
git(fixtureRoot, ['commit', '-m', 'fixture'])
return fixtureRoot
}
describe('client build environment', () => {
it('requires an exact public environment for a named artifact profile', () => {
const expected = {
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.3',
} as const
expect(() => { assertClientBuildEnvironment({ PATH: '/bin', ...expected }, expected) }).not.toThrow()
@@ -73,7 +100,9 @@ describe('client build environment', () => {
DSH_BUILD_CLIENT_PROFILE: 'official',
DSH_CLIENT_BUILD_PROFILE: 'local',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_GIT_DIRTY: 'true',
DSH_CLIENT_TITLE: 'Local title',
DSH_CLIENT_VERSION: '1.2.3',
DSH_CLIENT_EXTRA: 'local-extra',
}
@@ -84,24 +113,108 @@ describe('client build environment', () => {
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.3',
})
expect(() => {
resolveClientBuildEnvironment({ DSH_BUILD_CLIENT_PROFILE: 'official' })
}).toThrow(/DSH_CLIENT_COMMIT_HASH/)
expect(() => {
resolveClientBuildEnvironment({
DSH_BUILD_CLIENT_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
})
}).toThrow(/DSH_CLIENT_VERSION/)
expect(() => { resolveClientBuildEnvironment({}, 'unknown') }).toThrow(/unknown client build profile/)
expect(clientBuildProcessEnvironment(parent, {
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.3',
})).toEqual({
PATH: '/bin',
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.3',
})
expect(repositoryCommitHash('/unused', { DSH_CLIENT_COMMIT_HASH: COMMIT_HASH })).toBe(COMMIT_HASH.slice(0, 7))
})
it('owns repository version, commit, and dirty metadata for complete builds', () => {
const fixtureRoot = repositoryFixture()
const commit = git(fixtureRoot, ['rev-parse', '--short=7', 'HEAD'])
expect(repositoryVersion(fixtureRoot)).toBe('1.2.3-rc.4')
expect(repositoryGitDirty(fixtureRoot)).toBe(false)
expect(repositoryClientBuildEnvironment(fixtureRoot, {
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH,
DSH_CLIENT_EXTRA: 'preserved',
DSH_CLIENT_GIT_DIRTY: 'true',
DSH_CLIENT_VERSION: 'spoofed',
})).toEqual({
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_EXTRA: 'preserved',
DSH_CLIENT_VERSION: '1.2.3-rc.4',
})
expect(officialClientBuildEnvironment(fixtureRoot)).toEqual({
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: commit,
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.3-rc.4',
})
write(join(fixtureRoot, '.gitignore'), 'ignored.txt\n')
git(fixtureRoot, ['add', '.gitignore'])
git(fixtureRoot, ['commit', '-m', 'ignore fixture'])
write(join(fixtureRoot, 'ignored.txt'), 'ignored\n')
expect(repositoryGitDirty(fixtureRoot)).toBe(false)
rmSync(join(fixtureRoot, 'ignored.txt'))
write(join(fixtureRoot, 'tracked.txt'), 'unstaged\n')
expect(repositoryGitDirty(fixtureRoot)).toBe(true)
write(join(fixtureRoot, 'tracked.txt'), 'committed\n')
expect(repositoryGitDirty(fixtureRoot)).toBe(false)
write(join(fixtureRoot, 'tracked.txt'), 'staged\n')
git(fixtureRoot, ['add', 'tracked.txt'])
expect(repositoryGitDirty(fixtureRoot)).toBe(true)
git(fixtureRoot, ['commit', '-m', 'staged fixture'])
expect(repositoryGitDirty(fixtureRoot)).toBe(false)
write(join(fixtureRoot, 'untracked.txt'), 'untracked\n')
expect(repositoryGitDirty(fixtureRoot)).toBe(true)
expect(repositoryClientBuildEnvironment(fixtureRoot, {
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH,
})).toEqual({
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_GIT_DIRTY: 'true',
DSH_CLIENT_VERSION: '1.2.3-rc.4',
})
rmSync(join(fixtureRoot, 'untracked.txt'))
const submoduleSource = repositoryFixture('9.8.7')
git(fixtureRoot, ['-c', 'protocol.file.allow=always', 'submodule', 'add', submoduleSource, 'submodule'])
git(fixtureRoot, ['commit', '-am', 'submodule fixture'])
expect(repositoryGitDirty(fixtureRoot)).toBe(false)
write(join(fixtureRoot, 'submodule/tracked.txt'), 'modified submodule\n')
expect(repositoryGitDirty(fixtureRoot)).toBe(true)
})
it('omits dirty metadata when repository metadata is unavailable', () => {
const fixtureRoot = mkdtempSync(join(tmpdir(), 'dsh-client-build-no-git-'))
roots.push(fixtureRoot)
write(join(fixtureRoot, 'package.json'), '{"version":"2.0.0"}\n')
expect(repositoryGitDirty(fixtureRoot)).toBeUndefined()
expect(repositoryClientBuildEnvironment(fixtureRoot, {
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH,
DSH_CLIENT_GIT_DIRTY: 'true',
})).toEqual({
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_VERSION: '2.0.0',
})
})
it('defines only public client values over a non-enumerable fallback', () => {
expect(clientBuildEnvironmentDefines({
PATH: '/bin',
@@ -151,6 +264,7 @@ describe('client build environment', () => {
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: COMMIT_HASH.slice(0, 7),
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.3',
}
const official = buildFixture(officialEnvironment)
const defaultBuild = buildFixture({})
+84 -2
View File
@@ -1,5 +1,5 @@
import { createHash } from 'node:crypto'
import { execFileSync } from 'node:child_process'
import { execFileSync, spawnSync } from 'node:child_process'
import {
existsSync,
globSync,
@@ -25,6 +25,9 @@ const OFFICIAL_CLIENT_BUILD_ENVIRONMENT = {
/** Public variable carrying the source commit embedded in client artifacts. */
const CLIENT_COMMIT_HASH_VARIABLE = 'DSH_CLIENT_COMMIT_HASH'
/** Public variable carrying the repository package version embedded in client artifacts. */
const CLIENT_VERSION_VARIABLE = 'DSH_CLIENT_VERSION'
/** Repository-relative path of the complete client build record. */
export const CLIENT_BUILD_RECORD_PATH = '.dsh-build/client-build-environment.json'
@@ -57,6 +60,76 @@ export function repositoryCommitHash(root: string, environment: NodeJS.ProcessEn
return value.slice(0, 7).toLowerCase()
}
/**
* Resolve the repository package version used by browser build metadata.
* @param root - repository root containing the authoritative package.json.
* @returns the repository's semver-compatible package version.
*/
export function repositoryVersion(root: string): string {
const path = resolve(root, 'package.json')
let manifest: unknown
try {
manifest = JSON.parse(readFileSync(path, 'utf8'))
} catch (error) {
const detail = error instanceof Error ? error.message : String(error)
throw new Error(`cannot read repository version from ${path}: ${detail}`)
}
if (!isObject(manifest) || typeof manifest.version !== 'string'
|| !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.version)) {
throw new Error(`repository package.json has an invalid version ${JSON.stringify(isObject(manifest) ? manifest.version : undefined)}`)
}
return manifest.version
}
/**
* Read whether Git reports any staged, unstaged, untracked, or submodule change.
* @param root - repository root whose worktree is inspected.
* @returns true or false inside a Git worktree; undefined without Git metadata.
*/
export function repositoryGitDirty(root: string): boolean | undefined {
const probe = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
})
if (probe.error !== undefined || probe.status !== 0 || probe.stdout.trim() !== 'true') return undefined
const status = spawnSync('git', ['status', '--porcelain=v1', '--untracked-files=normal'], {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
if (status.error !== undefined) throw status.error
if (status.status !== 0) {
throw new Error(`git status failed in ${root}: ${status.stderr.trim() || String(status.status)}`)
}
return status.stdout !== ''
}
/**
* Resolve the public environment for a complete default build from one checkout.
* Repository-owned metadata replaces inherited values; other public values pass through.
* @param root - repository root supplying version and Git metadata.
* @param environment - caller environment supplying optional commit and public extensions.
* @returns complete public client environment for the default build.
*/
export function repositoryClientBuildEnvironment(
root: string,
environment: NodeJS.ProcessEnv = process.env,
): ClientBuildEnvironment {
const inherited = { ...clientBuildEnvironment(environment) }
delete inherited.DSH_CLIENT_COMMIT_HASH
delete inherited.DSH_CLIENT_GIT_DIRTY
delete inherited.DSH_CLIENT_VERSION
const dirty = repositoryGitDirty(root)
return {
...inherited,
DSH_CLIENT_COMMIT_HASH: repositoryCommitHash(root, environment),
...(dirty === true ? { DSH_CLIENT_GIT_DIRTY: 'true' } : {}),
DSH_CLIENT_VERSION: repositoryVersion(root),
}
}
/**
* Resolve the exact public values required by an official build at one commit.
* @param root - repository root whose HEAD must match the built source.
@@ -69,6 +142,7 @@ export function officialClientBuildEnvironment(
): Readonly<Record<`DSH_CLIENT_${string}`, string>> {
return {
DSH_CLIENT_COMMIT_HASH: repositoryCommitHash(root, environment),
DSH_CLIENT_VERSION: repositoryVersion(root),
...OFFICIAL_CLIENT_BUILD_ENVIRONMENT,
}
}
@@ -115,10 +189,18 @@ export function resolveClientBuildEnvironment(
if (profile === undefined) return clientBuildEnvironment(environment)
if (profile === 'official') {
const commitHash = environment[CLIENT_COMMIT_HASH_VARIABLE]
const version = environment[CLIENT_VERSION_VARIABLE]
if (commitHash === undefined) {
throw new Error(`${CLIENT_COMMIT_HASH_VARIABLE} is required for the official client build profile`)
}
return { DSH_CLIENT_COMMIT_HASH: commitHash, ...OFFICIAL_CLIENT_BUILD_ENVIRONMENT }
if (version === undefined) {
throw new Error(`${CLIENT_VERSION_VARIABLE} is required for the official client build profile`)
}
return {
DSH_CLIENT_COMMIT_HASH: commitHash,
DSH_CLIENT_VERSION: version,
...OFFICIAL_CLIENT_BUILD_ENVIRONMENT,
}
}
throw new Error(`unknown client build profile ${JSON.stringify(profile)}; expected "official"`)
}
+40 -2
View File
@@ -1,7 +1,10 @@
/**
* Pins shared client-bundle preset rules: the module-edge purity gate and
* the physical watch dependencies hidden behind virtual CSS Modules.
* Pins shared client-bundle preset rules: module-edge purity, source-map
* chaining, and physical watch dependencies hidden behind virtual CSS Modules.
*/
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it, vi } from 'vitest'
import { clientBundle, requestedExternals } from '../packages/client/tsdown.client.ts'
@@ -14,6 +17,11 @@ interface CssModulePlugin {
load?: (this: { addWatchFile: (id: string) => void }, id: string) => Promise<unknown>
}
interface SourceMapPlugin {
name: string
load?: (id: string) => Promise<unknown>
}
/** A representative dynamic bundle using the shared client baseline. */
const REQUESTING_PACKAGE = '@deepseek-ai/dsh-client-ui-conversation'
@@ -59,6 +67,14 @@ function cssModulePlugin(): CssModulePlugin {
return plugin
}
function sourceMapPlugin(): SourceMapPlugin {
const configs = clientConfigs()
const plugins = (configs[0] as { plugins: SourceMapPlugin[] }).plugins
const plugin = plugins.find(candidate => candidate.name === 'dsh-tsc-sourcemap')
if (plugin?.load === undefined) throw new Error('tsc sourcemap plugin missing from client config')
return plugin
}
describe('client bundle purity gate', () => {
const resolveId = purityResolveId()
@@ -143,6 +159,28 @@ describe('client bundle debug artifacts', () => {
it('emits source maps for plugin TS and TSX outside the Vite module graph', () => {
const configs = clientConfigs()
expect(configs[0]?.sourcemap).toBe(true)
expect(configs[0]?.outputOptions).toMatchObject({ sourcemapExcludeSources: false })
})
it('chains emitted tsc maps when the production Client build consumes lib/types', async () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-client-sourcemap-'))
try {
const entry = join(root, 'lib', 'types', 'client', 'index.js')
const source = join(root, 'src', 'client', 'index.ts')
const map = { version: 3, names: [], mappings: 'AAAA', sources: ['../../../src/client/index.ts'] }
mkdirSync(join(root, 'lib', 'types', 'client'), { recursive: true })
mkdirSync(join(root, 'src', 'client'), { recursive: true })
writeFileSync(entry, 'export const marker = true\n//# sourceMappingURL=index.js.map\n')
writeFileSync(`${entry}.map`, JSON.stringify(map))
writeFileSync(source, 'export const marker: true = true\n')
await expect(sourceMapPlugin().load!(entry)).resolves.toEqual({
code: 'export const marker = true',
map: { ...map, sourcesContent: ['export const marker: true = true\n'] },
})
} finally {
rmSync(root, { recursive: true, force: true })
}
})
it('maps first-party sources to their repository package paths', () => {
+5 -5
View File
@@ -14,14 +14,14 @@ describe('cordisConfigFiles', () => {
it('finds Loader YAML without treating translation records as configs', () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-cordis-config-files-'))
roots.push(root)
for (const directory of ['.claude', 'docs', 'examples', 'node_modules/pkg', 'vendor/pkg']) {
for (const directory of ['.claude', 'apps/cli/config/examples', 'docs', 'node_modules/pkg', 'vendor/pkg']) {
mkdirSync(join(root, directory), { recursive: true })
}
for (const file of [
'.claude/hidden.cordis.yml',
'docs/cordis-primer.i18n.yaml',
'examples/agent.cordis.yaml',
'examples/headless.cordis.yml',
'apps/cli/config/examples/agent.cordis.yaml',
'apps/cli/config/examples/headless.cordis.yml',
'node_modules/pkg/hidden.cordis.yml',
'vendor/pkg/hidden.cordis.yml',
]) {
@@ -29,8 +29,8 @@ describe('cordisConfigFiles', () => {
}
expect(cordisConfigFiles(root)).toEqual([
join('examples', 'agent.cordis.yaml'),
join('examples', 'headless.cordis.yml'),
join('apps', 'cli', 'config', 'examples', 'agent.cordis.yaml'),
join('apps', 'cli', 'config', 'examples', 'headless.cordis.yml'),
])
})
})
-1
View File
@@ -17,7 +17,6 @@ const allSpecs = new Set([
...globSync('packages/*/*/tests/**/*.spec.ts', { cwd: root }),
...globSync('packages/*/*/tests/**/*.spec.tsx', { cwd: root }),
...globSync('apps/*/tests/**/*.spec.ts', { cwd: root }),
...globSync('examples/*/tests/**/*.spec.ts', { cwd: root }),
...globSync('scripts/**/*.spec.ts', { cwd: root }),
].map(path => path.replaceAll('\\', '/')))
+9 -11
View File
@@ -1,20 +1,18 @@
/** Boot the ACP Code Mode overlay. Requires a DeepSeek API key. */
/** Run one headless task through the shipped Code Mode composition. Requires a model credential. */
import { spawn } from 'node:child_process'
if (process.argv.length > 2) {
console.error('usage: pnpm run demo:code-mode')
process.exit(2)
}
const task = process.argv.slice(2).join(' ').trim()
|| 'Inspect this repository with Code Mode and report its top-level architecture.'
const child = spawn(process.execPath, [
'--import',
'tsx/esm',
'apps/cli/src/bin.ts',
'--profile',
'acp',
'--patch',
'examples/acp-agent/cordis.yml',
'--patch',
'examples/acp-agent/code-mode.cordis.yml',
], { stdio: 'inherit' })
'headless',
task,
], {
stdio: 'inherit',
env: { ...process.env, DSH_TOOLS_MODE: 'code' },
})
child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) })
-26
View File
@@ -1,26 +0,0 @@
/**
* Boot the self-referential Cordis tools under Web or ACP, defaulting to Web. This is a repository demo wrapper, not a product CLI feature.
*/
import { spawn } from 'node:child_process'
const SURFACES = new Map([
// The browser surface with the cordis toolset layered on: `dsh web --config`
// applies this overlay over the shipped web composition; it owns port 3081.
['web', ['--import', 'tsx/esm', 'apps/cli/src/bin.ts', 'web', '--patch', 'examples/web-cordis/cordis.yml']],
['acp', [
'--import', 'tsx/esm', 'apps/cli/src/bin.ts', '--profile', 'acp',
'--patch', 'examples/acp-agent/cordis.yml',
'--patch', 'examples/acp-agent/cordis-tools.cordis.yml',
]],
])
const surface = process.argv[2] ?? 'web'
const args = SURFACES.get(surface)
if (args === undefined || process.argv.length > 3) {
console.error('usage: pnpm run demo:cordis [web|acp]')
process.exit(2)
}
if (surface === 'web') console.log('Cordis Web: http://127.0.0.1:3081')
const child = spawn(process.execPath, args, { stdio: 'inherit' })
child.on('exit', (code, signal) => { process.exit(signal === null ? code ?? 1 : 1) })
+39 -1
View File
@@ -3,7 +3,45 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { expect, it } from 'vitest'
import type { TsdownBundle } from 'tsdown'
import { discoverLibraryDirs, discoverPluginDirs, watchClientPlugins } from './dev-web.ts'
import { writeClientBuildRecord } from './client-build-environment.ts'
import {
devWebBuildEnvironment,
discoverLibraryDirs,
discoverPluginDirs,
watchClientPlugins,
} from './dev-web.ts'
it('samples one local environment at startup without validating watcher outputs', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-dev-web-environment-'))
try {
await mkdir(join(root, 'apps/web/dist'), { recursive: true })
await mkdir(join(root, 'packages/client/example/lib'), { recursive: true })
await writeFile(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' }))
await writeFile(join(root, 'apps/web/dist/index.html'), '<main></main>')
await writeFile(join(root, 'packages/client/example/lib/client.js'), 'module.exports = {}\n')
writeClientBuildRecord(root, {
DSH_CLIENT_BUILD_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: 'fffffff',
DSH_CLIENT_TITLE: 'DeepSeek Harness',
DSH_CLIENT_VERSION: '1.2.2',
})
await writeFile(join(root, 'packages/client/example/lib/client.js'), 'module.exports = { changed: true }\n')
expect(devWebBuildEnvironment(root, {
PATH: '/bin',
DSH_BUILD_CLIENT_PROFILE: 'official',
DSH_CLIENT_COMMIT_HASH: 'abc1234',
DSH_CLIENT_EXTRA: 'launch-value',
})).toEqual({
PATH: '/bin',
DSH_CLIENT_COMMIT_HASH: 'abc1234',
DSH_CLIENT_EXTRA: 'launch-value',
DSH_CLIENT_VERSION: '1.2.3',
})
} finally {
await rm(root, { recursive: true, force: true })
}
})
it('discovers dsh.client packages with sibling roles', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-dev-web-discovery-'))
+28
View File
@@ -34,6 +34,11 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
import { execa } from 'execa'
import { build } from 'tsdown'
import type { TsdownBundle } from 'tsdown'
import {
CLIENT_BUILD_PROFILE_SELECTOR,
clientBuildProcessEnvironment,
repositoryClientBuildEnvironment,
} from './client-build-environment.ts'
const repoRoot = fileURLToPath(new URL('..', import.meta.url))
@@ -49,6 +54,19 @@ const SHELL_PACKAGE = '@deepseek-ai/dsh-web-frontend'
*/
const TEST_INFRASTRUCTURE_PREFIX = 'packages/test-support/'
/**
* Sample one local public environment for every long-lived watcher stage.
* @param root - repository root supplying version and Git metadata.
* @param environment - watcher launch environment supplying public extensions.
* @returns process environment shared by tsdown and spawned watcher stages.
*/
export function devWebBuildEnvironment(
root: string,
environment: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
return clientBuildProcessEnvironment(environment, repositoryClientBuildEnvironment(root, environment))
}
/**
* Discover the watch workspace by declaration: every packages/<group>/<name>
* whose package.json carries `dsh.client` with platform "web" is a client
@@ -175,6 +193,16 @@ interface StageHandle {
const invokedPath = process.argv[1]
const isMain = invokedPath !== undefined && import.meta.url === pathToFileURL(resolve(invokedPath)).href
if (isMain) {
const buildEnvironment = devWebBuildEnvironment(repoRoot, process.env)
for (const name of Object.keys(process.env)) {
if (name === CLIENT_BUILD_PROFILE_SELECTOR || name.startsWith('DSH_CLIENT_')) {
Reflect.deleteProperty(process.env, name)
}
}
for (const [name, value] of Object.entries(buildEnvironment)) {
if (name.startsWith('DSH_CLIENT_') && value !== undefined) process.env[name] = value
}
const pluginDirs = discoverPluginDirs()
const libraryDirs = discoverLibraryDirs()
if (pluginDirs.length === 0) {
-1
View File
@@ -5,7 +5,6 @@
"docs/cordis-primer.md": 600,
"docs/defensive-patterns.md": 550,
"docs/testing.md": 1150,
"examples/AGENTS.md": 310,
"packages/AGENTS.md": 675,
"packages/README.md": 994
}
+5
View File
@@ -397,6 +397,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
PrepareSessionOptions: 'persistence.md',
SessionHeader: 'persistence.md',
SessionInspection: 'persistence.md',
BorrowedSessionSource: 'persistence.md',
SessionLocation: 'persistence.md',
SessionPreparation: 'persistence.md',
SessionPersistenceSnapshot: 'persistence.md',
@@ -436,6 +437,8 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
SessionEventTraceRequest: 'session-query.md',
SessionEventWindow: 'session-query.md',
SessionLineageTrace: 'session-query.md',
SessionObservation: 'session-query.md',
SessionObservationOptions: 'session-query.md',
SessionRecord: 'session-query.md',
SessionResultFilter: 'session-query.md',
SessionSearchExecContext: 'session-query.md',
@@ -574,6 +577,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
WorkspaceOrderValue: 'workspace.md',
WorkspaceRenameRequest: 'workspace.md',
WorkspaceValue: 'workspace.md',
ClientArtifactBaseline: 'client-modules.md',
WebBootGraph: 'client-modules.md',
SessionTelemetryRecord: 'session-telemetry.md',
WorkflowRunInfo: 'workflow.md',
@@ -602,6 +606,7 @@ export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
'Error',
'EntryTree',
'Exclude',
'Extract',
'Map',
'NonNullable',
'Omit',
+1 -25
View File
@@ -773,31 +773,13 @@ const APP_EXAMPLES = [
config: 'packages/bundle/base/cordis.patch.yml',
summary: 'The dsh-base bundle patch shared by the web, headless, sdk, and acp profiles; their mode bundles and user layers patch over it, while sdk-minimal owns a separate standalone tree.',
},
{
id: 'headless',
rel: 'examples/headless-agent/composition.md',
title: 'Headless Agent Snapshot Composition',
label: 'examples/headless-agent',
config: 'examples/headless-agent/cordis.yml',
summary: 'The headless snapshot composition combines the real DeepSeek adapter and coding capabilities with one explicitly configured persisted top-level agent; its JSONL driver is test-only.',
},
{
id: 'acp',
rel: 'examples/acp-agent/composition.md',
title: 'ACP Automation Profile Patch',
label: 'examples/acp-agent',
config: 'examples/acp-agent/cordis.yml',
summary: 'The ACP example patches the shipped base + acp-app profile for demos and snapshots; dsh owns launch, and the ACP bridge exposes fresh automation sessions without a stdout logger or pre-created agent.',
},
]
type AppExample = typeof APP_EXAMPLES[number]
function renderAppComposition(example: AppExample): string {
const plugins = parseExampleCordis(example.config)
const maintenance = example.id === 'acp'
? 'hybrid: the patch row list is parsed from its `cordis.yml`; the scope summary is curated'
: 'hybrid: the leaf plugin list is parsed from its `cordis.yml`; app package expansion is curated from package source'
const maintenance = 'hybrid: the patch row list is parsed from its `cordis.yml`; app package expansion is curated from package source'
const lines = generatedHeader(example.title)
lines.push(
example.summary,
@@ -1440,9 +1422,6 @@ function renderIndex(docs: GraphDoc[]): string {
const labels: Record<string, string> = {
'docs/capability-seams.md': 'capability seams and core services',
'apps/cli/composition.md': 'dsh shared base composition',
'examples/headless-agent/composition.md': 'headless-agent app composition',
'examples/cordis-agent/composition.md': 'cordis-agent app composition',
'examples/acp-agent/composition.md': 'acp-agent app composition',
'docs/event-producer-consumer.md': 'event producer/consumer matrix',
'docs/agent-lifecycle.md': 'agent turn and step lifecycle',
'docs/tool-execution-pipeline.md': 'tool execution pipeline',
@@ -1450,9 +1429,6 @@ function renderIndex(docs: GraphDoc[]): string {
const modes: Record<string, string> = {
'docs/capability-seams.md': 'hybrid generated',
'apps/cli/composition.md': 'hybrid generated',
'examples/headless-agent/composition.md': 'hybrid generated',
'examples/cordis-agent/composition.md': 'hybrid generated',
'examples/acp-agent/composition.md': 'hybrid generated',
'docs/event-producer-consumer.md': 'hybrid generated',
'docs/agent-lifecycle.md': 'curated',
'docs/tool-execution-pipeline.md': 'curated',
-1
View File
@@ -339,7 +339,6 @@ describe('manifestPatterns', () => {
'tools/*/package.json',
'native/landlock-run/package.json',
'native/landlock-run/packages/*/package.json',
'examples/*/package.json',
])
})
})
+2 -6
View File
@@ -24,8 +24,8 @@ const ALL_KINDS = ['dependencies', 'devDependencies', 'optionalDependencies', 'p
/**
* Workspace areas that never reach a user: repository tooling and gates (the
* root manifest), test infrastructure, the documentation site, the runnable
* demo leaves, and the native launcher's build workspace. A runtime
* root manifest), test infrastructure, the documentation site, and the native
* launcher's build workspace. A runtime
* declaration by anything outside these areas is a disclosure-relevant
* runtime dependency because any plugin package can be mounted from a user's
* `cordis.yml`.
@@ -35,7 +35,6 @@ const DEV_ONLY_AREAS = [
'packages/test-support/',
'packages/test-support/client-runtime/',
'website/',
'examples/',
'native/',
] as const
@@ -136,9 +135,6 @@ export function manifestPatterns(rootMembers: readonly string[]): string[] {
return [
'package.json',
...rootMembers.map(member => `${member}/package.json`),
// The demo leaves join the workspace through `examples/package.json`, so
// their own manifests are members of nothing and no glob above reaches them.
'examples/*/package.json',
]
}
+1 -1
View File
@@ -56,7 +56,7 @@ describe('Oxlint executable contract', () => {
// A test under packages/client states its face in the filename, so the
// probe carries the Client suffix to reach the Client aggregate.
['client package test', 'packages/client/ui-trajectory/tests', 'tsconfig.client.json', '.client.ts'],
['example', 'examples/headless-agent/tests', 'tsconfig.host.json'],
['CLI profile test', 'apps/cli/tests/profiles/headless/tests', 'tsconfig.host.json'],
['website', 'website', 'tsconfig.host.json'],
] as const
const source = `export function probePromise(): Promise<void> {
+5 -2
View File
@@ -29,6 +29,7 @@ function write(path: string, content: string): void {
function buildFixture(environment: Record<string, string>): string {
const root = mkdtempSync(join(tmpdir(), 'dsh-release-build-'))
roots.push(root)
write(join(root, 'package.json'), `${JSON.stringify({ version: environment.DSH_CLIENT_VERSION ?? '0.0.1' })}\n`)
write(join(root, 'apps/web/dist/index.html'), '<main></main>')
write(join(root, 'packages/client/example/lib/client.js'), 'module.exports = {}\n')
writeClientBuildRecord(root, environment)
@@ -106,11 +107,13 @@ describe('release families', () => {
vi.stubEnv('DSH_CLIENT_COMMIT_HASH', officialEnvironment.DSH_CLIENT_COMMIT_HASH)
const official = buildFixture(officialEnvironment)
const defaultBuild = buildFixture({})
const missing = join(defaultBuild, 'missing')
write(join(missing, 'package.json'), `${JSON.stringify({ version: officialEnvironment.DSH_CLIENT_VERSION })}\n`)
expect(() => { dsh.verifyBuildArtifacts(official) }).not.toThrow()
expect(() => { dsh.verifyBuildArtifacts(defaultBuild) }).toThrow(/DSH_CLIENT_TITLE/)
expect(() => { dsh.verifyBuildArtifacts(join(defaultBuild, 'missing')) }).toThrow(/record.*missing/)
expect(() => { vendor.verifyBuildArtifacts(join(defaultBuild, 'missing')) }).not.toThrow()
expect(() => { dsh.verifyBuildArtifacts(missing) }).toThrow(/record.*missing/)
expect(() => { vendor.verifyBuildArtifacts(missing) }).not.toThrow()
write(join(official, 'packages/client/example/lib/client.js'), 'module.exports = { changed: true }\n')
expect(() => { dsh.verifyBuildArtifacts(official) }).toThrow(/artifacts differ/)
-17
View File
@@ -194,23 +194,6 @@ const EXACT_EDITS: readonly ExactEdit[] = [
errors.push(\`\${label}: @deepseek-ai/cordis peer (\${peer}) and dev (\${dev}) ranges must match\`)`,
expect: 1,
},
{
// The rescoped name is already covered by the `@deepseek-ai/.+` pattern beside it.
id: 'knip-logger-console',
file: 'knip.json',
find: ` "ignoreDependencies": [
"@cordisjs/plugin-logger-console",
"@deepseek-ai/.+"
]
},
"packages/host/directory-picker-auto": {`,
replace: ` "ignoreDependencies": [
"@deepseek-ai/.+"
]
},
"packages/host/directory-picker-auto": {`,
expect: 1,
},
{
id: 'knip-bundle-base',
file: 'knip.json',
+6 -2
View File
@@ -160,7 +160,7 @@ describe('gate graph validation', () => {
},
)
it('keeps native Windows coverage blocking while retaining the observational inventory', () => {
it('keeps native Windows coverage blocking and behind the complete build', () => {
const complete = withPnpmEntrypoint(() => gatesForMode('ci-windows-complete'))
const observational = withPnpmEntrypoint(() => gatesForMode('ci-windows-observational'))
.filter(gate => gate.id !== 'build' && gate.id !== 'docs-site-build')
@@ -168,6 +168,7 @@ describe('gate graph validation', () => {
expect(byId.get('coverage')?.allowFailure).not.toBe(true)
expect(byId.get('coverage-exempt-heavy')?.allowFailure).not.toBe(true)
expect(byId.get('coverage')?.needs).toContain('build')
expect(byId.get('coverage-exempt-heavy')?.needs).toContain('build')
expect(observational).not.toHaveLength(0)
for (const gate of observational) {
@@ -387,7 +388,7 @@ describe('Node 24 lane ownership', () => {
const subject = withPnpmEntrypoint(() => gatesForMode('ci-consumers'))
expect(defaultConcurrency('ci-consumers', subject.length, 4)).toEqual({
workers: 10,
workers: 11,
source: 'ci-consumers gate count',
})
expect(subject.map(item => item.id)).toEqual([
@@ -397,6 +398,7 @@ describe('Node 24 lane ownership', () => {
'built-package-invariants',
'lint-and-duplication',
'snapshot',
'expected-output',
'web-snapshot',
'doc-typecheck',
'node-next-types',
@@ -413,6 +415,7 @@ describe('Node 24 lane ownership', () => {
expect(subject.find(item => item.id === 'lint-and-duplication')?.needs).toEqual(['built-package-invariants'])
for (const id of [
'snapshot',
'expected-output',
'web-snapshot',
'doc-typecheck',
'node-next-types',
@@ -421,6 +424,7 @@ describe('Node 24 lane ownership', () => {
expect(subject.find(item => item.id === id)?.needs).toEqual(['built-package-invariants'])
}
expect(subject.find(item => item.id === 'snapshot')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' })
expect(subject.find(item => item.id === 'expected-output')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' })
expect(subject.find(item => item.id === 'doc-typecheck')?.env).toEqual({
DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1',
})
+18 -7
View File
@@ -241,6 +241,7 @@ export function gatesForMode(selected: Mode): Gate[] {
pnpmScript('issue-management', 'test:issue-management', { label: 'Issue management policy' }),
pnpmScript('duplication', 'duplication'),
snapshotGate(),
expectedOutputGate(),
pnpmScript('build', 'build'),
pnpmScript('build:web', 'build:web'),
...hygieneLeafGates({ artifactNeeds: ['build'] }),
@@ -427,6 +428,7 @@ function ciConsumerGates(): Gate[] {
needs: validatedBuild,
}),
snapshotGate(validatedBuild),
expectedOutputGate(validatedBuild),
webSnapshotGate(validatedBuild),
pnpmScript('doc-typecheck', 'doc-typecheck:contracts-ready', {
needs: validatedBuild,
@@ -471,9 +473,10 @@ function ciWindowsBlockingGates(): Gate[] {
}
function ciWindowsCompleteGates(): Gate[] {
const coverage = coverageGates().map(gate => gate.id === 'coverage-exempt-heavy'
? { ...gate, needs: [...new Set(['build', ...(gate.needs ?? [])])] }
: gate)
const coverage = coverageGates().map(gate => ({
...gate,
needs: [...new Set(['build', ...(gate.needs ?? [])])],
}))
const coverageAfter = coverage.map(gate => gate.id)
const observational = ciWindowsObservationalGates()
// The required production site replaces the observational MPA build; both
@@ -585,9 +588,8 @@ function coverageGates(): Gate[] {
]
}
// Example and package snapshots boot their bins in `lib` mode (built artifacts under plain Node,
// plugins via real exports); script snapshots execute their real source entry path.
// Callers wait either on `build` or on a validation gate that transitively owns that build.
// Recorded-session adapters boot process scenarios in `lib` mode. Callers wait
// either on `build` or on a validation gate that transitively owns that build.
function snapshotGate(needs: string[] = ['build']): Gate {
return pnpmScript('snapshot', 'test:snapshot', {
env: { DSH_EXAMPLE_MODE: 'lib' },
@@ -595,6 +597,15 @@ function snapshotGate(needs: string[] = ['build']): Gate {
})
}
// Owner-local process expectations consume built package exports without entering
// the recorded-session corpus or the credentialed provider lane.
function expectedOutputGate(needs: string[] = ['build']): Gate {
return pnpmScript('expected-output', 'test:expected', {
env: { DSH_EXAMPLE_MODE: 'lib' },
needs,
})
}
function builtPackageInvariantsGate(needs?: string[]): Gate {
return pnpmScript('built-package-invariants', 'verify-built-package-invariants', {
label: 'built package invariants',
@@ -697,7 +708,7 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate {
'run',
'--config',
'vitest.e2e.config.ts',
'examples/headless-agent/tests/keyless-smoke.e2e.ts',
'apps/cli/tests/profiles/headless/tests/keyless-smoke.e2e.ts',
'apps/cli/tests/built-bin.e2e.ts',
'packages/host/directory-picker-native/tests/built-worker.e2e.ts',
'packages/sdk/server/tests/built-scope-carrier.e2e.ts',
@@ -1,17 +0,0 @@
/** Repository-wide canonical-layout check for committed session snapshots. */
import { resolve } from 'node:path'
import { expect, it } from 'vitest'
import { inspectSessionFixtureLayouts } from './session-fixture-layout.ts'
const root = resolve(import.meta.dirname, '..')
it('keeps every session-format JSONL fixture projected into canonical packed layout', () => {
const nonCanonical = inspectSessionFixtureLayouts(root)
.filter(fixture => fixture.source !== fixture.canonical)
.map(fixture => fixture.path)
expect(
nonCanonical,
'Run `pnpm run migrate:packed-session-fixtures` and commit the mechanical fixture rewrite.',
).toEqual([])
})
+17 -1
View File
@@ -1,9 +1,15 @@
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { type SessionEvent } from '@deepseek-ai/dsh-session'
import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
import { canonicalSessionFixture, isPhysicalSessionFixture } from './session-fixture-layout.ts'
import {
canonicalSessionFixture,
inspectSessionFixtureLayouts,
isPhysicalSessionFixture,
} from './session-fixture-layout.ts'
const HEADER = ' {"type":"session","version":0,"id":"fixture","createdAt":1,"delegationDepth":0} '
const root = resolve(import.meta.dirname, '..')
function chunkRun(): SessionEvent[] {
return Array.from({ length: 4 }, (_, index) => ({
@@ -80,3 +86,13 @@ describe('isPhysicalSessionFixture', () => {
expect(isPhysicalSessionFixture('apps/web/tests/snapshots/example/session.jsonl')).toBe(false)
})
})
it('keeps every session-format JSONL fixture projected into canonical packed layout', () => {
const nonCanonical = inspectSessionFixtureLayouts(root)
.filter(fixture => fixture.source !== fixture.canonical)
.map(fixture => fixture.path)
expect(
nonCanonical,
'Run `pnpm run migrate:packed-session-fixtures` and commit the mechanical fixture rewrite.',
).toEqual([])
})
+180
View File
@@ -0,0 +1,180 @@
/** Repository-wide ownership and storage invariants for the recorded-session corpus. */
import { existsSync } from 'node:fs'
import { lstat, readFile, readdir, realpath } from 'node:fs/promises'
import { dirname, join, relative, resolve } from 'node:path'
import { expect, it } from 'vitest'
import {
captureExpectedWorkspaceSnapshot,
EMPTY_WORKSPACE_MARKER,
parseSnapshotManifest,
redactSessionSnapshotIds,
scrubSystemPrompts,
scrubToolSchemas,
sessionFixtureNames,
type SnapshotManifest,
} from '@deepseek-ai/dsh-session-snapshot'
const repoRoot = resolve(import.meta.dirname, '..')
const corpusRoot = join(repoRoot, 'snapshots')
const profiles = ['acp', 'sdk', 'session', 'web'] as const
const snapshotAdapters = [
'apps/web/tests/message-feedback-protocol.snapshot.ts',
'apps/web/tests/minimal-preset.snapshot.ts',
'snapshots/acp/acp.snapshot.ts',
'snapshots/sdk/sdk.snapshot.ts',
'snapshots/session/headless.snapshot.ts',
] as const
interface Scenario {
readonly key: string
readonly profile: string
readonly name: string
readonly dir: string
readonly manifest: SnapshotManifest & {
composition: string
recording: 'live' | 'authored'
header: NonNullable<SnapshotManifest['header']>
}
}
async function scenarios(): Promise<Scenario[]> {
const result: Scenario[] = []
for (const profile of profiles) {
const root = join(corpusRoot, profile)
for (const entry of await readdir(root, { withFileTypes: true })) {
if (!entry.isDirectory()) continue
const dir = join(root, entry.name)
const path = join(dir, 'snapshot.yml')
expect(existsSync(path), `${profile}/${entry.name}/snapshot.yml`).toBe(true)
const manifest = parseSnapshotManifest(await readFile(path, 'utf8'), path)
expect(manifest.scenario, `${profile}/${entry.name}: scenario`).toBe(entry.name)
expect(manifest.profile, `${profile}/${entry.name}: profile`).toBe(profile === 'session' ? 'headless' : profile)
expect(manifest.composition, `${profile}/${entry.name}: composition`).toBeTypeOf('string')
expect(manifest.recording, `${profile}/${entry.name}: recording`).toMatch(/^(live|authored)$/)
expect(manifest.header, `${profile}/${entry.name}: header`).toBeDefined()
result.push({
key: `${profile}/${entry.name}`,
profile,
name: entry.name,
dir,
manifest: {
...manifest,
composition: manifest.composition as string,
recording: manifest.recording as 'live' | 'authored',
header: manifest.header as NonNullable<SnapshotManifest['header']>,
},
})
}
}
return result
}
function referencedScenario(owner: Scenario, source: string): string {
return source.includes('/') ? source : `${owner.profile}/${source}`
}
async function snapshotNamedTests(): Promise<string[]> {
const files: string[] = []
const visit = async (directory: string, relativeDir: string): Promise<void> => {
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (['dist', 'lib', 'node_modules'].includes(entry.name)) continue
await visit(join(directory, entry.name), join(relativeDir, entry.name))
} else if (entry.isFile() && /\.snapshot\.tsx?$/u.test(entry.name)) {
files.push(join(relativeDir, entry.name).split(/[/\\]/u).join('/'))
}
}
}
for (const root of ['apps', 'native', 'packages', 'python', 'scripts', 'snapshots', 'website']) {
await visit(join(repoRoot, root), root)
}
return files.sort()
}
it('reserves the snapshot test suffix for recorded-session adapters', async () => {
expect(await snapshotNamedTests()).toEqual([...snapshotAdapters])
})
it('keeps every recorded session owned, pinned, redacted, and header-scrubbed', async () => {
const all = await scenarios()
const byKey = new Map(all.map(scenario => [scenario.key, scenario]))
const pinByClass = new Map<string, Scenario>()
for (const scenario of all) {
if (scenario.manifest.header.pin !== true) continue
const key = `${scenario.manifest.composition}/${scenario.manifest.header.class}`
expect(pinByClass.has(key), `${key}: duplicate header pin`).toBe(false)
pinByClass.set(key, scenario)
}
for (const scenario of all) {
const { manifest, dir, key } = scenario
const classKey = `${manifest.composition}/${manifest.header.class}`
expect(pinByClass.has(classKey), `${key}: missing composition/header pin ${classKey}`).toBe(true)
const localSession = join(dir, 'session.jsonl')
if (manifest.session === undefined) {
expect(existsSync(localSession), `${key}: owner session.jsonl`).toBe(true)
} else {
expect(existsSync(localSession), `${key}: borrower must not own session.jsonl`).toBe(false)
const target = resolve(dir, manifest.session.source)
expect(existsSync(target), `${key}: session source`).toBe(true)
const targetDir = await realpath(dirname(target))
const sourceKey = relative(corpusRoot, targetDir).split(/[/\\]/).join('/')
expect(byKey.has(sourceKey), `${key}: session source must name a corpus owner`).toBe(true)
expect(byKey.get(sourceKey)?.manifest.session, `${key}: session source cannot chain through a borrower`).toBeUndefined()
}
expect(existsSync(join(dir, 'replay.override.json')), `${key}: replay override presence`)
.toBe(manifest.replay?.override === true)
expect(existsSync(join(dir, 'workspace.expected')), `${key}: final workspace presence`)
.toBe(manifest.workspace?.final === true)
if (manifest.workspace?.final === true) {
const expectedRoot = join(dir, 'workspace.expected')
const expectedWorkspace = await captureExpectedWorkspaceSnapshot(expectedRoot)
expect(existsSync(join(expectedRoot, EMPTY_WORKSPACE_MARKER)), `${key}: empty workspace marker`)
.toBe(expectedWorkspace.length === 0)
}
expect(existsSync(join(dir, 'input.json')), `${key}: executable input metadata is ACP-only`)
.toBe(scenario.profile === 'acp')
if (scenario.profile !== 'acp') {
expect(existsSync(join(dir, 'stdout.expected.jsonl')), `${key}: ACP transcript outside ACP`).toBe(false)
}
if (manifest.header.pin === true) {
const promptSource = byKey.get(referencedScenario(scenario, manifest.header.systemPromptSource ?? scenario.name))
const schemaSource = byKey.get(referencedScenario(scenario, manifest.header.toolSchemasSource ?? scenario.name))
expect(promptSource, `${key}: system-prompt source`).toBeDefined()
expect(schemaSource, `${key}: tool-schema source`).toBeDefined()
expect(existsSync(join((promptSource as Scenario).dir, 'system-prompt.expected.md')), `${key}: system-prompt sidecar`).toBe(true)
expect(existsSync(join((schemaSource as Scenario).dir, 'tool-schemas.expected.json')), `${key}: tool-schema sidecar`).toBe(true)
for (const [field, source] of [
['system-prompt.expected.md', promptSource],
['tool-schemas.expected.json', schemaSource],
] as const) {
const local = join(dir, field)
if (!existsSync(local) || !(await lstat(local)).isSymbolicLink()) continue
expect(await realpath(local), `${key}: ${field} symlink follows its manifest source`)
.toBe(await realpath(join((source as Scenario).dir, field)))
}
}
if (manifest.session !== undefined) continue
const names = sessionFixtureNames(await readdir(dir))
const fixtures = await Promise.all(names.map(name => readFile(join(dir, name), 'utf8')))
expect(redactSessionSnapshotIds(fixtures), `${key}: typed identity fixed point`).toEqual(fixtures)
for (const [index, fixture] of fixtures.entries()) {
expect(scrubSystemPrompts(fixture), `${key}/${names[index]}: system prompt must be a sidecar`).toBe(fixture)
expect(scrubToolSchemas(fixture), `${key}/${names[index]}: tool schemas must be a sidecar`).toBe(fixture)
}
for (const index of manifest.header.childSystemPrompts ?? []) {
expect(names[index], `${key}: child prompt index ${index}`).toBeDefined()
expect(existsSync(join(dir, `system-prompt.${index}.expected.md`)), `${key}: child prompt sidecar ${index}`).toBe(true)
}
for (const index of manifest.header.childToolSchemas ?? []) {
expect(names[index], `${key}: child schema index ${index}`).toBeDefined()
expect(existsSync(join(dir, `tool-schemas.${index}.expected.json`)), `${key}: child schema sidecar ${index}`).toBe(true)
}
}
})
+36 -20
View File
@@ -30,7 +30,7 @@ CODE_PROMPT = "Use run_code to compute the packaged worker smoke value."
CODE_WORKER_TEXT = "code worker smoke ok"
WORKFLOW_PROMPT = "Use workflow to compute the packaged worker smoke value without agents."
WORKFLOW_WORKER_TEXT = "workflow worker smoke ok"
MINIMAL_PROMPT = "Exercise the packaged minimal agent's persistent Bash and string-replacement editor."
MINIMAL_PROMPT = "Exercise the packaged minimal agent's persistent shell and string-replacement editor."
MINIMAL_TEXT = "minimal agent smoke ok"
MINIMAL_EDITOR_PATH_PREFIX = "Editor path: "
FS_SEARCH_PROMPT = "Exercise the packaged filesystem search tools."
@@ -41,11 +41,20 @@ MCP_TEXT = "MCP client smoke ok"
PROFILE_PLUGIN_PROMPT = "Verify the Python-installed dsh profile plugin."
PROFILE_PLUGIN_TEXT = "profile plugin smoke ok"
PROFILE_PLUGIN_MARKER = "PYTHON_INSTALLED_DSH_PROFILE_PLUGIN"
MINIMAL_BASH_COMMAND = (
"counter=$(( ${counter:-0} + 1 )); export counter; "
"printf 'COUNT=%s CWD=%s\\n' \"$counter\" \"$PWD\"; "
"if [ \"$counter\" -eq 1 ]; then cd /tmp; fi"
IS_WINDOWS = sys.platform == "win32"
MINIMAL_SHELL_TOOL = "pwsh" if IS_WINDOWS else "bash"
MINIMAL_SHELL_COMMAND = (
"$global:dshSdkCounter = [int]$global:dshSdkCounter + 1; "
'Write-Output "COUNT=$global:dshSdkCounter CWD=$((Get-Location).Path)"; '
"if ($global:dshSdkCounter -eq 1) { Set-Location $env:TEMP }"
if IS_WINDOWS
else (
"counter=$(( ${counter:-0} + 1 )); export counter; "
"printf 'COUNT=%s CWD=%s\\n' \"$counter\" \"$PWD\"; "
"if [ \"$counter\" -eq 1 ]; then cd /tmp; fi"
)
)
MINIMAL_SHELL_SECOND_CWD = str(Path(tempfile.gettempdir()).resolve()) if IS_WINDOWS else "/tmp"
LEGACY_CUSTOM_DISABLED_ROWS = (
"agent-instructions",
"goal",
@@ -108,6 +117,8 @@ ADVANCED_SNAPSHOT_FILENAMES = ("result.json", "session.jsonl", "session.1.jsonl"
MINIMAL_SNAPSHOT_DIRECTORY = (
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "minimal"
)
if IS_WINDOWS:
MINIMAL_SNAPSHOT_DIRECTORY /= "win-x64"
MINIMAL_SNAPSHOT_FILENAMES = ("model-visible.json",)
RESTART_SNAPSHOT_DIRECTORY = (
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "restart"
@@ -301,8 +312,8 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
if minimal_prompt is not None:
return tool_call_chunks(
"minimal-bash-1",
"bash",
{"command": MINIMAL_BASH_COMMAND},
MINIMAL_SHELL_TOOL,
{"command": MINIMAL_SHELL_COMMAND},
)
scenario_prompts = {
SNAPSHOT_DIRECT_CHILD_PROMPT,
@@ -438,17 +449,18 @@ def minimal_tool_followup(
"""Verify the checked-in minimal composition's PTY and editor."""
if not call_id.startswith("minimal-"):
return None
if call_id == "minimal-bash-1" and tool_name == "bash":
if call_id == "minimal-bash-1" and tool_name == MINIMAL_SHELL_TOOL:
if "COUNT=1" not in tool_text:
raise AssertionError(f"first persistent bash call lost its output: {tool_text}")
raise AssertionError(f"first persistent shell call lost its output: {tool_text}")
return tool_call_chunks(
"minimal-bash-2",
"bash",
{"command": MINIMAL_BASH_COMMAND},
MINIMAL_SHELL_TOOL,
{"command": MINIMAL_SHELL_COMMAND},
)
if call_id == "minimal-bash-2" and tool_name == "bash":
if "COUNT=2 CWD=/tmp" not in tool_text:
raise AssertionError(f"persistent bash did not retain state: {tool_text}")
if call_id == "minimal-bash-2" and tool_name == MINIMAL_SHELL_TOOL:
expected = f"COUNT=2 CWD={MINIMAL_SHELL_SECOND_CWD}"
if expected.lower() not in tool_text.lower():
raise AssertionError(f"persistent shell did not retain state: {tool_text}")
messages = body.get("messages")
if not isinstance(messages, list):
raise AssertionError("persistent editor smoke request has no messages")
@@ -800,8 +812,9 @@ def smoke_sdk_live() -> None:
sessions = dsh_home / "sessions"
marker = root / "live-api-marker.txt"
session_id = "installed-wheel-live-api"
shell_tool = "pwsh" if IS_WINDOWS else "bash"
create_prompt = (
"Use the bash tool to create the file at the absolute path below with exactly one line "
f"Use the {shell_tool} tool to create the file at the absolute path below with exactly one line "
f"containing {LIVE_API_SENTINEL}. Then reply with exactly {LIVE_API_SENTINEL}.\n{marker}"
)
verify_prompt = (
@@ -845,8 +858,8 @@ def smoke_sdk_live() -> None:
raise AssertionError(f"{label} turn returned {result.final_response!r}")
if not marker.is_file():
raise AssertionError(f"real-model tool turn did not create {marker}")
if marker.read_bytes() != f"{LIVE_API_SENTINEL}\n".encode():
raise AssertionError(f"real-model tool turn wrote unexpected bytes to {marker}")
if marker.read_text(encoding="utf-8").splitlines() != [LIVE_API_SENTINEL]:
raise AssertionError(f"real-model tool turn wrote unexpected text to {marker}")
assert_zstd_session_log(sessions)
@@ -921,6 +934,7 @@ def smoke_sdk_custom(base_url: str, executable: Path) -> None:
{"id": "session-log-deepseek", "config": {"enabled": True}},
*({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
{"id": "tool-bash", "disabled": True},
{"id": "tool-pwsh", "disabled": True},
{
"id": "tool-subagent",
"config": {
@@ -989,7 +1003,7 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
raise AssertionError(f"minimal agent run emitted no final response: {result.events}")
if editor_path.read_text() != "created by packaged editor\n":
raise AssertionError(f"packaged editor wrote unexpected content: {editor_path.read_text()!r}")
assert_session_log(sessions, root, MINIMAL_TEXT, "COUNT=1", "COUNT=2 CWD=/tmp")
assert_session_log(sessions, root, MINIMAL_TEXT, "COUNT=1", "COUNT=2")
files = build_minimal_snapshot_files(MockModelHandler.requests[first_request:], root)
compare_snapshot_files(
@@ -1105,7 +1119,7 @@ def smoke_sdk_profile_plugin(base_url: str) -> None:
"insert": [{"id": "python-sdk-blackbox-plugin", "name": "dsh-python-blackbox-plugin"}],
}], indent=2))
dsh = Path(sysconfig.get_path("scripts")) / "dsh"
dsh = Path(sysconfig.get_path("scripts")) / ("dsh.exe" if IS_WINDOWS else "dsh")
environment = {**os.environ, "DSH_HOME": str(dsh_home)}
installed = subprocess.run(
[str(dsh), "plugin", "--profile", "sdk", "add", f"file:{plugin}"],
@@ -1170,6 +1184,7 @@ def smoke_sdk_snapshot(base_url: str, executable: Path, update_snapshots: bool)
{"id": "session-log-deepseek", "config": {"enabled": True}},
*({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
{"id": "tool-bash", "disabled": True},
{"id": "tool-pwsh", "disabled": True},
{
"id": "tool-subagent",
"config": {
@@ -1243,6 +1258,7 @@ def smoke_sdk_restart_snapshot(base_url: str, executable: Path, update_snapshots
{"id": "session-log-deepseek", "config": {"enabled": True}},
*({"id": row_id, "disabled": True} for row_id in LEGACY_CUSTOM_DISABLED_ROWS),
{"id": "tool-bash", "disabled": True},
{"id": "tool-pwsh", "disabled": True},
{
"id": "tool-subagent",
"config": {
@@ -1758,7 +1774,7 @@ def compare_snapshot_files(
if update:
directory.mkdir(parents=True, exist_ok=True)
for name, content in files.items():
(directory / name).write_text(content, encoding="utf-8")
(directory / name).write_text(content, encoding="utf-8", newline="\n")
print(f"smoke-python-runtime: updated snapshots in {directory}")
existing = {
@@ -81,7 +81,7 @@
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent Bash and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
}
]
},
@@ -167,7 +167,7 @@
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent Bash and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
},
{
"role": "assistant",
@@ -267,7 +267,7 @@
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent Bash and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
},
{
"role": "assistant",
@@ -381,7 +381,7 @@
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent Bash and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}/created.txt"
},
{
"role": "assistant",
@@ -0,0 +1,430 @@
[
{
"tools": [
{
"type": "function",
"function": {
"name": "pwsh",
"description": "Run commands in a PowerShell shell\n* When invoking this tool, the contents of the \"command\" parameter does NOT need to be XML-escaped.\n* You don't have access to the internet via this tool.\n* State is persistent across command calls and discussions with the user.\n* Use native Windows paths (C:\\...) and $env:NAME variables; this is PowerShell, not bash.\n* Please avoid commands that may produce a very large amount of output.\n* Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The PowerShell command to run. Relative path is preferred in the command."
}
},
"required": [
"command"
]
}
}
},
{
"type": "function",
"function": {
"name": "str_replace_editor",
"description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.",
"enum": [
"view",
"create",
"str_replace",
"insert"
]
},
"path": {
"type": "string",
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`."
},
"file_text": {
"type": "string",
"description": "Required parameter of `create` command, with the content of the file to be created."
},
"insert_line": {
"type": "integer",
"description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`."
},
"new_str": {
"type": "string",
"description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert."
},
"old_str": {
"type": "string",
"description": "Required parameter of `str_replace` command containing the string in `path` to replace."
},
"view_range": {
"type": "array",
"description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
"items": {
"type": "integer"
}
}
},
"required": [
"command",
"path"
]
}
}
}
],
"messages": [
{
"role": "system",
"text": "You are a helpful software engineer assistant."
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}\\created.txt"
}
]
},
{
"tools": [
{
"type": "function",
"function": {
"name": "pwsh",
"description": "Run commands in a PowerShell shell\n* When invoking this tool, the contents of the \"command\" parameter does NOT need to be XML-escaped.\n* You don't have access to the internet via this tool.\n* State is persistent across command calls and discussions with the user.\n* Use native Windows paths (C:\\...) and $env:NAME variables; this is PowerShell, not bash.\n* Please avoid commands that may produce a very large amount of output.\n* Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The PowerShell command to run. Relative path is preferred in the command."
}
},
"required": [
"command"
]
}
}
},
{
"type": "function",
"function": {
"name": "str_replace_editor",
"description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.",
"enum": [
"view",
"create",
"str_replace",
"insert"
]
},
"path": {
"type": "string",
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`."
},
"file_text": {
"type": "string",
"description": "Required parameter of `create` command, with the content of the file to be created."
},
"insert_line": {
"type": "integer",
"description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`."
},
"new_str": {
"type": "string",
"description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert."
},
"old_str": {
"type": "string",
"description": "Required parameter of `str_replace` command containing the string in `path` to replace."
},
"view_range": {
"type": "array",
"description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
"items": {
"type": "integer"
}
}
},
"required": [
"command",
"path"
]
}
}
}
],
"messages": [
{
"role": "system",
"text": "You are a helpful software engineer assistant."
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}\\created.txt"
},
{
"role": "assistant",
"toolCalls": [
{
"id": "minimal-bash-1",
"name": "pwsh"
}
]
},
{
"role": "tool",
"toolCallId": "minimal-bash-1",
"text": "{{tool-result}}"
}
]
},
{
"tools": [
{
"type": "function",
"function": {
"name": "pwsh",
"description": "Run commands in a PowerShell shell\n* When invoking this tool, the contents of the \"command\" parameter does NOT need to be XML-escaped.\n* You don't have access to the internet via this tool.\n* State is persistent across command calls and discussions with the user.\n* Use native Windows paths (C:\\...) and $env:NAME variables; this is PowerShell, not bash.\n* Please avoid commands that may produce a very large amount of output.\n* Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The PowerShell command to run. Relative path is preferred in the command."
}
},
"required": [
"command"
]
}
}
},
{
"type": "function",
"function": {
"name": "str_replace_editor",
"description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.",
"enum": [
"view",
"create",
"str_replace",
"insert"
]
},
"path": {
"type": "string",
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`."
},
"file_text": {
"type": "string",
"description": "Required parameter of `create` command, with the content of the file to be created."
},
"insert_line": {
"type": "integer",
"description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`."
},
"new_str": {
"type": "string",
"description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert."
},
"old_str": {
"type": "string",
"description": "Required parameter of `str_replace` command containing the string in `path` to replace."
},
"view_range": {
"type": "array",
"description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
"items": {
"type": "integer"
}
}
},
"required": [
"command",
"path"
]
}
}
}
],
"messages": [
{
"role": "system",
"text": "You are a helpful software engineer assistant."
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}\\created.txt"
},
{
"role": "assistant",
"toolCalls": [
{
"id": "minimal-bash-1",
"name": "pwsh"
}
]
},
{
"role": "tool",
"toolCallId": "minimal-bash-1",
"text": "{{tool-result}}"
},
{
"role": "assistant",
"toolCalls": [
{
"id": "minimal-bash-2",
"name": "pwsh"
}
]
},
{
"role": "tool",
"toolCallId": "minimal-bash-2",
"text": "{{tool-result}}"
}
]
},
{
"tools": [
{
"type": "function",
"function": {
"name": "pwsh",
"description": "Run commands in a PowerShell shell\n* When invoking this tool, the contents of the \"command\" parameter does NOT need to be XML-escaped.\n* You don't have access to the internet via this tool.\n* State is persistent across command calls and discussions with the user.\n* Use native Windows paths (C:\\...) and $env:NAME variables; this is PowerShell, not bash.\n* Please avoid commands that may produce a very large amount of output.\n* Please run long lived commands in the background, e.g. 'Start-Job' or start a server with Start-Process.",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The PowerShell command to run. Relative path is preferred in the command."
}
},
"required": [
"command"
]
}
}
},
{
"type": "function",
"function": {
"name": "str_replace_editor",
"description": "Custom editing tool for viewing, creating and editing files\n* State is persistent across command calls and discussions with the user\n* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep\n* The `create` command cannot be used if the specified `path` already exists as a file\n* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`\n\nNotes for using the `str_replace` command:\n* The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces!\n* If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique\n* The `new_str` parameter should contain the edited lines that should replace the `old_str`",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.",
"enum": [
"view",
"create",
"str_replace",
"insert"
]
},
"path": {
"type": "string",
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`."
},
"file_text": {
"type": "string",
"description": "Required parameter of `create` command, with the content of the file to be created."
},
"insert_line": {
"type": "integer",
"description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`."
},
"new_str": {
"type": "string",
"description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert."
},
"old_str": {
"type": "string",
"description": "Required parameter of `str_replace` command containing the string in `path` to replace."
},
"view_range": {
"type": "array",
"description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
"items": {
"type": "integer"
}
}
},
"required": [
"command",
"path"
]
}
}
}
],
"messages": [
{
"role": "system",
"text": "You are a helpful software engineer assistant."
},
{
"role": "user",
"text": "Exercise the packaged minimal agent's persistent shell and string-replacement editor.\nEditor path: {{cwd}}\\created.txt"
},
{
"role": "assistant",
"toolCalls": [
{
"id": "minimal-bash-1",
"name": "pwsh"
}
]
},
{
"role": "tool",
"toolCallId": "minimal-bash-1",
"text": "{{tool-result}}"
},
{
"role": "assistant",
"toolCalls": [
{
"id": "minimal-bash-2",
"name": "pwsh"
}
]
},
{
"role": "tool",
"toolCallId": "minimal-bash-2",
"text": "{{tool-result}}"
},
{
"role": "assistant",
"toolCalls": [
{
"id": "minimal-editor",
"name": "str_replace_editor"
}
]
},
{
"role": "tool",
"toolCallId": "minimal-editor",
"text": "{{tool-result}}"
}
]
}
]
+1 -1
View File
@@ -120,7 +120,7 @@ describe('global test invariant host', () => {
it('mounts the owning package companion while leaving non-package roots service-only', () => {
expect(testInvariantCompanionPaths('/repo/packages/core/tools/tests/tools.spec.ts'))
.toEqual(['../packages/core/tools/src/invariant.ts'])
expect(testInvariantCompanionPaths('/repo/examples/echo-agent/tests/echo.spec.ts')).toEqual([])
expect(testInvariantCompanionPaths('/repo/apps/cli/tests/profiles/headless/example.spec.ts')).toEqual([])
expect(testInvariantCompanionPaths('/repo/scripts/test-invariants.spec.ts'))
.toEqual(Object.keys(testInvariantCompanions).sort())
})
+1 -1
View File
@@ -303,7 +303,7 @@ describe('translation scope discovery', () => {
'packages/example/guide.md',
'packages/example/CONTRIBUTING.md',
'packages/example/BRAND_GUIDELINES.md',
'examples/tutorial.md',
'other/tutorial.md',
'website/reference.md',
'packages/example/README.txt',
'vendor/example/README.md',
+15
View File
@@ -1771,11 +1771,26 @@
"symbol": "WebBootEntry",
"source": "packages/client/modules/src/client/manifest.ts"
},
{
"doc": "docs/subsystems/client-modules.md",
"symbol": "WebBootBatchPhase",
"source": "packages/client/modules/src/client/manifest.ts"
},
{
"doc": "docs/subsystems/client-modules.md",
"symbol": "WebBootBatch",
"source": "packages/client/modules/src/client/manifest.ts"
},
{
"doc": "docs/subsystems/client-modules.md",
"symbol": "WebBootGraph",
"source": "packages/client/modules/src/client/manifest.ts"
},
{
"doc": "docs/subsystems/client-modules.md",
"symbol": "ClientArtifactBaseline",
"source": "packages/client/modules/src/index.ts"
},
{
"doc": "docs/subsystems/session-telemetry.md",
"symbol": "SessionTelemetrySharingStatus",
@@ -65,12 +65,12 @@ describe('application entrypoints', () => {
])
})
it('rejects an unclassified executable in an example workspace', () => {
it('rejects an unclassified executable in an app workspace', () => {
const root = fixture()
write(root, 'examples/rogue/src/bin.ts', '#!/usr/bin/env node\n')
write(root, 'apps/rogue/src/bin.ts', '#!/usr/bin/env node\n')
expect(applicationEntrypointViolations(root)).toEqual([
'examples/rogue/src/bin.ts: executable source has no application/build/test classification',
'apps/rogue/src/bin.ts: executable source has no application/build/test classification',
])
})
+8 -14
View File
@@ -32,25 +32,23 @@ const MANIFEST_BIN_ALLOWLIST = new Map<string, ManifestBin>([
/** Every executable in a Node application workspace has one explicit role. */
const EXECUTABLE_SOURCE_ALLOWLIST = new Map<string, string>([
['apps/cli/src/bin.ts', 'supported dsh application launcher'],
['examples/acp-agent/tests/fixtures/shell/tool-pwsh/driver.ts', 'test-only subprocess driver'],
['examples/acp-agent/tests/fixtures/subagent/subagent-acp/driver.ts', 'test-only subprocess driver'],
['examples/acp-agent/tests/fixtures/subagent/subagent-claude-code/driver.ts', 'test-only subprocess driver'],
['examples/acp-agent/tests/fixtures/subagent/subagent-codex/driver.ts', 'test-only subprocess driver'],
['examples/headless-agent/tests/fixtures/headless-driver.ts', 'test-only subprocess driver'],
['examples/headless-agent/tests/fixtures/session-telemetry-otel-driver.ts', 'test-only subprocess driver'],
['examples/headless-agent/tests/fixtures/time-context-driver.ts', 'test-only subprocess driver'],
['examples/python-sdk-agent/tests/fixtures/subagent/subagent-dsh-sdk/driver.ts', 'test-only subprocess driver'],
['packages/context/time-context/tests/fixtures/driver.ts', 'test-only subprocess driver'],
['packages/experimental/webworker-packer/bin.js', 'private build-only wrapper'],
['packages/experimental/webworker-packer/src/bin.ts', 'private build-only implementation'],
['packages/sdk/client/tests/fake-runtime.ts', 'test-only SDK runtime peer'],
['packages/session/session-telemetry-otel/tests/fixtures/driver.ts', 'test-only subprocess driver'],
['packages/shell/tool-pwsh/tests/fixtures/loader/driver.ts', 'test-only subprocess driver'],
['packages/subagent/subagent-acp/tests/fixtures/loader/driver.ts', 'test-only subprocess driver'],
['packages/subagent/subagent-claude-code/tests/fixtures/loader/driver.ts', 'test-only subprocess driver'],
['packages/subagent/subagent-codex/tests/fixtures/loader/driver.ts', 'test-only subprocess driver'],
['packages/subagent/subagent-dsh-sdk/tests/fixtures/loader/driver.ts', 'test-only subprocess driver'],
['packages/test-support/loader-smoke/tests/fixtures/headless-driver.ts', 'test-only subprocess driver'],
['packages/test-support/llm-mock-server/src/bin.ts', 'test-only model server'],
])
/** Root demos are application wrappers and therefore must visibly select dsh. */
const ROOT_DEMO_POLICIES = new Map<string, DemoPolicy>([
['demo:acp', { kind: 'dsh-direct' }],
['demo:code-mode', { kind: 'dsh-wrapper', wrapper: 'scripts/demo-code-mode.mjs' }],
['demo:cordis', { kind: 'dsh-wrapper', wrapper: 'scripts/demo-cordis.mjs' }],
])
const SOURCE_PATTERNS = [
@@ -62,10 +60,6 @@ const SOURCE_PATTERNS = [
'apps/**/*.js',
'apps/**/*.mjs',
'apps/**/*.cjs',
'examples/**/*.ts',
'examples/**/*.js',
'examples/**/*.mjs',
'examples/**/*.cjs',
'packages/**/*.ts',
'packages/**/*.js',
'packages/**/*.mjs',
+1 -3
View File
@@ -11,9 +11,7 @@ const ROOT = resolve(import.meta.dirname, '..')
/** Shipped Cordis configuration these rules apply to. */
const SHIPPED_CONFIG_GLOBS = [
'apps/*/config/*.yml',
'examples/*/*.cordis.yml',
'examples/*/cordis.yml',
'apps/*/config/**/*.yml',
// Bundle identity comes from the package manifest, not the domain directory.
'packages/*/*/cordis.patch.yml',
// The Python runtime ships its own default composition inside the wheel.
+67
View File
@@ -12,6 +12,8 @@ import {
bundleManifestPaths,
bundlePluginDependencyErrors,
metadataExpressionErrors,
packageTestFixtureDependencyErrors,
packageTestPluginDependencyErrors,
} from './verify-cordis-config.ts'
describe('verify-cordis-config metadata expressions', () => {
@@ -86,3 +88,68 @@ describe('workspace Bundle discovery and product dependency closures', () => {
])
})
})
describe('package-owned Loader test dependency closures', () => {
it('requires package test configs to declare each named plugin they load', () => {
const manifestPath = 'packages/example/owner/package.json'
const file = 'packages/example/owner/tests/fixtures/cordis.yml'
const manifest = {
name: '@deepseek-ai/dsh-owner',
dependencies: {},
devDependencies: {
'@deepseek-ai/dsh-declared': 'workspace:^',
},
}
expect(packageTestPluginDependencyErrors(manifestPath, manifest, [
{ file, name: '@deepseek-ai/dsh-owner' },
{ file, name: '@deepseek-ai/dsh-declared' },
{ file, name: '@deepseek-ai/dsh-missing' },
])).toEqual([
`${file}: @deepseek-ai/dsh-missing must be declared in ${manifestPath} dependencies or devDependencies`,
])
})
it('requires executable package test fixtures to declare their bare imports', () => {
const fixture = mkdtempSync(join(tmpdir(), 'dsh-package-test-entrypoint-'))
try {
const packageDir = join(fixture, 'packages/example/owner')
const driverDir = join(packageDir, 'tests/fixtures/loader')
mkdirSync(driverDir, { recursive: true })
writeFileSync(join(packageDir, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-owner',
devDependencies: {
'@deepseek-ai/dsh-declared': 'workspace:^',
},
}))
writeFileSync(join(driverDir, 'driver.ts'), [
"import '@deepseek-ai/dsh-owner'",
"import '@deepseek-ai/dsh-declared'",
"import '@deepseek-ai/dsh-missing'",
].join('\n'))
writeFileSync(join(driverDir, 'cordis.yml'), '[]\n')
writeFileSync(join(driverDir, 'fixture.mjs'), "import '@deepseek-ai/dsh-declared'\n")
const unrelatedDir = join(packageDir, 'tests/fixtures/unrelated')
mkdirSync(unrelatedDir, { recursive: true })
writeFileSync(join(unrelatedDir, 'driver.ts'), "import '@deepseek-ai/dsh-unrelated'\n")
expect(packageTestFixtureDependencyErrors(fixture)).toEqual([
'packages/example/owner/tests/fixtures/loader/driver.ts: '
+ '@deepseek-ai/dsh-missing must be declared in '
+ 'packages/example/owner/package.json dependencies or devDependencies',
])
} finally {
rmSync(fixture, { recursive: true, force: true })
}
})
it('fails loud when package-owned Loader fixtures disappear from the scan', () => {
const fixture = mkdtempSync(join(tmpdir(), 'dsh-empty-package-test-entrypoint-'))
try {
expect(packageTestFixtureDependencyErrors(fixture)).toEqual([
'package test fixture dependency scan found no package-owned Loader configs',
])
} finally {
rmSync(fixture, { recursive: true, force: true })
}
})
})
+114 -67
View File
@@ -5,9 +5,9 @@
* activate, against that plugin context) and the entry `disabled` field (at
* every mount decision, against the loader context). Every other entry
* metadata field stays static, so an expression there remains truthy data and
* silently changes composition. Example configs and the dsh Web composition
* resolve named plugins from their owning workspace manifests. Local example
* packages must also be in the root TypeScript project graph.
* silently changes composition. Shipped and test-only dsh overlays resolve
* named plugins from the CLI application's owning manifest; package-owned
* Loader fixtures resolve from their package manifest.
*/
import { globSync, readFileSync } from 'node:fs'
@@ -20,6 +20,7 @@ import { isCordisGroupEntry, isJsExpr, loadCordisYaml } from './cordis-yaml.ts'
export interface PackageManifest {
name?: string
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
optionalDependencies?: Record<string, string>
dsh?: { bundle?: { patch?: string } }
}
@@ -30,14 +31,10 @@ export interface PluginReference {
}
const root = resolve(import.meta.dirname, '..')
// These example files are overlays consumed by the built dsh app, so their bare
// specifiers resolve from apps/cli rather than the examples workspace.
// These overlays are consumed by the built dsh app, so their bare specifiers
// resolve from apps/cli.
const appOverlayFiles = new Set([
'examples/web-github-review/tests/fixtures/real-cli/cordis.yml',
'examples/web-cordis/cordis.yml',
'examples/web-github-review/cordis.yml',
'examples/web-schedule/cordis.yml',
...globSync('examples/mcp-memory/*.cordis.yml', { cwd: root }),
...globSync('apps/cli/config/examples/**/*.yml', { cwd: root }),
])
const metadataFields = ['id', 'name', 'group', 'inject', 'intercept', 'isolate'] as const
@@ -74,8 +71,9 @@ if (import.meta.main) {
}
}
errors.push(...validateExampleResolution())
errors.push(...validateAppResolution())
errors.push(...validatePackageTestResolution())
errors.push(...packageTestFixtureDependencyErrors())
errors.push(...validateSourcePlaneResolution())
errors.push(...validatePresetPlaneSeparation())
errors.push(...validateClientHalvesDeclared())
@@ -225,37 +223,14 @@ function recordPlugin(entry: Record<string, unknown>, file: string): void {
if (typeof entry.name === 'string') pluginReferences.push({ file, name: entry.name })
}
function validateExampleResolution(): string[] {
const violations: string[] = []
const exampleManifest = readManifest('examples/package.json')
const dependencies = exampleManifest.dependencies ?? {}
const localPackages = localPackageDirectories()
const rootReferences = rootProjectReferences()
const exampleReferences = pluginReferences.filter(reference => reference.file.startsWith('examples/') && !appOverlayFiles.has(reference.file))
violations.push(...missingPluginDependencies(exampleReferences, dependencies, 'examples/package.json'))
const requiredPackages = new Set(exampleReferences.map(reference => packageNameFromSpecifier(reference.name)))
const localExamplePackages = new Set([
...Object.keys(dependencies),
...[...requiredPackages].filter(packageName => packageName !== undefined),
])
for (const packageName of localExamplePackages) {
const packageDirectory = localPackages.get(packageName)
if (packageDirectory === undefined || rootReferences.has(packageDirectory)) continue
const repoPath = relative(root, packageDirectory).replaceAll('\\', '/')
violations.push(`tsconfig.json: missing project reference for ${packageName} (${repoPath})`)
}
return violations
}
function validateAppResolution(): string[] {
const violations: string[] = []
const bundleManifests = bundleManifestPaths()
// App overlays (and any config left under apps/cli/config) resolve from the
// dsh app's own dependency surface — the profile module fallback mirrors it.
const appManifest = readManifest('apps/cli/package.json')
const appDependencies = {
...readManifest('apps/cli/package.json').dependencies,
...appManifest.dependencies,
// The fallback also links every in-box bundle's own dependencies
// (healProfilesModuleFallback). Optional Profile bundles stay outside the
// app installation until that Profile installs them.
@@ -265,7 +240,17 @@ function validateAppResolution(): string[] {
const shipped = new Set(globSync('*.cordis.yml', { cwd: resolve(root, 'apps/cli/config') })
.map(file => `apps/cli/config/${file}`))
const appReferences = pluginReferences.filter(reference => shipped.has(reference.file) || appOverlayFiles.has(reference.file))
violations.push(...missingPluginDependencies(appReferences, appDependencies, 'apps/cli/package.json or a bundle manifest'))
violations.push(...missingPluginDependencies(
appReferences,
appDependencies,
'apps/cli/package.json dependencies or a bundle manifest',
))
const appTestReferences = pluginReferences.filter(reference => reference.file.startsWith('apps/cli/tests/'))
violations.push(...missingPluginDependencies(
appTestReferences,
{ ...appManifest.dependencies, ...appManifest.devDependencies },
'apps/cli/package.json dependencies or devDependencies',
))
// Each bundle's patch rows must resolve from that bundle's own dependencies:
// per-layer resolution anchors on the bundle package directory.
for (const manifestPath of bundleManifests) {
@@ -280,6 +265,95 @@ function validateAppResolution(): string[] {
return violations
}
/**
* Package-owned Loader fixtures resolve named plugins from their package's
* dependency surface, not from a repository-level test umbrella.
* @returns one violation per configured package absent from the owner manifest.
*/
function validatePackageTestResolution(): string[] {
const referencesByManifest = new Map<string, PluginReference[]>()
for (const reference of pluginReferences) {
const manifestPath = packageTestManifestPath(reference.file)
if (manifestPath === undefined) continue
const references = referencesByManifest.get(manifestPath) ?? []
references.push(reference)
referencesByManifest.set(manifestPath, references)
}
return [...referencesByManifest].flatMap(([manifestPath, references]) =>
packageTestPluginDependencyErrors(manifestPath, readManifest(manifestPath), references))
}
/**
* Validate the named plugins one package-owned Loader fixture resolves.
* Self-references use Node package self-resolution; every other package must
* be an ordinary production or test dependency of the owner.
* @param manifestPath Repository-relative owner manifest path.
* @param manifest Parsed owner manifest.
* @param references Named plugin references from owner-local test configs.
* @returns Missing dependency diagnostics.
*/
export function packageTestPluginDependencyErrors(
manifestPath: string,
manifest: PackageManifest,
references: readonly PluginReference[],
): string[] {
return missingPluginDependencies(
references.filter(reference => packageNameFromSpecifier(reference.name) !== manifest.name),
{ ...manifest.dependencies, ...manifest.devDependencies },
`${manifestPath} dependencies or devDependencies`,
)
}
/**
* Validate imports made by fixture modules adjacent to package-owned Loader
* configs. These files execute as plain Node/tsx children, so a stale root
* `node_modules` link must not hide an undeclared dependency.
* @param repoRoot Repository root to scan.
* @returns Missing dependency diagnostics.
*/
export function packageTestFixtureDependencyErrors(repoRoot: string = root): string[] {
const fixtureDirectories = new Set(cordisConfigFiles(repoRoot)
.filter(file => packageTestManifestPath(file) !== undefined)
.map(file => dirname(file).replaceAll('\\', '/')))
if (fixtureDirectories.size === 0) {
return ['package test fixture dependency scan found no package-owned Loader configs']
}
const referencesByManifest = new Map<string, PluginReference[]>()
let fixtureModuleCount = 0
for (const fixtureDirectory of fixtureDirectories) {
const files = globSync([
`${fixtureDirectory}/**/*.ts`,
`${fixtureDirectory}/**/*.mjs`,
], { cwd: repoRoot })
fixtureModuleCount += files.length
for (const file of files) {
const manifestPath = packageTestManifestPath(file)
if (manifestPath === undefined) continue
const references = referencesByManifest.get(manifestPath) ?? []
const source = readFileSync(resolve(repoRoot, file), 'utf8')
for (const imported of ts.preProcessFile(source, true, true).importedFiles) {
references.push({ file: file.replaceAll('\\', '/'), name: imported.fileName })
}
referencesByManifest.set(manifestPath, references)
}
}
if (fixtureModuleCount === 0) {
return ['package test fixture dependency scan found no fixture modules beside Loader configs']
}
return [...referencesByManifest].flatMap(([manifestPath, references]) =>
packageTestPluginDependencyErrors(
manifestPath,
readManifest(manifestPath, repoRoot),
references,
))
}
/** Owner manifest for a package-local test path. */
function packageTestManifestPath(file: string): string | undefined {
const match = /^(packages\/[^/]+\/[^/]+)\/tests(?:\/|$)/.exec(file.replaceAll('\\', '/'))
return match?.[1] === undefined ? undefined : `${match[1]}/package.json`
}
/**
* Discover workspace Bundle packages from their manifest declaration.
* @param repoRoot Repository root to scan.
@@ -308,7 +382,7 @@ export function bundlePluginDependencyErrors(
// A Bundle may mount its own package (for example, its provider or runtime row).
references.filter(reference => packageNameFromSpecifier(reference.name) !== manifest.name),
manifest.dependencies ?? {},
manifestPath,
`${manifestPath} dependencies`,
)
}
@@ -366,7 +440,7 @@ function validateSourcePlaneResolution(): string[] {
function missingPluginDependencies(
references: readonly PluginReference[],
dependencies: Readonly<Record<string, string>>,
manifestPath: string,
dependencyOwner: string,
): string[] {
const requiredPackages = new Map<string, Set<string>>()
const require = (packageName: string, file: string): void => {
@@ -384,7 +458,7 @@ function missingPluginDependencies(
}
return [...requiredPackages].flatMap(([packageName, locations]) => packageName in dependencies
? []
: `${[...locations].join(', ')}: ${packageName} must be declared in ${manifestPath} dependencies`)
: `${[...locations].join(', ')}: ${packageName} must be declared in ${dependencyOwner}`)
}
function readManifest(path: string, repoRoot: string = root): PackageManifest {
@@ -401,33 +475,6 @@ function localPackageDirectories(): Map<string, string> {
return packages
}
function rootProjectReferences(): Set<string> {
// The root solution references the host and client aggregates (the two
// sides merge cordis Context under the same keys, so one program cannot see
// both — but this BFS only collects reference paths, it never forms a
// program). Seed the solution and follow nested aggregate references to
// collect the covered leaf project set.
const collected = new Set<string>()
const queue = [resolve(root, 'tsconfig.json')]
const seen = new Set<string>()
for (let file = queue.pop(); file !== undefined; file = queue.pop()) {
if (seen.has(file)) continue
seen.add(file)
const config = ts.readConfigFile(file, path => ts.sys.readFile(path))
if (config.error !== undefined) {
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
}
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
for (const reference of references) {
if (typeof reference.path !== 'string') continue
const target = resolve(dirname(file), reference.path)
if (target.endsWith('.json')) queue.push(target)
else collected.add(target)
}
}
return collected
}
function packageNameFromSpecifier(specifier: string): string | undefined {
if (specifier.startsWith('.') || specifier.startsWith('/') || /^[a-z][a-z+.-]*:/i.test(specifier)) return undefined
const segments = specifier.split('/')
+1 -1
View File
@@ -12,7 +12,7 @@ import { findReferenceViolations, uniqueRepoFiles, type ReferenceViolation as Vi
const root = resolve(import.meta.dirname, '..')
/** Repo-authored TypeScript that may cite docs in comments. */
const PATTERNS = ['packages/**/*.ts', 'examples/**/*.ts']
const PATTERNS = ['packages/**/*.ts']
/** Paths excluded from the scan: built output and vendored upstream source. */
const isExcluded = (p: string): boolean =>
-1
View File
@@ -23,7 +23,6 @@ const PATTERNS = [
'docs/**/*.md',
'packages/*/*.md',
'packages/*/*/*.md',
'examples/**/*.md',
'AGENTS.md',
'packages/AGENTS.md',
'.agents/skills/**/*.md',
+2 -1
View File
@@ -22,10 +22,11 @@ const PATTERNS = [
'docs/**/*.md',
'packages/*/*.md',
'packages/*/*/*.md',
'examples/**/system-prompt.expected.md',
'snapshots/**/system-prompt.expected.md',
'packages/**/system-prompt.expected.md',
'AGENTS.md',
'packages/AGENTS.md',
'snapshots/AGENTS.md',
]
/** A located hard-wrap: a prose paragraph spanning more than one source line. */
-1
View File
@@ -22,7 +22,6 @@ const PATTERNS = [
'docs/**/*.md',
'packages/*/*.md',
'packages/*/*/*.md',
'examples/**/*.md',
'AGENTS.md',
'packages/AGENTS.md',
'.agents/skills/**/*.md',
-1
View File
@@ -26,7 +26,6 @@ const PATTERNS = [
'AGENTS.md',
'packages/AGENTS.md',
'packages/**/*.ts',
'examples/**/*.ts',
]
/** Paths excluded from the scan: built output and vendored upstream source. */
@@ -152,7 +152,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/skill/skill-filesystem': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
'packages/spill/spill': { kind: 'indirect', reason: 'The storage seam delegates model rendering to spill consumers.' },
'packages/spill/spill-local': { kind: 'indirect', reason: 'The storage backend delegates model rendering to spill consumers.' },
'packages/test-support/acp-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' },
'packages/test-support/session-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' },
'packages/test-support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' },
'packages/runtime-diagnostics/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' },
'packages/test-support/loader-smoke': { kind: 'none', reason: 'The test harness submits an ordinary user task but delegates prompt and tool composition to the loaded tree.' },
+8 -3
View File
@@ -21,6 +21,7 @@ const platforms = {
'linux-x64': { tag: 'manylinux_2_28_x86_64', executable: 'runtime-linux-x64' },
'linux-arm64': { tag: 'manylinux_2_28_aarch64', executable: 'runtime-linux-arm64' },
'macos-arm64': { tag: 'macosx_14_0_arm64', executable: 'runtime-macos-arm64' },
'win-x64': { tag: 'win_amd64', executable: 'runtime-win-x64.exe' },
}
function workspace(root: string, name: string, manifest: Record<string, unknown>): void {
@@ -35,7 +36,7 @@ afterEach(() => {
})
describe('verifyRuntimeClosure', () => {
it('requires only plugins active for a Linux or macOS target', async () => {
it('requires only plugins active for each published target', async () => {
const root = fixture({
'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/shared': 'workspace:^' } },
'python/sdk-runtime/platforms.json': platforms,
@@ -52,6 +53,9 @@ describe('verifyRuntimeClosure', () => {
- id: macos
name: '@scope/macos'
disabled: !!js process.platform !== 'darwin'
- id: windows
name: '@scope/windows'
disabled: !!js process.platform !== 'win32'
`,
})
@@ -61,6 +65,7 @@ describe('verifyRuntimeClosure', () => {
expect(result.failures).toEqual([
'standard preset -> @scope/linux (linux-arm64, linux-x64)',
'standard preset -> @scope/macos (macos-arm64)',
'standard preset -> @scope/windows (win-x64)',
])
})
@@ -78,7 +83,7 @@ describe('verifyRuntimeClosure', () => {
const result = await verifyRuntimeClosure(root)
expect(result.failures).toEqual([
'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64)',
'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64, win-x64)',
])
})
@@ -112,7 +117,7 @@ describe('verifyRuntimeClosure', () => {
const result = await verifyRuntimeClosure(root)
expect(result.failures).toEqual([
'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64)',
'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64, win-x64)',
])
})
+1
View File
@@ -181,6 +181,7 @@ function disabledOnPlatform(value: unknown, processPlatform: string): boolean {
function processPlatformForTarget(target: string): string {
if (target.startsWith('linux-')) return 'linux'
if (target.startsWith('macos-')) return 'darwin'
if (target.startsWith('win-')) return 'win32'
throw new Error(`verify-runtime-closure: unsupported runtime target ${JSON.stringify(target)}`)
}