Merge remote-tracking branch 'origin/master' into worktree/docs-website

# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	pnpm-workspace.yaml
#	scripts/verify-md-wrap.ts
This commit is contained in:
Yichen Jiang
2026-07-14 09:50:42 +08:00
145 changed files with 11502 additions and 583 deletions
+470
View File
@@ -0,0 +1,470 @@
/**
* Build the single-file SDK runtime executables
* (docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
*
* Every settled decision is hardcoded — the PoC judged @yao-pkg/pkg's
* standard mode unusable for this architecture (its ESM→CJS transform breaks
* every runtime `import()`), so the pipeline is fixed on `--sea` mode, plain
* ESM entry, plain-source assets, and a hoisted (symlink-free) staged tree.
*
* Pipeline — every step fails loud with the command it ran:
*
* 1. `pnpm run build` — all packages emit `lib/` (skippable via --skip-build).
* 2. `pnpm --filter dsh-jsonrpc-agent-pkg deploy` — materialize the
* closure-manifest package (python/sdk-runtime/package.json — the single
* source of truth for the exe's plugin set) into the staging dir
* (cleared first; pnpm refuses a non-empty deploy target). Flags, all
* verified against pnpm 11.7: `--legacy` because the workspace does not
* set `inject-workspace-packages=true`; `node-linker=hoisted` for a plain
* file tree with zero symlinks (the safe shape for pkg's VFS, and it
* physically guarantees a single cordis copy); `auto-install-peers=false`
* so transitive `^0.0.x` peers on unpublished packages never hit the
* registry; `link-workspace-packages=true` so the closure resolves to
* workspace/vendor sources.
* 3. Inject the pkg config into the staged package.json: `bin` = the ESM
* `node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js` (SEA mode
* hands it to Node's default ESM loader — no CJS shim), plus whole-tree
* asset globs. The cordis Loader resolves plugins
* through runtime dynamic `import()` of bare package names, so pkg's
* static analysis discovers none of them — the entire staged tree must be
* globbed in explicitly.
* 4. `pnpm dlx @yao-pkg/pkg@<pinned> <staging> --sea --targets <t> --output
* <out>/dsh-jsonrpc-agent-pkg-<platform>-<arch>` — once per target (SEA mode
* packs a single target per invocation), so each product gets its
* canonical name directly.
* 5. Sync into the Python runtime package
* (python/sdk-runtime/src/deepseek_harness_runtime/runtime/,
* created if missing): each product under its canonical filename (exe
* mode), plus the whole staged closure into runtime/node/ (node mode —
* `node runtime/node/node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js`
* runs it directly; the injected pkg
* fields are harmless to node). dist-exe/ keeps the originals for CI
* artifact upload.
*
* `pnpm exec tsx scripts/build-exe-for-python-sdk.ts` → host-platform exe into dist-exe/
* `pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=node24-linux-x64,node24-linux-arm64,node24-macos-arm64`
* `pnpm exec tsx scripts/build-exe-for-python-sdk.ts --dry-run` → print the plan without executing
*/
import { spawn } from 'node:child_process'
import { existsSync, mkdirSync, statSync } from 'node:fs'
import { copyFile, readFile, rm, writeFile } from 'node:fs/promises'
import { basename, join, resolve, sep } from 'node:path'
import { parseArgs } from 'node:util'
const root = resolve(import.meta.dirname, '..')
/**
* The deploy root: the closure-manifest package (python/sdk-runtime) whose
* dependencies define the exe's contents; the runnable entry inside the
* closure is {@link ENTRY_BIN}.
*/
const DEPLOY_ROOT_PACKAGE = 'dsh-jsonrpc-agent-pkg'
/** The bin entry inside the deployed closure (the dsh-jsonrpc-agent app bin). */
const ENTRY_BIN = 'node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js'
/** Basename of every product; the canonical name appends `-<platform>-<arch>`. */
const OUTPUT_BASENAME = 'dsh-jsonrpc-agent-pkg'
/** Default exe Node major; SEA mode requires >= node22, the repo tracks node24. */
const DEFAULT_NODE_RANGE = 'node24'
/** Pinned pkg version (the one the PoC and acceptance ran on) for reproducible builds. */
const PKG_SPEC = '@yao-pkg/pkg@6.21.0'
/** Staging dir for the deployed closure — cleared on every run (gitignored). */
// (No external staging dir: the deploy target IS the Python runtime's
// node-mode carrier — see PYTHON_RUNTIME_DIR/PYTHON_NODE_SUBDIR.)
/** Product output dir (gitignored). */
const OUT_DIR = 'dist-exe'
/**
* Python runtime package dir the products are synced into. A parallel change
* owns the directory and its .gitignore; this script's only contract is the
* destination path, so a missing dir is created, never an error.
*/
const PYTHON_RUNTIME_DIR = 'python/sdk-runtime/src/deepseek_harness_runtime/runtime'
/** Subdir of {@link PYTHON_RUNTIME_DIR} carrying the staged closure for node-mode execution. */
const PYTHON_NODE_SUBDIR = 'node'
/** Deploy-root documentation is not runtime input and violates the generated-directory i18n exclusion if retained. */
const DEPLOY_ONLY_DOCS = ['README.md', 'README.zh.md', 'README.i18n.yaml']
/**
* Whole-tree asset globs. The cordis Loader dynamic-imports bare package names
* at runtime, invisible to pkg's static analysis, so every runtime file in the
* closure is listed; SEA mode ships them as plain source in the VFS. Every
* package.json must ride along — bare-name resolution dies without them (the
* json glob would already match, but the manifests are resolution-critical, so
* they get their own explicit entry).
*/
const ASSET_GLOBS = [
'package.json',
'node_modules/**/*.js',
'node_modules/**/*.cjs',
'node_modules/**/*.mjs',
'node_modules/**/package.json',
'node_modules/**/*.json',
'node_modules/**/*.node',
'node_modules/**/*.wasm',
]
const PLATFORMS = ['linux', 'macos'] as const
const ARCHES = ['x64', 'arm64'] as const
type Platform = (typeof PLATFORMS)[number]
type Arch = (typeof ARCHES)[number]
/** True when `value` is a supported pkg platform tag. */
function isPlatform(value: string): value is Platform {
return (PLATFORMS as readonly string[]).includes(value)
}
/** True when `value` is a supported pkg CPU tag. */
function isArch(value: string): value is Arch {
return (ARCHES as readonly string[]).includes(value)
}
/**
* One pkg target triple, e.g. `node24-linux-x64`, as an immutable value.
* Construction goes through {@link Target.parse} (a `--targets` entry) or
* {@link Target.host} (the default), which own all validation.
*/
class Target {
private constructor(
/** pkg Node range (`node<major>`); pins the official base binary pkg pulls. */
readonly nodeRange: string,
/**
* pkg platform tag. Windows is a documented non-goal
* (docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
*/
readonly platform: Platform,
/** pkg CPU tag. */
readonly arch: Arch,
) {}
/** The pkg `--targets` spec string `<nodeRange>-<platform>-<arch>`. */
get spec(): string {
return `${this.nodeRange}-${this.platform}-${this.arch}`
}
/**
* Parse and validate one target spec; throws on any malformed component.
* @param spec - the raw triple, e.g. `node24-linux-x64`.
* @returns the parsed target.
*/
static parse(spec: string): Target {
const parts = spec.split('-')
const [nodeRange, platform, arch] = parts
if (parts.length !== 3 || nodeRange === undefined || platform === undefined || arch === undefined) {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)} must be <nodeRange>-<platform>-<arch>, e.g. node24-linux-x64.`)
}
if (!/^node\d+$/.test(nodeRange)) {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)}: node range must look like node24, got ${JSON.stringify(nodeRange)}.`)
}
if (!isPlatform(platform)) {
throw new Error(`build-exe-for-python-sdk: target ${JSON.stringify(spec)}: platform must be one of ${PLATFORMS.join(', ')} (Windows is a docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md non-goal), got ${JSON.stringify(platform)}.`)
}
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)}.`)
}
return new Target(nodeRange, platform, arch)
}
/**
* The default target when --targets is omitted: the host platform on node24.
* @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
if (platform === undefined) {
throw new Error(`build-exe-for-python-sdk: unsupported host platform ${process.platform}; pass --targets explicitly.`)
}
const arch = process.arch === 'x64' || process.arch === 'arm64' ? process.arch : undefined
if (arch === undefined) {
throw new Error(`build-exe-for-python-sdk: unsupported host arch ${process.arch}; pass --targets explicitly.`)
}
return new Target(DEFAULT_NODE_RANGE, platform, arch)
}
}
/**
* Parsed CLI configuration. {@link BuildCli.parse} is the only constructor
* path — it owns flag parsing, target validation, and the --help / bad-flag
* process exits, so an instance always holds a valid plan.
*/
class BuildCli {
private constructor(
/** Build targets; defaults to the host platform only. */
readonly targets: readonly Target[],
/** Skip step 1 (`pnpm run build`); lib/ artifacts must already exist. */
readonly skipBuild: boolean,
/** Print every command and config patch instead of executing. */
readonly dryRun: boolean,
) {}
/**
* Parse argv into a validated configuration. Exits the process for --help
* (code 0, usage) and for unknown/malformed flags (code 1, usage on
* stderr); throws on invalid or colliding targets.
* @param argv - the raw arguments (`process.argv.slice(2)`).
* @returns the parsed, validated configuration.
*/
static parse(argv: string[]): BuildCli {
let values: ReturnType<typeof BuildCli.parseRaw>
try {
values = BuildCli.parseRaw(argv)
} catch (error) {
console.error(`build-exe-for-python-sdk: ${error instanceof Error ? error.message : String(error)}\n`)
console.error(BuildCli.usage())
process.exit(1)
}
if (values.help) {
console.log(BuildCli.usage())
process.exit(0)
}
const targets = values.targets === undefined
? [Target.host()]
: values.targets.split(',').map(part => part.trim()).filter(part => part !== '').map(spec => Target.parse(spec))
if (targets.length === 0) throw new Error('build-exe-for-python-sdk: --targets is empty.')
const seen = new Set<string>()
for (const target of targets) {
const key = `${target.platform}-${target.arch}`
if (seen.has(key)) {
throw new Error(`build-exe-for-python-sdk: duplicate platform-arch ${key} in --targets; canonical product names would collide.`)
}
seen.add(key)
}
return new BuildCli(targets, values['skip-build'], values['dry-run'])
}
/** The flag grammar in one place; parseArgs throws on any unknown flag. */
private static parseRaw(argv: string[]) {
return parseArgs({
args: argv,
options: {
'targets': { type: 'string' },
'skip-build': { type: 'boolean', default: false },
'dry-run': { type: 'boolean', default: false },
'help': { type: 'boolean', default: false },
},
}).values
}
/** The --help text; also printed under flag-parse errors. */
private static usage(): string {
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.',
' 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.',
' --help print this help.',
'',
'Settled decisions are hardcoded (docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md): pkg runs in --sea mode',
`(standard mode breaks runtime import()), pinned to ${PKG_SPEC}; the deploy tree is`,
`hoisted/symlink-free; the closure deploys straight into ${PYTHON_RUNTIME_DIR}/${PYTHON_NODE_SUBDIR} and products land in ${OUT_DIR}/.`,
].join('\n')
}
}
/** The pnpm executable name for the host OS. */
function pnpmBin(): string {
return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
}
/**
* Render a command line for logs and error messages, quoting arguments that
* contain spaces.
* @param command - the executable.
* @param args - its arguments.
* @returns the printable command line.
*/
function formatCommand(command: string, args: string[]): string {
return [command, ...args].map(part => (part.includes(' ') ? JSON.stringify(part) : part)).join(' ')
}
/**
* The four-step build pipeline over one parsed CLI. Steps are sequential
* async methods; every subprocess inherits stdio and fails loud with the
* exact command it ran. In --dry-run the command/filesystem layer prints
* what it would do instead of executing.
*/
class SingleExeBuild {
/**
* Absolute staging dir — the Python runtime's node-mode carrier: step 2
* deploys the closure DIRECTLY here (cleared first; it is a pure build
* product, the checked-in default `cordis.yml` lives one level up), step 4
* reads it as the pkg input, and node mode runs it in place.
*/
readonly staging = resolve(root, PYTHON_RUNTIME_DIR, PYTHON_NODE_SUBDIR)
/** Absolute product output dir. */
private readonly outDir = resolve(root, OUT_DIR)
constructor(private readonly cli: BuildCli) {}
/** Gate the manifest before spending time compiling or packaging it. */
async verifyClosure(): Promise<void> {
await this.run('runtime dependency closure', pnpmBin(), ['run', 'verify-runtime-closure'])
}
/** Step 1: `pnpm run build` — all packages emit `lib/` (skipped via --skip-build). */
async build(): Promise<void> {
if (this.cli.skipBuild) {
console.log('build-exe-for-python-sdk: skipping pnpm run build (--skip-build)')
return
}
await this.run('build', pnpmBin(), ['run', 'build'])
}
/** Step 2: clear the staging dir and deploy the bridge closure into it. */
async deployStaging(): Promise<void> {
if (this.staging === root || root.startsWith(this.staging + sep)) {
throw new Error(`build-exe-for-python-sdk: refusing to clear staging dir ${this.staging}: it contains the repo root.`)
}
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(), [
'--filter',
DEPLOY_ROOT_PACKAGE,
'deploy',
'--legacy',
'--prod',
'--config.node-linker=hoisted',
'--config.auto-install-peers=false',
'--config.link-workspace-packages=true',
this.staging,
])
if (this.cli.dryRun) {
for (const name of DEPLOY_ONLY_DOCS) console.log(`build-exe-for-python-sdk: [dry-run] rm -f ${join(this.staging, name)}`)
} else {
await Promise.all(DEPLOY_ONLY_DOCS.map(name => rm(join(this.staging, name), { force: true })))
}
}
/** Step 3: patch the staged package.json with the bin entry + pkg asset globs. */
async injectPkgConfig(): Promise<void> {
const patch = { bin: ENTRY_BIN, pkg: { assets: ASSET_GLOBS } }
const manifestPath = join(this.staging, 'package.json')
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] patch ${manifestPath} with ${JSON.stringify(patch)}`)
return
}
if (!existsSync(manifestPath)) {
throw new Error(`build-exe-for-python-sdk: ${manifestPath} missing — pnpm deploy did not produce a staged package.`)
}
if (!existsSync(join(this.staging, ENTRY_BIN))) {
throw new Error(`build-exe-for-python-sdk: ${join(this.staging, ENTRY_BIN)} missing — run without --skip-build so lib/ artifacts exist.`)
}
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as Record<string, unknown>
await writeFile(manifestPath, `${JSON.stringify({ ...manifest, ...patch }, null, 2)}\n`)
console.log(`build-exe-for-python-sdk: injected pkg config into ${manifestPath}`)
}
/**
* Step 4: run @yao-pkg/pkg over the staged tree for ONE target (SEA mode
* packs a single target per invocation) and return the product path.
* @param target - the pkg target triple to build.
* @returns the canonical product path `<out>/dsh-jsonrpc-agent-pkg-<platform>-<arch>`.
*/
async pack(target: Target): Promise<string> {
const product = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
if (!this.cli.dryRun) mkdirSync(this.outDir, { recursive: true })
await this.run(`pkg ${target.spec}`, pnpmBin(), [
'dlx',
PKG_SPEC,
this.staging,
'--sea',
'--targets',
target.spec,
'--output',
product,
])
if (!this.cli.dryRun && !existsSync(product)) {
throw new Error(`build-exe-for-python-sdk: product ${product} is missing after the pkg run; inspect ${this.outDir}.`)
}
return product
}
/**
* Print each product path (and size, when it exists on disk).
* @param products - the product paths returned by {@link pack}.
*/
printProducts(products: string[]): void {
console.log(this.cli.dryRun ? 'build-exe-for-python-sdk: [dry-run] would produce:' : 'build-exe-for-python-sdk: products:')
for (const product of products) {
if (this.cli.dryRun) {
console.log(` ${product}`)
continue
}
const megabytes = statSync(product).size / (1024 * 1024)
console.log(` ${product} (${megabytes.toFixed(1)} MB)`)
}
}
/**
* Step 5: copy every product into the Python runtime package under its
* canonical filename (exe mode). The node-mode carrier needs no sync — step
* 2 deployed the closure into it directly. dist-exe/ keeps the originals
* for CI artifact upload; the destination dir is created if missing.
* @param products - the product paths returned by {@link pack}.
*/
async syncToPythonRuntime(products: string[]): Promise<void> {
const destDir = resolve(root, PYTHON_RUNTIME_DIR)
if (this.cli.dryRun) {
for (const product of products) {
console.log(`build-exe-for-python-sdk: [dry-run] cp ${product} ${join(destDir, basename(product))}`)
}
return
}
mkdirSync(destDir, { recursive: true })
for (const product of products) {
const destination = join(destDir, basename(product))
await copyFile(product, destination)
console.log(`build-exe-for-python-sdk: synced ${destination}`)
}
}
/**
* Run one pipeline step as a subprocess with inherited stdio; reject —
* carrying the printable command — on spawn failure and non-zero exit
* alike. In --dry-run, print the command instead of executing.
* @param label - the step name used in logs and error messages.
* @param command - the executable.
* @param args - its arguments.
*/
private async run(label: string, command: string, args: string[]): Promise<void> {
const printable = formatCommand(command, args)
if (this.cli.dryRun) {
console.log(`build-exe-for-python-sdk: [dry-run] ${printable}`)
return
}
console.log(`build-exe-for-python-sdk: ${label}: ${printable}`)
await new Promise<void>((resolvePromise, reject) => {
const child = spawn(command, args, { cwd: root, stdio: 'inherit' })
child.once('error', (error) => {
reject(new Error(`build-exe-for-python-sdk: ${label} failed to spawn: ${error.message} (${printable})`))
})
child.once('exit', (code, signal) => {
if (code === 0) {
resolvePromise()
return
}
const cause = code === null ? `signal ${signal ?? 'unknown'}` : `exit code ${code}`
reject(new Error(`build-exe-for-python-sdk: ${label} failed (${cause}): ${printable}`))
})
})
}
}
/** Entry point: parse the CLI, then await each pipeline step in order. */
async function main(): Promise<void> {
const cli = BuildCli.parse(process.argv.slice(2))
const pipeline = new SingleExeBuild(cli)
console.log(`build-exe-for-python-sdk: targets: ${cli.targets.map(target => target.spec).join(', ')}`)
console.log(`build-exe-for-python-sdk: staging: ${pipeline.staging}`)
await pipeline.verifyClosure()
await pipeline.build()
await pipeline.deployStaging()
await pipeline.injectPkgConfig()
const products: string[] = []
for (const target of cli.targets) products.push(await pipeline.pack(target))
pipeline.printProducts(products)
await pipeline.syncToPythonRuntime(products)
}
await main()
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Stage and build one Python wheel at the repository version."""
from __future__ import annotations
import argparse
import email
import json
import os
import re
import shutil
import stat
import subprocess
import tempfile
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
PLATFORMS = {
"linux-x64": ("manylinux_2_28_x86_64", "dsh-jsonrpc-agent-pkg-linux-x64"),
"linux-arm64": ("manylinux_2_28_aarch64", "dsh-jsonrpc-agent-pkg-linux-arm64"),
"macos-arm64": ("macosx_11_0_arm64", "dsh-jsonrpc-agent-pkg-macos-arm64"),
}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--package", choices=("sdk", "runtime"), required=True)
parser.add_argument(
"--tag",
help="optional python-vX.Y.Z release tag; it must match package.json",
)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--platform", choices=tuple(PLATFORMS))
parser.add_argument("--runtime-exe", type=Path)
args = parser.parse_args()
version = repository_version()
validate_release_tag(args.tag, version)
if args.package == "runtime" and (args.platform is None or args.runtime_exe is None):
parser.error("runtime builds require --platform and --runtime-exe")
if args.package == "sdk" and (args.platform is not None or args.runtime_exe is not None):
parser.error("SDK builds do not accept --platform or --runtime-exe")
output_dir = args.output_dir.resolve()
output_dir.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="dsh-python-release-") as temporary:
staging = Path(temporary) / args.package
if args.package == "sdk":
stage_sdk(staging, version)
environment = None
expected = output_dir / f"deepseek_harness-{version}-py3-none-any.whl"
else:
platform_tag, executable_name = PLATFORMS[args.platform]
stage_runtime(staging, version, args.runtime_exe.resolve(), executable_name)
environment = {"DSH_RUNTIME_PLATFORM_TAG": platform_tag}
expected = output_dir / f"deepseek_harness_runtime_bin-{version}-py3-none-{platform_tag}.whl"
command = ["uv", "build", "--wheel", "--out-dir", str(output_dir), str(staging)]
subprocess.run(command, cwd=ROOT, env=None if environment is None else {**os.environ, **environment}, check=True)
if not expected.is_file():
raise RuntimeError(f"build did not produce expected wheel: {expected}")
verify_wheel(expected, args.package, version, None if args.platform is None else PLATFORMS[args.platform])
print(expected)
def repository_version(root: Path = ROOT) -> str:
package_json = root / "package.json"
try:
payload = json.loads(package_json.read_text())
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"could not read repository version from {package_json}") from error
version = payload.get("version") if isinstance(payload, dict) else None
if not isinstance(version, str) or re.fullmatch(r"\d+\.\d+\.\d+", version) is None:
raise ValueError(
f"{package_json} version must be stable X.Y.Z, got {version!r}"
)
return version
def validate_release_tag(tag: str | None, version: str) -> None:
if tag is None:
return
expected = f"python-v{version}"
if tag != expected:
raise ValueError(
f"release tag must match repository version: expected {expected!r}, got {tag!r}"
)
def copy_package(source: Path, destination: Path) -> None:
shutil.copytree(
source,
destination,
ignore=shutil.ignore_patterns(
".venv",
".pytest_cache",
"__pycache__",
"*.pyc",
"dist",
"node_modules",
"dsh-jsonrpc-agent-pkg-*",
),
)
def rewrite_version(pyproject: Path, version: str) -> None:
text, count = re.subn(
r'^version = "[^"]+"$',
f'version = "{version}"',
pyproject.read_text(),
count=1,
flags=re.MULTILINE,
)
if count != 1:
raise RuntimeError(f"could not rewrite version in {pyproject}")
pyproject.write_text(text)
def stage_sdk(destination: Path, version: str) -> None:
copy_package(ROOT / "python" / "sdk", destination)
pyproject = destination / "pyproject.toml"
rewrite_version(pyproject, version)
text, count = re.subn(
r'"deepseek-harness-runtime-bin==[^"]+"',
f'"deepseek-harness-runtime-bin=={version}"',
pyproject.read_text(),
count=1,
)
if count != 1:
raise RuntimeError("SDK must contain exactly one runtime dependency pin")
pyproject.write_text(text)
def stage_runtime(destination: Path, version: str, executable: Path, executable_name: str) -> None:
if not executable.is_file():
raise FileNotFoundError(f"runtime executable does not exist: {executable}")
if executable.stat().st_mode & stat.S_IXUSR == 0:
raise PermissionError(f"runtime executable is not executable: {executable}")
copy_package(ROOT / "python" / "sdk-runtime", destination)
rewrite_version(destination / "pyproject.toml", version)
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
runtime_dir.mkdir(parents=True, exist_ok=True)
destination_executable = runtime_dir / executable_name
shutil.copyfile(executable, destination_executable)
destination_executable.chmod(executable.stat().st_mode & 0o777)
def verify_wheel(
wheel: Path,
package: str,
version: str,
platform: tuple[str, str] | None,
) -> None:
expected_tag = "py3-none-any" if platform is None else f"py3-none-{platform[0]}"
with zipfile.ZipFile(wheel) as archive:
wheel_metadata_path = next(name for name in archive.namelist() if name.endswith(".dist-info/WHEEL"))
metadata_path = next(name for name in archive.namelist() if name.endswith(".dist-info/METADATA"))
wheel_metadata = email.message_from_bytes(archive.read(wheel_metadata_path))
metadata = email.message_from_bytes(archive.read(metadata_path))
if wheel_metadata.get_all("Tag") != [expected_tag]:
raise RuntimeError(f"{wheel} has wrong WHEEL tags: {wheel_metadata.get_all('Tag')}")
if metadata.get("Version") != version:
raise RuntimeError(f"{wheel} has version {metadata.get('Version')}, expected {version}")
executables = [name for name in archive.namelist() if "/runtime/dsh-jsonrpc-agent-pkg-" in name]
if package == "runtime":
assert platform is not None
if len(executables) != 1 or not executables[0].endswith(f"/runtime/{platform[1]}"):
raise RuntimeError(f"{wheel} must contain exactly {platform[1]}, found {executables}")
mode = archive.getinfo(executables[0]).external_attr >> 16
if mode & stat.S_IXUSR == 0:
raise RuntimeError(f"{wheel} runtime executable lost its executable bit")
elif executables:
raise RuntimeError(f"SDK wheel unexpectedly contains runtime executables: {executables}")
if package == "sdk":
requirements = metadata.get_all("Requires-Dist") or []
expected_requirement = f"deepseek-harness-runtime-bin=={version}"
if expected_requirement not in requirements:
raise RuntimeError(f"{wheel} does not pin {expected_requirement}; found {requirements}")
if __name__ == "__main__":
main()
+17 -5
View File
@@ -61,6 +61,9 @@ function readJson(path: string): PackageManifest {
return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
}
const rootManifest = readJson(join(root, 'package.json'))
const repositoryVersion = rootManifest.version
/** Repo-relative dirs holding a package.json, walked to the configured depth. */
function packageDirs(base: string, depth: number): string[] {
if (depth === 1) {
@@ -78,7 +81,7 @@ function packageDirs(base: string, depth: number): string[] {
function workspaceManifests(): WorkspaceManifest[] {
const manifests: WorkspaceManifest[] = [
{ dir: '.', manifest: readJson(join(root, 'package.json')) },
{ dir: '.', manifest: rootManifest },
]
for (const { dir: base, depth } of workspaceGlobs) {
@@ -107,7 +110,7 @@ const dshBinPackageFiles = [
const dshWorkerPackageFiles = [
'lib/index.js',
'lib/worker.js',
'lib/worker.cjs',
'lib/types/**/*.d.ts',
'lib/types/**/*.d.ts.map',
'src',
@@ -147,8 +150,8 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
if (peer && dev && peer !== dev) {
errors.push(`${label}: cordis peer (${peer}) and dev (${dev}) ranges must match`)
}
if (manifest.version !== '0.0.1') {
errors.push(`${label}: package.json must set "version": "0.0.1"`)
if (manifest.version !== repositoryVersion) {
errors.push(`${label}: package.json version must match root version ${repositoryVersion ?? '(missing)'}`)
}
if (manifest.type !== 'module') {
errors.push(`${label}: package.json must set "type": "module"`)
@@ -205,7 +208,16 @@ function checkHierarchyShape(): string[] {
return errors
}
const errors = [...workspaceManifests().flatMap(checkWorkspace), ...checkHierarchyShape()]
function checkRepositoryVersion(): string[] {
if (repositoryVersion && /^\d+\.\d+\.\d+$/.test(repositoryVersion)) return []
return ['package.json: version must be stable X.Y.Z']
}
const errors = [
...checkRepositoryVersion(),
...workspaceManifests().flatMap(checkWorkspace),
...checkHierarchyShape(),
]
if (errors.length > 0) {
console.error(errors.join('\n'))
process.exitCode = 1
+8 -71
View File
@@ -22,22 +22,15 @@ import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'no
import { dirname, relative, resolve } from 'node:path'
import ts from 'typescript'
import { collectEvents, collectServices } from './gen-cordis-catalog.ts'
import {
collectPackageGraph,
escapeMermaidLabel as escLabel,
graphNodeId as nodeId,
type PackageGraphNode,
} from './package-graph.ts'
const root = resolve(import.meta.dirname, '..')
const SCOPE = '@deepseek-ai/dsh-'
interface PkgJson {
name: string
peerDependencies?: Record<string, string>
}
interface Pkg {
short: string
name: string
group: string
rel: string
deps: string[]
}
type Pkg = PackageGraphNode
interface GraphDoc {
rel: string
@@ -312,62 +305,6 @@ function linkFromDoc(docRel: string, targetRel: string): string {
return relative(dirname(docRel), targetRel).replaceAll('\\', '/')
}
function collectPackages(): Pkg[] {
const pkgs: Pkg[] = []
for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) {
const json = JSON.parse(readFileSync(resolve(root, rel), 'utf8')) as PkgJson
if (!json.name.startsWith(SCOPE)) continue
const [, group, leaf] = rel.split('/')
if (group === undefined || leaf === undefined) throw new Error(`gen-doc-graphs: unexpected package path ${rel}`)
const deps = Object.keys(json.peerDependencies ?? {})
.filter(dep => dep.startsWith(SCOPE))
.map(dep => dep.slice(SCOPE.length))
.sort()
pkgs.push({
short: json.name.slice(SCOPE.length),
name: json.name,
group,
rel: dirname(rel),
deps,
})
}
return topoSort(pkgs)
}
function topoSort(pkgs: Pkg[]): Pkg[] {
const remaining = new Map(pkgs.map(p => [p.short, p]))
const placed = new Set<string>()
const out: Pkg[] = []
while (remaining.size > 0) {
const ready = [...remaining.values()]
.filter(pkg => pkg.deps.every(dep => placed.has(dep)))
.sort(comparePackages)
if (ready.length === 0) throw new Error(`gen-doc-graphs: dependency cycle among ${[...remaining.keys()].join(', ')}`)
for (const pkg of ready) {
out.push(pkg)
placed.add(pkg.short)
remaining.delete(pkg.short)
}
}
return out
}
function comparePackages(a: Pkg, b: Pkg): number {
const groupA = GROUP_ORDER.indexOf(a.group)
const groupB = GROUP_ORDER.indexOf(b.group)
const normA = groupA === -1 ? Number.MAX_SAFE_INTEGER : groupA
const normB = groupB === -1 ? Number.MAX_SAFE_INTEGER : groupB
return normA - normB || a.group.localeCompare(b.group) || a.short.localeCompare(b.short)
}
function nodeId(prefix: string, value: string): string {
return `${prefix}_${value.replace(/[^a-zA-Z0-9_]/g, '_')}`
}
function escLabel(value: string): string {
return value.replace(/"/g, '\\"')
}
function mermaidCode(value: string): string {
return `<code>${value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code>`
}
@@ -844,7 +781,7 @@ function renderSnapshotReplay(): string {
}
function renderDocs(): GraphDoc[] {
const pkgs = collectPackages()
const pkgs = collectPackageGraph(root, GROUP_ORDER, 'gen-doc-graphs')
const docs: GraphDoc[] = [
{ rel: 'docs/capability-seams.md', content: renderCapabilitySeams(pkgs) },
...APP_EXAMPLES.map(example => ({ rel: example.rel, content: renderAppComposition(example) })),
+10 -76
View File
@@ -18,23 +18,18 @@
* is stale (CI / pre-push gate)
*/
import { dirname, resolve } from 'node:path'
import { globSync, readFileSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { readFileSync, writeFileSync } from 'node:fs'
import {
collectPackageGraph,
escapeMermaidLabel as escLabel,
graphNodeId as nodeId,
type PackageGraphNode,
} from './package-graph.ts'
const root = resolve(import.meta.dirname, '..')
const OUT = 'docs/module-graph.md'
const SCOPE = '@deepseek-ai/dsh-'
interface Pkg {
/** Short name, `@deepseek-ai/dsh-` prefix stripped (e.g. `agent-loop`). */
short: string
/** Package group from `packages/<group>/<pkg>`. */
group: string
/** Repo-relative package directory. */
rel: string
/** Short names of this package's in-repo peer dependencies, sorted. */
deps: string[]
}
type Pkg = PackageGraphNode
const GROUP_ORDER = [
'util',
@@ -55,67 +50,6 @@ const GROUP_ORDER = [
'ui',
]
/** Read every workspace package and its `@deepseek-ai/dsh-*` peer edges. */
function collect(): Pkg[] {
const pkgs: Pkg[] = []
for (const rel of globSync('packages/*/*/package.json', { cwd: root })) {
const json = JSON.parse(readFileSync(resolve(root, rel), 'utf8')) as {
name: string
peerDependencies?: Record<string, string>
}
if (!json.name.startsWith(SCOPE)) continue
const deps = Object.keys(json.peerDependencies ?? {})
.filter(d => d.startsWith(SCOPE))
.map(d => d.slice(SCOPE.length))
.sort()
const [, group, leaf] = rel.split('/')
if (group === undefined || leaf === undefined) throw new Error(`gen-module-graph: unexpected package path ${rel}`)
pkgs.push({ short: json.name.slice(SCOPE.length), group, rel: dirname(rel), deps })
}
return topoSort(pkgs)
}
/**
* Order packages low-level → high-level: a package appears only after every
* package it depends on. Kahn-style layering with an alphabetical tiebreak
* within each layer, so the output stays deterministic (the freshness check
* compares whole-file). The graph is a DAG, so this always terminates; a cycle
* would leave nodes unplaced and throw.
*/
function topoSort(pkgs: Pkg[]): Pkg[] {
const remaining = new Map(pkgs.map(p => [p.short, p]))
const placed = new Set<string>()
const out: Pkg[] = []
while (remaining.size > 0) {
const ready = [...remaining.values()]
.filter(p => p.deps.every(d => placed.has(d)))
.sort(comparePackages)
if (ready.length === 0) throw new Error(`gen-module-graph: dependency cycle among ${[...remaining.keys()].join(', ')}`)
for (const p of ready) {
out.push(p)
placed.add(p.short)
remaining.delete(p.short)
}
}
return out
}
function comparePackages(a: Pkg, b: Pkg): number {
const groupA = GROUP_ORDER.indexOf(a.group)
const groupB = GROUP_ORDER.indexOf(b.group)
const normA = groupA === -1 ? Number.MAX_SAFE_INTEGER : groupA
const normB = groupB === -1 ? Number.MAX_SAFE_INTEGER : groupB
return normA - normB || a.group.localeCompare(b.group) || a.short.localeCompare(b.short)
}
function nodeId(prefix: string, value: string): string {
return `${prefix}_${value.replace(/[^a-zA-Z0-9_]/g, '_')}`
}
function escLabel(value: string): string {
return value.replace(/"/g, '\\"')
}
function packageLink(pkg: Pkg): string {
return `[\`${pkg.short}\`](../${pkg.rel})`
}
@@ -170,7 +104,7 @@ function render(pkgs: Pkg[]): string {
].join('\n')
}
const content = render(collect())
const content = render(collectPackageGraph(root, GROUP_ORDER, 'gen-module-graph'))
if (process.argv.includes('--check')) {
let committed: string | null = null
+2 -88
View File
@@ -45,6 +45,7 @@
import { globSync, readFileSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import ts from 'typescript'
import { parseJsDoc, pointer, rawJsDoc, reportViolations } from './jsdoc.ts'
const root = resolve(import.meta.dirname, '..')
const OUT = 'docs/persistence-catalog.md'
@@ -95,12 +96,6 @@ export interface AnnotatedLogEventEntry extends LogEventEntry {
surface: boolean
}
/** Repo-relative source pointer `file:line` for a node's first character. */
function pointer(rel: string, sf: ts.SourceFile, node: ts.Node): string {
const { line } = sf.getLineAndCharacterOfPosition(node.getStart(sf))
return `${rel}:${line + 1}`
}
const printer = ts.createPrinter({ removeComments: true })
/**
@@ -118,87 +113,6 @@ function payloadText(type: ts.TypeNode, sf: ts.SourceFile): string {
.trim()
}
/** The raw `/** … */` JSDoc block immediately preceding a node, or '' if none. */
function rawJsDoc(text: string, node: ts.Node): string {
const ranges = ts.getLeadingCommentRanges(text, node.getFullStart()) ?? []
const jsdoc = ranges.filter(r => text.slice(r.pos, r.pos + 3) === '/**').at(-1)
return jsdoc ? text.slice(jsdoc.pos, jsdoc.end) : ''
}
/**
* Parse a raw JSDoc block into description prose, flagging whether any `@mode`
* tag is present (forbidden on log events). Output obeys the repo's markdown
* conventions so the generated file passes verify-md-wrap: each prose paragraph
* collapses to ONE physical line, and a `-` bullet list is preserved with each
* item on its own single line (continuation lines folded in). `{@link Foo}`
* unwraps to `Foo`. Description prose ends at the FIRST block tag (standard
* JSDoc semantics): tag lines and their continuation lines are never prose.
*/
function parseJsDoc(raw: string): { doc: string; hasMode: boolean } {
const inner = raw
.replace(/^\/\*\*/, '')
.replace(/\*\/$/, '')
.split('\n')
.map(l => l.replace(/^\s*\*?\s?/, '').replace(/\s+$/, ''))
let hasMode = false
let inTags = false
const blocks: string[] = []
let para: string[] = []
let list: string[] = []
let item: string[] = []
const join = (parts: string[]): string => parts.join(' ').replace(/\s+/g, ' ').trim()
const flushItem = (): void => {
if (item.length) list.push(join(item))
item = []
}
const flushList = (): void => {
flushItem()
if (list.length) blocks.push(list.join('\n')) // one block, items on own lines
list = []
}
const flushPara = (): void => {
flushList()
if (para.length) blocks.push(join(para))
para = []
}
for (const line of inner) {
// Tag detection runs on the trimmed line: the normalization above strips at
// most one post-`*` space, so an extra-indented `* @mode` still reaches
// here with leading whitespace and must not leak into prose.
const tagLine = line.trimStart()
if (/^@mode\b/.test(tagLine)) { hasMode = true; flushPara(); inTags = true; continue }
if (tagLine.startsWith('@')) { flushPara(); inTags = true; continue }
if (inTags) continue // block-tag territory: continuations are never prose
if (line.trim() === '') { flushPara(); continue }
if (/^-\s+/.test(line)) {
// A list item starts: a pending paragraph (e.g. an intro line directly
// above the list, no blank between) flushes FIRST so it renders above.
flushItem()
if (para.length) { blocks.push(join(para)); para = [] }
item.push(line)
continue
}
if (item.length) { item.push(line); continue } // continuation of current item
para.push(line)
}
flushPara()
const doc = blocks.join('\n\n').replace(/\{@link\s+([^}]+)\}/g, '$1').trim()
return { doc, hasMode }
}
/**
* Throw one aggregate error for every completeness violation a walk collected.
* Aggregation is deliberate: a remediation pass sees the whole list at once
* instead of replaying the gate once per offender.
*/
function reportViolations(violations: string[]): void {
if (violations.length === 0) return
throw new Error(
`gen-persistence-catalog: ${violations.length} JSDoc completeness violation(s):\n`
+ violations.map(v => ` ${v}`).join('\n'),
)
}
/**
* Every `interface SessionEventMap` declaration in a source file: the owning
* top-level declaration (in `@deepseek-ai/dsh-session`) and any declaration
@@ -323,7 +237,7 @@ export function collectLogEvents(scanRoot: string = root): LogEventEntry[] {
}
}
}
reportViolations(violations)
reportViolations('gen-persistence-catalog', violations)
return entries
}
+18 -15
View File
@@ -1,14 +1,13 @@
/**
* Shared JSDoc parsing and completeness-check helpers for the documentation
* gates: the cordis catalog generator (`scripts/gen-cordis-catalog.ts` — the
* events + `ctx.<key>` service surface), the plugin config catalog generator
* (`scripts/gen-config-catalog.ts`, which renders the parsed prose), and the
* export-surface gate (`scripts/verify-export-jsdoc.ts` — every module-level
* export). One home for the mechanics so "documented" means the same thing on
* every gated surface: description prose ends at the first block tag; every
* checkable parameter needs a non-empty `@param`; a non-void ANNOTATED return
* needs a non-empty `@returns`; a stale `@param` naming no real parameter
* errors.
* gates: the cordis and persistence catalog generators
* (`scripts/gen-cordis-catalog.ts` / `scripts/gen-persistence-catalog.ts`),
* the plugin config catalog generator (`scripts/gen-config-catalog.ts`), and
* the export-surface gate (`scripts/verify-export-jsdoc.ts`). One home for the
* mechanics so "documented" means the same thing on every gated surface:
* description prose ends at the first block tag; every checkable parameter
* needs a non-empty `@param`; a non-void ANNOTATED return needs a non-empty
* `@returns`; a stale `@param` naming no real parameter errors.
*/
import ts from 'typescript'
@@ -39,15 +38,17 @@ export type Mode = 'emit' | 'waterfall' | 'parallel' | 'serial'
* tag lines and their continuation lines are never prose, so `@param` /
* `@returns` blocks are invisible to the rendered catalog.
* @param raw - the raw comment text including the JSDoc delimiters.
* @returns the collapsed description prose plus the parsed `@mode` (or null).
* @returns the collapsed description prose, parsed valid `@mode` (or null),
* and whether any `@mode` tag was present.
*/
export function parseJsDoc(raw: string): { doc: string; mode: Mode | null } {
export function parseJsDoc(raw: string): { doc: string; mode: Mode | null; hasMode: boolean } {
const inner = raw
.replace(/^\/\*\*/, '')
.replace(/\*\/$/, '')
.split('\n')
.map(l => l.replace(/^\s*\*?\s?/, '').replace(/\s+$/, ''))
let mode: Mode | null = null
let hasMode = false
let inTags = false
const blocks: string[] = []
let para: string[] = []
@@ -69,9 +70,11 @@ export function parseJsDoc(raw: string): { doc: string; mode: Mode | null } {
para = []
}
for (const line of inner) {
const m = /^@mode\s+(emit|waterfall|parallel|serial)\s*$/.exec(line)
if (m) { mode = m[1] as Mode; flushPara(); inTags = true; continue }
if (line.startsWith('@')) { flushPara(); inTags = true; continue }
const tagLine = line.trimStart()
const m = /^@mode\s+(emit|waterfall|parallel|serial)\s*$/.exec(tagLine)
if (m) { mode = m[1] as Mode; hasMode = true; flushPara(); inTags = true; continue }
if (/^@mode\b/.test(tagLine)) { hasMode = true; flushPara(); inTags = true; continue }
if (tagLine.startsWith('@')) { flushPara(); inTags = true; continue }
if (inTags) continue // block-tag territory: continuations are never prose
if (line.trim() === '') { flushPara(); continue }
if (/^-\s+/.test(line)) {
@@ -87,7 +90,7 @@ export function parseJsDoc(raw: string): { doc: string; mode: Mode | null } {
}
flushPara()
const doc = blocks.join('\n\n').replace(/\{@link\s+([^}]+)\}/g, '$1').trim()
return { doc, mode }
return { doc, mode, hasMode }
}
/**
+23
View File
@@ -0,0 +1,23 @@
/** Shared Markdown parsing and depth-first traversal for documentation gates. */
import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmFromMarkdown } from 'mdast-util-gfm'
import { gfm } from 'micromark-extension-gfm'
import type { Nodes } from 'mdast'
/** Parse GitHub-flavored Markdown with the repository's standard extensions. */
export function parseMarkdown(source: string): Nodes {
return fromMarkdown(source, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] })
}
/**
* Visit a Markdown tree depth-first; returning false prunes a node's children.
* @param node - current tree node.
* @param visitor - callback invoked before each node's children.
*/
export function visitMarkdown(node: Nodes, visitor: (node: Nodes) => boolean | void): void {
if (visitor(node) === false) return
if ('children' in node) {
for (const child of node.children) visitMarkdown(child, visitor)
}
}
+93
View File
@@ -0,0 +1,93 @@
/**
* Shared workspace-package graph discovery and Mermaid identifier helpers for
* the generated module graph and relationship-diagram generators. Each caller
* supplies its own group ordering because the documents use different visual
* priorities; manifest parsing and dependency-safe ordering have one owner.
*/
import { globSync, readFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
const SCOPE = '@deepseek-ai/dsh-'
/** One harness package and its in-repo peer-dependency edges. */
export interface PackageGraphNode {
/** Package name with the `@deepseek-ai/dsh-` prefix removed. */
short: string
/** Full npm package name. */
name: string
/** Package group from `packages/<group>/<pkg>`. */
group: string
/** Repo-relative package directory. */
rel: string
/** Short names of in-repo peer dependencies, sorted. */
deps: string[]
}
/**
* Read every harness package manifest and return dependency-safe graph nodes.
* @param root - absolute repository root.
* @param groupOrder - caller-specific tiebreak order for packages in the same dependency layer.
* @param gate - command name used in structural error messages.
* @returns package nodes ordered after all of their in-repo dependencies.
*/
export function collectPackageGraph(root: string, groupOrder: readonly string[], gate: string): PackageGraphNode[] {
const packages: PackageGraphNode[] = []
for (const rel of globSync('packages/*/*/package.json', { cwd: root }).sort()) {
const json = JSON.parse(readFileSync(resolve(root, rel), 'utf8')) as {
name: string
peerDependencies?: Record<string, string>
}
if (!json.name.startsWith(SCOPE)) continue
const [, group, leaf] = rel.split('/')
if (group === undefined || leaf === undefined) throw new Error(`${gate}: unexpected package path ${rel}`)
const deps = Object.keys(json.peerDependencies ?? {})
.filter(dep => dep.startsWith(SCOPE))
.map(dep => dep.slice(SCOPE.length))
.sort()
packages.push({
short: json.name.slice(SCOPE.length),
name: json.name,
group,
rel: dirname(rel),
deps,
})
}
return topoSort(packages, groupOrder, gate)
}
function topoSort(packages: PackageGraphNode[], groupOrder: readonly string[], gate: string): PackageGraphNode[] {
const remaining = new Map(packages.map(pkg => [pkg.short, pkg]))
const placed = new Set<string>()
const out: PackageGraphNode[] = []
while (remaining.size > 0) {
const ready = [...remaining.values()]
.filter(pkg => pkg.deps.every(dep => placed.has(dep)))
.sort((a, b) => comparePackages(a, b, groupOrder))
if (ready.length === 0) throw new Error(`${gate}: dependency cycle among ${[...remaining.keys()].join(', ')}`)
for (const pkg of ready) {
out.push(pkg)
placed.add(pkg.short)
remaining.delete(pkg.short)
}
}
return out
}
function comparePackages(a: PackageGraphNode, b: PackageGraphNode, groupOrder: readonly string[]): number {
const groupA = groupOrder.indexOf(a.group)
const groupB = groupOrder.indexOf(b.group)
const normA = groupA === -1 ? Number.MAX_SAFE_INTEGER : groupA
const normB = groupB === -1 ? Number.MAX_SAFE_INTEGER : groupB
return normA - normB || a.group.localeCompare(b.group) || a.short.localeCompare(b.short)
}
/** Stable Mermaid id for a graph value. */
export function graphNodeId(prefix: string, value: string): string {
return `${prefix}_${value.replace(/[^a-zA-Z0-9_]/g, '_')}`
}
/** Escape a value embedded in a quoted Mermaid label. */
export function escapeMermaidLabel(value: string): string {
return value.replace(/"/g, '\\"')
}
+80
View File
@@ -0,0 +1,80 @@
/** Shared repository file discovery and line-oriented reference scanning. */
import { globSync, readFileSync, realpathSync } from 'node:fs'
import { relative, resolve } from 'node:path'
/** One authored path plus its canonical target for symlink deduplication. */
export interface RepoFile {
/** Absolute path matched by the caller's glob. */
abs: string
/** Absolute canonical path used only for deduplication. */
real: string
}
/** A rejected line-oriented repository reference. */
export interface ReferenceViolation {
/** Repo-relative file containing the reference. */
file: string
/** 1-based line containing the reference. */
line: number
/** Normalized reference text. */
ref: string
}
/**
* Expand repository-relative globs and deduplicate symlinked files.
* @param root - absolute repository root.
* @param patterns - repository-relative glob patterns, processed in order.
* @param isExcluded - optional predicate over each matched relative path.
* @returns matched files in stable first-seen order.
*/
export function uniqueRepoFiles(
root: string,
patterns: readonly string[],
isExcluded: (relativePath: string) => boolean = () => false,
): RepoFile[] {
const seen = new Set<string>()
const files: RepoFile[] = []
for (const pattern of patterns) {
for (const match of globSync(pattern, { cwd: root })) {
if (isExcluded(match)) continue
const abs = resolve(root, match)
const real = realpathSync(abs)
if (seen.has(real)) continue
seen.add(real)
files.push({ abs, real })
}
}
return files
}
/**
* Scan regex matches line by line and return the normalized matches rejected by
* a caller predicate.
* @param root - absolute repository root used for violation paths.
* @param absPath - absolute text-file path to scan.
* @param pattern - global regex matched independently against each line.
* @param normalize - maps raw regex text to the reference the gate evaluates.
* @param isViolation - returns true when the normalized reference is invalid.
* @returns every rejected reference in source order.
*/
export function findReferenceViolations(
root: string,
absPath: string,
pattern: RegExp,
normalize: (raw: string) => string,
isViolation: (ref: string) => boolean,
): ReferenceViolation[] {
const file = relative(root, absPath)
const out: ReferenceViolation[] = []
const lines = readFileSync(absPath, 'utf8').split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line === undefined) continue
for (const match of line.matchAll(pattern)) {
const ref = normalize(match[0])
if (isViolation(ref)) out.push({ file, line: i + 1, ref })
}
}
return out
}
+7 -1
View File
@@ -129,6 +129,7 @@ function gatesForMode(selected: Mode): Gate[] {
case 'ci-lint':
return [
lintGate(),
pnpmScript('duplication', 'duplication'),
]
case 'ci-coverage':
return [
@@ -151,7 +152,9 @@ function gatesForMode(selected: Mode): Gate[] {
]
case 'pre-push':
return [
pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }),
pnpmScript('test', 'test'),
pnpmScript('duplication', 'duplication'),
pnpmScript('snapshot', 'test:snapshot'),
pnpmScript('build', 'build'),
...hygieneLeafGates({ artifactNeeds: ['build'] }),
@@ -163,9 +166,11 @@ function gatesForMode(selected: Mode): Gate[] {
function ciPrimaryGates(): Gate[] {
return [
pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }),
pnpmScript('constraints', 'constraints'),
pnpmScript('typecheck', 'typecheck'),
lintGate(),
pnpmScript('duplication', 'duplication'),
coverageGate(),
pnpmScript('snapshot', 'test:snapshot'),
demoSmokeGate({ needs: ['lint'] }),
@@ -184,6 +189,7 @@ function ciPrimaryGates(): Gate[] {
function ciStaticGates(): Gate[] {
return [
pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }),
pnpmScript('constraints', 'constraints'),
demoSmokeGate(),
...docSyncLeafGates(),
@@ -328,7 +334,7 @@ function builtBinSmokeGate(): Gate {
'packages/ui/stdio-agent/tests/built-bin.e2e.ts',
'packages/ui/acp-agent/tests/built-bin.e2e.ts',
// The worker-entry packages' built bundles: the only automated proof
// that lib/index.js resolves its sibling lib/worker.js under plain node
// that lib/index.js resolves its sibling lib/worker.cjs under plain node
// (the e2e lane runs unbuilt, so these files self-skip there).
'packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts',
'packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts',
+794
View File
@@ -0,0 +1,794 @@
#!/usr/bin/env python3
"""Keyless full-turn and snapshot smoke for the Python SDK runtime."""
from __future__ import annotations
import argparse
import difflib
import json
import os
import queue
import subprocess
import tempfile
import threading
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import TYPE_CHECKING, Callable
if TYPE_CHECKING:
from deepseek_harness import TurnResult
EXPECTED_TEXT = "runtime smoke ok"
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"
SNAPSHOT_PROMPT = "Run the advanced packaged-runtime snapshot scenario."
SNAPSHOT_SESSION_ID = "advanced-executable"
SNAPSHOT_DIRECT_CHILD_PROMPT = "Reply with exactly DIRECT_CHILD_OK and nothing else."
SNAPSHOT_WORKFLOW_CHILD_PROMPT = "Reply with exactly WORKFLOW_CHILD_OK and nothing else."
SNAPSHOT_FINAL_TEXT = "ADVANCED_EXECUTABLE_OK"
SNAPSHOT_MOUNT_CODE = """\
return (ctx) => {
harness.registerTool(ctx, harness.defineTool({
name: 'snapshot_double',
description: 'Double a number for executable snapshot verification.',
parameters: { value: { type: 'number', required: true } },
async execute(args) {
return [{ type: 'text', text: String(args.value * 2) }]
}
}))
}
"""
SNAPSHOT_WORKFLOW_SCRIPT = (
"phase('Delegate')\n"
f"const reply = await agent('{SNAPSHOT_WORKFLOW_CHILD_PROMPT}', {{ label: 'workflow-child' }})\n"
"return { reply }"
)
SNAPSHOT_DIRECTORY = (
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "advanced"
)
SNAPSHOT_FILENAMES = ("result.json", "session.jsonl", "session.1.jsonl", "session.2.jsonl")
CUSTOM_CORDIS = """\
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
- id: agent-core
name: '@deepseek-ai/dsh-agent-core'
config:
tools:
mode: both
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js process.env.DSH_SESSION_ROOT
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: !!js process.env.DSH_CWD
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker'
- id: subagents
name: '@deepseek-ai/dsh-subagent'
- id: subagent-spawn
name: '@deepseek-ai/dsh-subagent-spawn'
config:
providerName: spawn
- id: subagent-tool
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn
- id: workflow-engine
name: '@deepseek-ai/dsh-workflow-workerthread'
config:
provider: spawn
- id: workflow-tool
name: '@deepseek-ai/dsh-tool-workflow'
- id: cordis-tool
name: '@deepseek-ai/dsh-tool-cordis'
"""
class MockModelHandler(BaseHTTPRequestHandler):
"""Return deterministic text, worker, and orchestration completions."""
requests: list[dict[str, object]] = []
def do_POST(self) -> None:
content_length = int(self.headers.get("content-length", "0"))
body = json.loads(self.rfile.read(content_length))
self.requests.append(body)
self.send_response(200)
self.send_header("content-type", "text/event-stream")
self.end_headers()
chunks = completion_chunks(body)
for chunk in chunks:
self.wfile.write(f"data: {json.dumps(chunk)}\n\n".encode())
self.wfile.write(b"data: [DONE]\n\n")
self.wfile.flush()
def log_message(self, _format: str, *_args: object) -> None:
return
def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
"""Choose the next deterministic model response from request history."""
messages = body.get("messages")
if not isinstance(messages, list) or not messages:
raise AssertionError(f"model request has no messages: {body}")
latest = messages[-1]
if not isinstance(latest, dict):
raise AssertionError(f"model request has an invalid latest message: {body}")
if latest.get("role") == "tool":
call_id, tool_name = latest_tool_call(messages)
tool_text = message_text(latest.get("content"))
advanced = advanced_tool_followup(body, call_id, tool_name, tool_text)
if advanced is not None:
return advanced
if "42" not in tool_text:
raise AssertionError(f"{tool_name} worker returned no expected value: {latest}")
if tool_name == "run_code":
return text_chunks(CODE_WORKER_TEXT)
if tool_name == "workflow":
return text_chunks(WORKFLOW_WORKER_TEXT)
raise AssertionError(f"unexpected tool follow-up: {tool_name}")
prompt = message_text(latest.get("content"))
if prompt == SNAPSHOT_DIRECT_CHILD_PROMPT:
return text_chunks("DIRECT_CHILD_OK")
if prompt == SNAPSHOT_WORKFLOW_CHILD_PROMPT:
return text_chunks("WORKFLOW_CHILD_OK")
if prompt == SNAPSHOT_PROMPT:
assert_advertised_tool(body, "cordis_mount")
return tool_call_chunks(
"advanced-mount",
"cordis_mount",
{"code": SNAPSHOT_MOUNT_CODE},
)
if prompt == CODE_PROMPT:
assert_advertised_tool(body, "run_code")
return tool_call_chunks("call-code-worker", "run_code", {"code": "return 6 * 7"})
if prompt == WORKFLOW_PROMPT:
assert_advertised_tool(body, "workflow")
return tool_call_chunks(
"call-workflow-worker",
"workflow",
{
"script": "return 6 * 7",
"meta": {
"name": "pkg-worker-smoke",
"description": "exercise the packaged workflow worker",
},
},
)
return text_chunks(EXPECTED_TEXT)
def advanced_tool_followup(
body: dict[str, object],
call_id: str,
tool_name: str,
tool_text: str,
) -> list[dict[str, object]] | None:
"""Advance the executable snapshot's deterministic parent tool chain."""
if not call_id.startswith("advanced-"):
return None
if call_id == "advanced-mount" and tool_name == "cordis_mount":
if "mounted dyn-1" not in tool_text:
raise AssertionError(f"cordis_mount returned no mount id: {tool_text}")
assert_advertised_tool(body, "run_code")
assert_advertised_tool(body, "snapshot_double")
return tool_call_chunks(
"advanced-code",
"run_code",
{"code": "return await tools.snapshot_double({ value: 21 })"},
)
if call_id == "advanced-code" and tool_name == "run_code":
if "42" not in tool_text:
raise AssertionError(f"run_code returned no dynamic-tool value: {tool_text}")
assert_advertised_tool(body, "subagent")
return tool_call_chunks(
"advanced-direct-child",
"subagent",
{
"description": "Check direct child",
"prompt": SNAPSHOT_DIRECT_CHILD_PROMPT,
},
)
if call_id == "advanced-direct-child" and tool_name == "subagent":
if "DIRECT_CHILD_OK" not in tool_text:
raise AssertionError(f"subagent returned no expected child value: {tool_text}")
assert_advertised_tool(body, "workflow")
return tool_call_chunks(
"advanced-workflow",
"workflow",
{
"script": SNAPSHOT_WORKFLOW_SCRIPT,
"meta": {
"name": "advanced-exe-snapshot",
"description": "exercise one packaged workflow child",
},
},
)
if call_id == "advanced-workflow" and tool_name == "workflow":
if "WORKFLOW_CHILD_OK" not in tool_text:
raise AssertionError(f"workflow returned no expected child value: {tool_text}")
assert_advertised_tool(body, "cordis_unmount")
return tool_call_chunks(
"advanced-unmount",
"cordis_unmount",
{"id": "dyn-1"},
)
if call_id == "advanced-unmount" and tool_name == "cordis_unmount":
if "unmounted dyn-1" not in tool_text:
raise AssertionError(f"cordis_unmount returned no disposal result: {tool_text}")
if "snapshot_double" in advertised_tool_names(body):
raise AssertionError("snapshot_double remained advertised after cordis_unmount")
return text_chunks(SNAPSHOT_FINAL_TEXT)
raise AssertionError(f"unexpected advanced tool follow-up: {call_id} {tool_name}: {tool_text}")
def text_chunks(text: str) -> list[dict[str, object]]:
"""Build a complete streaming text response."""
return [
{"choices": [{"delta": {"role": "assistant", "content": None, "reasoning_content": ""}}]},
{"choices": [{"delta": {"content": text}}]},
{
"choices": [{"delta": {"content": ""}, "finish_reason": "stop"}],
"usage": {"prompt_tokens": 3, "completion_tokens": 3},
},
]
def tool_call_chunks(call_id: str, name: str, arguments: dict[str, object]) -> list[dict[str, object]]:
"""Build a complete streaming function-call response."""
return [
{"choices": [{"delta": {"role": "assistant", "content": None, "reasoning_content": ""}}]},
{
"choices": [{
"delta": {
"tool_calls": [{
"index": 0,
"id": call_id,
"type": "function",
"function": {"name": name, "arguments": json.dumps(arguments)},
}],
},
}],
},
{
"choices": [{"delta": {"content": ""}, "finish_reason": "tool_calls"}],
"usage": {"prompt_tokens": 3, "completion_tokens": 3},
},
]
def latest_tool_call(messages: list[object]) -> tuple[str, str]:
"""Find the assistant call id and name paired with the latest tool result."""
for message in reversed(messages[:-1]):
if not isinstance(message, dict):
continue
calls = message.get("tool_calls")
if not isinstance(calls, list):
continue
for call in reversed(calls):
if not isinstance(call, dict):
continue
function = call.get("function")
call_id = call.get("id")
if (
isinstance(call_id, str)
and isinstance(function, dict)
and isinstance(function.get("name"), str)
):
return call_id, function["name"]
raise AssertionError(f"tool result has no preceding assistant tool call: {messages}")
def message_text(content: object) -> str:
"""Read OpenAI text content in either string or block-list form."""
if isinstance(content, str):
return content
if isinstance(content, list):
return "".join(
block.get("text", "")
for block in content
if isinstance(block, dict) and isinstance(block.get("text"), str)
)
return ""
def advertised_tool_names(body: dict[str, object]) -> set[str]:
"""Return the model-facing tool names advertised on one request."""
tools = body.get("tools")
if not isinstance(tools, list):
raise AssertionError(f"model request advertised no tools: {body}")
names: set[str] = set()
for tool in tools:
if not isinstance(tool, dict):
continue
function = tool.get("function")
if isinstance(function, dict) and isinstance(function.get("name"), str):
names.add(function["name"])
return names
def assert_advertised_tool(body: dict[str, object], expected: str) -> None:
"""Require the packaged deployment to expose the requested tool."""
names = advertised_tool_names(body)
if expected not in names:
raise AssertionError(f"model request did not advertise {expected}: {names}")
class MockModel:
def __enter__(self) -> "MockModel":
MockModelHandler.requests.clear()
self.server = ThreadingHTTPServer(("127.0.0.1", 0), MockModelHandler)
self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
self.thread.start()
host, port = self.server.server_address
self.url = f"http://{host}:{port}"
return self
def __exit__(self, _exc_type: object, _exc: object, _tb: object) -> None:
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--scenario",
choices=("all", "sdk-default", "sdk-custom", "sdk-snapshot", "direct"),
default="all",
)
parser.add_argument("--exe", type=Path)
parser.add_argument("--update-snapshots", action="store_true")
args = parser.parse_args()
if args.scenario in {"all", "sdk-custom", "sdk-snapshot", "direct"} and args.exe is None:
parser.error("--exe is required for custom, snapshot, and direct scenarios")
if args.update_snapshots and args.scenario not in {"all", "sdk-snapshot"}:
parser.error("--update-snapshots requires --scenario sdk-snapshot or all")
if args.exe is not None and not args.exe.is_file():
parser.error(f"runtime executable does not exist: {args.exe}")
with MockModel() as model:
if args.scenario in {"all", "sdk-default"}:
smoke_sdk_default(model.url)
if args.scenario in {"all", "sdk-custom"}:
assert args.exe is not None
smoke_sdk_custom(model.url, args.exe.resolve())
if args.scenario in {"all", "sdk-snapshot"}:
assert args.exe is not None
smoke_sdk_snapshot(model.url, args.exe.resolve(), args.update_snapshots)
if args.scenario in {"all", "direct"}:
assert args.exe is not None
smoke_direct(model.url, args.exe.resolve())
if not MockModelHandler.requests:
raise AssertionError("mock model endpoint received no requests")
print(f"smoke-python-runtime: {args.scenario} passed")
def smoke_sdk_default(base_url: str) -> None:
from deepseek_harness import DeepSeekHarness
with tempfile.TemporaryDirectory(prefix="dsh-sdk-default-") as temporary:
root = Path(temporary).resolve()
sessions = root / "sessions"
with DeepSeekHarness(
model="smoke-model",
cwd=str(root),
session_root=str(sessions),
api_key="sk-keyless-smoke",
base_url=base_url,
request_timeout_seconds=60,
) as harness:
result = harness.run("reply with the smoke text", session_id="default-smoke")
assert result.status == "ok", result
assert result.final_response == EXPECTED_TEXT, result.final_response
assert_session_log(sessions, root, EXPECTED_TEXT)
def smoke_sdk_custom(base_url: str, executable: Path) -> None:
from deepseek_harness import DeepSeekHarness
with tempfile.TemporaryDirectory(prefix="dsh-sdk-custom-") as temporary:
root = Path(temporary).resolve()
sessions = root / "sessions"
cordis = root / "cordis.yml"
cordis.write_text(CUSTOM_CORDIS)
with DeepSeekHarness(
model="smoke-model",
cwd=str(root),
session_root=str(sessions),
cordis=str(cordis),
runtime_bin=str(executable),
api_key="sk-keyless-smoke",
base_url=base_url,
request_timeout_seconds=60,
) as harness:
text_result = harness.run("reply with the smoke text", session_id="custom-smoke")
code_result = harness.run(CODE_PROMPT, session_id="custom-smoke")
workflow_result = harness.run(WORKFLOW_PROMPT, session_id="custom-smoke")
assert text_result.status == "ok", text_result
assert text_result.final_response == EXPECTED_TEXT, text_result.final_response
assert code_result.status == "ok", code_result
assert code_result.final_response == CODE_WORKER_TEXT, code_result.final_response
assert workflow_result.status == "ok", workflow_result
assert workflow_result.final_response == WORKFLOW_WORKER_TEXT, workflow_result.final_response
assert_session_log(sessions, root, EXPECTED_TEXT, CODE_WORKER_TEXT, WORKFLOW_WORKER_TEXT)
def smoke_sdk_snapshot(base_url: str, executable: Path, update_snapshots: bool) -> None:
"""Drive and compare the advanced SDK/executable behavioral snapshot."""
from deepseek_harness import DeepSeekHarness
with tempfile.TemporaryDirectory(prefix="dsh-sdk-snapshot-") as temporary:
root = Path(temporary).resolve()
sessions = root / "sessions"
cordis = root / "cordis.yml"
cordis.write_text(CUSTOM_CORDIS)
with DeepSeekHarness(
model="smoke-model",
cwd=str(root),
session_root=str(sessions),
cordis=str(cordis),
runtime_bin=str(executable),
api_key="sk-keyless-smoke",
base_url=base_url,
request_timeout_seconds=60,
) as harness:
result = harness.run(SNAPSHOT_PROMPT, session_id=SNAPSHOT_SESSION_ID)
assert result.status == "ok", result
assert result.final_response == SNAPSHOT_FINAL_TEXT, result.final_response
methods = [notification.method for notification in result.notifications]
if methods.count("subagent.started") != 2 or methods.count("subagent.finished") != 2:
raise AssertionError(f"advanced snapshot emitted unexpected subagent lifecycle: {methods}")
if not any(event.get("type") == "tool/code-dispatch" for event in result.events):
raise AssertionError("advanced snapshot emitted no tool/code-dispatch event")
logs = read_session_logs(sessions)
child_ids = snapshot_child_ids(result)
expected_ids = {SNAPSHOT_SESSION_ID, *child_ids}
if set(logs) != expected_ids:
raise AssertionError(f"advanced snapshot expected parent plus two child logs: {sorted(logs)}")
if "DIRECT_CHILD_OK" not in render_jsonl(logs[child_ids[0]]):
raise AssertionError("first advanced child log has no direct-subagent result")
if "WORKFLOW_CHILD_OK" not in render_jsonl(logs[child_ids[1]]):
raise AssertionError("second advanced child log has no workflow-subagent result")
files = build_snapshot_files(result, logs, child_ids, root)
compare_snapshot_files(files, update_snapshots)
def smoke_direct(base_url: str, executable: Path) -> None:
with tempfile.TemporaryDirectory(prefix="dsh-direct-") as temporary:
root = Path(temporary).resolve()
sessions = root / "sessions"
cordis = root / "cordis.yml"
cordis.write_text(CUSTOM_CORDIS)
environment = {
**os.environ,
"DSH_CORDIS_CONFIG": str(cordis),
"DSH_SESSION_ROOT": str(sessions),
"DSH_CWD": str(root),
"DEEPSEEK_API_KEY": "sk-keyless-smoke",
"DEEPSEEK_BASE_URL": base_url,
}
peer = RuntimePeer([str(executable)], root, environment)
try:
peer.send({"jsonrpc": "2.0", "id": "initialize", "method": "initialize", "params": {"cwd": str(root), "model": "smoke-model"}})
peer.read_until(lambda message: message.get("id") == "initialize")
peer.send({
"jsonrpc": "2.0",
"id": "prompt",
"method": "session/prompt",
"params": {"sessionId": "direct-smoke", "contentBlocks": [{"type": "text", "text": "reply with the smoke text"}]},
})
messages = peer.read_until(lambda message: message.get("id") == "prompt")
if not any(message.get("method") == "session.finished" and message.get("params", {}).get("status") == "ok" for message in messages):
messages.extend(peer.read_until(lambda message: message.get("method") == "session.finished"))
event_text = json.dumps(messages)
if EXPECTED_TEXT not in event_text:
raise AssertionError(f"direct runtime emitted no final response: {messages}")
peer.send({"jsonrpc": "2.0", "id": "shutdown", "method": "shutdown"})
peer.read_until(lambda message: message.get("id") == "shutdown")
finally:
peer.close()
assert_session_log(sessions, root, EXPECTED_TEXT)
class RuntimePeer:
def __init__(self, argv: list[str], cwd: Path, environment: dict[str, str]) -> None:
self.process = subprocess.Popen(
argv,
cwd=cwd,
env=environment,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
bufsize=1,
)
self.stdout: queue.Queue[str | None] = queue.Queue()
self.stderr: list[str] = []
threading.Thread(target=self._read_stdout, daemon=True).start()
threading.Thread(target=self._read_stderr, daemon=True).start()
def send(self, message: dict[str, object]) -> None:
if self.process.stdin is None:
raise RuntimeError("runtime stdin is unavailable")
self.process.stdin.write(json.dumps(message) + "\n")
self.process.stdin.flush()
def read_until(self, predicate: Callable[[dict[str, object]], bool]) -> list[dict[str, object]]:
deadline = time.monotonic() + 60
messages: list[dict[str, object]] = []
while time.monotonic() < deadline:
try:
line = self.stdout.get(timeout=min(0.25, deadline - time.monotonic()))
except queue.Empty:
continue
if line is None:
raise RuntimeError(f"runtime exited before expected message; stderr: {''.join(self.stderr)}")
try:
message = json.loads(line)
except json.JSONDecodeError:
continue
messages.append(message)
if predicate(message):
return messages
raise TimeoutError(f"runtime timed out; messages={messages}; stderr={''.join(self.stderr)}")
def close(self) -> None:
if self.process.stdin is not None and not self.process.stdin.closed:
self.process.stdin.close()
try:
self.process.wait(timeout=10)
except subprocess.TimeoutExpired:
self.process.kill()
self.process.wait()
if self.process.returncode not in {0, -15}:
raise RuntimeError(f"runtime exited {self.process.returncode}; stderr: {''.join(self.stderr)}")
def _read_stdout(self) -> None:
assert self.process.stdout is not None
for line in self.process.stdout:
self.stdout.put(line)
self.stdout.put(None)
def _read_stderr(self) -> None:
assert self.process.stderr is not None
self.stderr.extend(self.process.stderr)
def assert_session_log(sessions: Path, cwd: Path, *expected_texts: str) -> None:
logs = list(sessions.rglob("*.jsonl"))
if len(logs) != 1:
raise AssertionError(f"expected one JSONL session log under {sessions}, found {logs}")
lines = logs[0].read_text().splitlines()
header = json.loads(lines[0])
if header.get("cwd") != str(cwd):
raise AssertionError(f"session header cwd is not absolute/canonical: {header}")
rendered = "\n".join(lines)
for expected in expected_texts:
if expected not in rendered:
raise AssertionError(f"session log has no {expected!r} response: {logs[0]}")
def read_session_logs(sessions: Path) -> dict[str, list[dict[str, object]]]:
"""Parse every persisted JSONL session into a map keyed by header id."""
logs: dict[str, list[dict[str, object]]] = {}
for path in sorted(sessions.rglob("*.jsonl")):
records = [
json.loads(line)
for line in path.read_text(encoding="utf-8").splitlines()
if line
]
if not records or records[0].get("type") != "session":
raise AssertionError(f"session log has no header: {path}")
session_id = records[0].get("id")
if not isinstance(session_id, str):
raise AssertionError(f"session log header has no string id: {path}")
if session_id in logs:
raise AssertionError(f"duplicate persisted session id: {session_id}")
logs[session_id] = records
return logs
def snapshot_child_ids(result: "TurnResult") -> list[str]:
"""Return the two child session ids in their SDK notification order."""
child_ids: list[str] = []
for notification in result.notifications:
if notification.method != "subagent.started":
continue
payload = notification.payload
if payload.get("parentSessionId") != SNAPSHOT_SESSION_ID:
continue
child_id = payload.get("childSessionId")
if isinstance(child_id, str) and child_id not in child_ids:
child_ids.append(child_id)
if len(child_ids) != 2:
raise AssertionError(f"advanced snapshot expected two child session ids: {child_ids}")
return child_ids
def build_snapshot_files(
result: "TurnResult",
logs: dict[str, list[dict[str, object]]],
child_ids: list[str],
cwd: Path,
) -> dict[str, str]:
"""Render the SDK result and three persisted logs into stable goldens."""
replacements = [(str(cwd), "{{cwd}}"), (SNAPSHOT_SESSION_ID, "{{parent}}")]
for index, child_id in enumerate(child_ids, start=1):
replacements.append((child_id, f"{{{{child-{index}}}}}"))
agent_id = snapshot_agent_id(result, child_id)
replacements.append((agent_id, f"{{{{agent-{index}}}}}"))
replacements.sort(key=lambda pair: len(pair[0]), reverse=True)
result_value = {
"session_id": result.session_id,
"status": result.status,
"final_response": result.final_response,
"events": result.events,
"notifications": [
{"method": notification.method, "payload": notification.payload}
for notification in result.notifications
],
"session_root": result.session_root,
}
normalized_result = normalize_snapshot_value(result_value, replacements)
files = {
"result.json": json.dumps(normalized_result, indent=2, ensure_ascii=False) + "\n",
"session.jsonl": render_jsonl(
[normalize_snapshot_value(record, replacements) for record in logs[SNAPSHOT_SESSION_ID]]
),
}
for index, child_id in enumerate(child_ids, start=1):
files[f"session.{index}.jsonl"] = render_jsonl(
[normalize_snapshot_value(record, replacements) for record in logs[child_id]]
)
if tuple(files) != SNAPSHOT_FILENAMES:
raise AssertionError(f"advanced snapshot file set drifted: {tuple(files)}")
return files
def snapshot_agent_id(result: "TurnResult", child_id: str) -> str:
"""Find the successful subagent id paired with one child session."""
for notification in result.notifications:
if notification.method != "subagent.finished":
continue
payload = notification.payload
if payload.get("childSessionId") != child_id:
continue
if payload.get("provider") != "spawn" or payload.get("status") != "ok":
raise AssertionError(f"advanced child did not finish successfully: {payload}")
agent_id = payload.get("agentId")
if isinstance(agent_id, str):
return agent_id
raise AssertionError(f"advanced snapshot has no finished agent for child {child_id}")
def normalize_snapshot_value(
value: object,
replacements: list[tuple[str, str]],
) -> object:
"""Scrub volatile values and bulky request headers without losing behavior."""
if isinstance(value, str):
normalized = value
for actual, token in replacements:
normalized = normalized.replace(actual, token)
return normalized
if isinstance(value, list):
return [normalize_snapshot_value(item, replacements) for item in value]
if not isinstance(value, dict):
return value
normalized = {
key: normalize_snapshot_value(item, replacements)
for key, item in value.items()
}
if normalized.get("type") == "session" and "createdAt" in normalized:
normalized["createdAt"] = 0
if "seq" in normalized and "time" in normalized:
normalized["time"] = 0
scrub_snapshot_header(normalized)
return normalized
def scrub_snapshot_header(value: dict[object, object]) -> None:
"""Tokenize request-header bulk while retaining delta tool names."""
data = value.get("data")
if not isinstance(data, dict):
return
if value.get("type") == "request/header":
header = data.get("header")
if not isinstance(header, dict):
return
if "system" in header:
header["system"] = "{{system}}"
tools = header.get("tools")
if isinstance(tools, list):
header["tools"] = [
tool.get("name") if isinstance(tool, dict) else "{{tools}}"
for tool in tools
]
if isinstance(header.get("messagePrefix"), list):
header["messagePrefix"] = ["{{messagePrefix}}" for _ in header["messagePrefix"]]
return
if value.get("type") != "request/header-delta":
return
system = data.get("system")
if isinstance(system, dict) and isinstance(system.get("insert"), list):
system["insert"] = ["{{system}}" for _ in system["insert"]]
tools = data.get("tools")
if isinstance(tools, dict):
for key in ("added", "changed"):
if isinstance(tools.get(key), list):
tools[key] = [scrub_snapshot_tool_schema(tool) for tool in tools[key]]
if isinstance(data.get("messagePrefix"), list):
data["messagePrefix"] = ["{{messagePrefix}}" for _ in data["messagePrefix"]]
def scrub_snapshot_tool_schema(value: object) -> object:
"""Keep a changed tool's name while tokenizing its schema bulk."""
if not isinstance(value, dict):
return value
return {
key: item if key == "name" else "{{tools}}"
for key, item in value.items()
}
def render_jsonl(records: list[object]) -> str:
"""Render parsed JSON values as compact, newline-terminated JSONL."""
return "".join(
json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n"
for record in records
)
def compare_snapshot_files(files: dict[str, str], update: bool) -> None:
"""Write or exactly compare the advanced executable snapshot files."""
if update:
SNAPSHOT_DIRECTORY.mkdir(parents=True, exist_ok=True)
for name, content in files.items():
(SNAPSHOT_DIRECTORY / name).write_text(content, encoding="utf-8")
print(f"smoke-python-runtime: updated snapshots in {SNAPSHOT_DIRECTORY}")
existing = {
path.name
for path in SNAPSHOT_DIRECTORY.iterdir()
if path.is_file()
} if SNAPSHOT_DIRECTORY.is_dir() else set()
expected = set(SNAPSHOT_FILENAMES)
if existing != expected:
raise AssertionError(
"advanced snapshot files differ: "
f"missing={sorted(expected - existing)}, unexpected={sorted(existing - expected)}"
)
for name, actual in files.items():
expected_text = (SNAPSHOT_DIRECTORY / name).read_text(encoding="utf-8")
if actual == expected_text:
continue
diff = "".join(difflib.unified_diff(
expected_text.splitlines(keepends=True),
actual.splitlines(keepends=True),
fromfile=f"expected/{name}",
tofile=f"actual/{name}",
))
raise AssertionError(
f"advanced executable snapshot mismatch in {name}; "
"rerun with --update-snapshots after reviewing the behavior\n"
f"{diff}"
)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
{"type":"session","version":0,"id":"{{child-1}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{parent}}"}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"step/start","seq":2,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":3,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","workflow"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":4,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":9,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[4,5,6,7,8],"surfaceOp":"append"}
{"type":"step/end","seq":10,"time":0,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":11,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -0,0 +1,13 @@
{"type":"session","version":0,"id":"{{child-2}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{parent}}"}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"step/start","seq":2,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":3,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","workflow"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":4,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":9,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[4,5,6,7,8],"surfaceOp":"append"}
{"type":"step/end","seq":10,"time":0,"data":{"turn":1,"step":1}}
{"type":"turn/end","seq":11,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
@@ -0,0 +1,66 @@
{"type":"session","version":0,"id":"{{parent}}","createdAt":0,"cwd":"{{cwd}}"}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Run the advanced packaged-runtime snapshot scenario."}],"source":{"kind":"user"}},"surfaceOp":"append"}
{"type":"step/start","seq":2,"time":0,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":3,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","subagent","workflow"]},"reason":"initial"}}
{"type":"assistant/chunk","seq":4,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":5,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-mount","name":"cordis_mount","argumentsDelta":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n async execute(args) {\\n return [{ type: 'text', text: String(args.value * 2) }]\\n }\\n }))\\n}\\n\"}"}}}
{"type":"assistant/chunk","seq":6,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n async execute(args) {\\n return [{ type: 'text', text: String(args.value * 2) }]\\n }\\n }))\\n}\\n\"}"}}}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":9,"time":0,"data":{"turn":1,"step":1,"content":[{"type":"tool-call","id":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n async execute(args) {\\n return [{ type: 'text', text: String(args.value * 2) }]\\n }\\n }))\\n}\\n\"}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[4,5,6,7,8],"surfaceOp":"append"}
{"type":"tool/call","seq":10,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","name":"cordis_mount","arguments":"{\"code\": \"return (ctx) => {\\n harness.registerTool(ctx, harness.defineTool({\\n name: 'snapshot_double',\\n description: 'Double a number for executable snapshot verification.',\\n parameters: { value: { type: 'number', required: true } },\\n async execute(args) {\\n return [{ type: 'text', text: String(args.value * 2) }]\\n }\\n }))\\n}\\n\"}"}}
{"type":"tool/result","seq":11,"time":0,"data":{"turn":1,"step":1,"callId":"advanced-mount","content":[{"type":"text","text":"mounted dyn-1 (plugin \"<anonymous>\", state: active)"}],"isError":false},"sourceEventSeqs":[10],"surfaceOp":"append"}
{"type":"step/end","seq":12,"time":0,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":13,"time":0,"data":{"turn":1,"step":2}}
{"type":"request/header","seq":14,"time":0,"data":{"header":{"config":{"model":"smoke-model"},"system":"{{system}}","tools":["bash","bash_kill","bash_output","cordis_inspect","cordis_mount","cordis_unmount","run_code","skill","snapshot_double","subagent","workflow"]},"reason":"fallback"}}
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-code","name":"run_code","argumentsDelta":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}}}
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}}}}
{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":20,"time":0,"data":{"turn":1,"step":2,"content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[15,16,17,18,19],"surfaceOp":"append"}
{"type":"tool/call","seq":21,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\"}"}}
{"type":"tool/code-dispatch","seq":22,"time":0,"data":{"parentCallId":"advanced-code","subCallId":"advanced-code:code:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"resultSummary":"42"}}
{"type":"tool/result","seq":23,"time":0,"data":{"turn":1,"step":2,"callId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false,"meta":{"logs":[],"dispatches":1}},"sourceEventSeqs":[21],"surfaceOp":"append"}
{"type":"step/end","seq":24,"time":0,"data":{"turn":1,"step":2}}
{"type":"step/start","seq":25,"time":0,"data":{"turn":1,"step":3}}
{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-direct-child","name":"subagent","argumentsDelta":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}
{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}}}
{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":30,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":31,"time":0,"data":{"turn":1,"step":3,"content":[{"type":"tool-call","id":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[26,27,28,29,30],"surfaceOp":"append"}
{"type":"tool/call","seq":32,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","name":"subagent","arguments":"{\"description\": \"Check direct child\", \"prompt\": \"Reply with exactly DIRECT_CHILD_OK and nothing else.\"}"}}
{"type":"tool/result","seq":33,"time":0,"data":{"turn":1,"step":3,"callId":"advanced-direct-child","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"isError":false},"sourceEventSeqs":[32],"surfaceOp":"append"}
{"type":"step/end","seq":34,"time":0,"data":{"turn":1,"step":3}}
{"type":"step/start","seq":35,"time":0,"data":{"turn":1,"step":4}}
{"type":"assistant/chunk","seq":36,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":37,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-workflow","name":"workflow","argumentsDelta":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}
{"type":"assistant/chunk","seq":38,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}}}
{"type":"assistant/chunk","seq":39,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":40,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":41,"time":0,"data":{"turn":1,"step":4,"content":[{"type":"tool-call","id":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[36,37,38,39,40],"surfaceOp":"append"}
{"type":"tool/call","seq":42,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","name":"workflow","arguments":"{\"script\": \"phase('Delegate')\\nconst reply = await agent('Reply with exactly WORKFLOW_CHILD_OK and nothing else.', { label: 'workflow-child' })\\nreturn { reply }\", \"meta\": {\"name\": \"advanced-exe-snapshot\", \"description\": \"exercise one packaged workflow child\"}}"}}
{"type":"tool/result","seq":43,"time":0,"data":{"turn":1,"step":4,"callId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false},"sourceEventSeqs":[42],"surfaceOp":"append"}
{"type":"step/end","seq":44,"time":0,"data":{"turn":1,"step":4}}
{"type":"step/start","seq":45,"time":0,"data":{"turn":1,"step":5}}
{"type":"assistant/chunk","seq":46,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":47,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"tool-call-delta","index":0,"id":"advanced-unmount","name":"cordis_unmount","argumentsDelta":"{\"id\": \"dyn-1\"}"}}}
{"type":"assistant/chunk","seq":48,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}}}
{"type":"assistant/chunk","seq":49,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":50,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":51,"time":0,"data":{"turn":1,"step":5,"content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[46,47,48,49,50],"surfaceOp":"append"}
{"type":"tool/call","seq":52,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
{"type":"tool/result","seq":53,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","content":[{"type":"text","text":"unmounted dyn-1 (plugin \"<anonymous>\")"}],"isError":false},"sourceEventSeqs":[52],"surfaceOp":"append"}
{"type":"step/end","seq":54,"time":0,"data":{"turn":1,"step":5}}
{"type":"step/start","seq":55,"time":0,"data":{"turn":1,"step":6}}
{"type":"request/header-delta","seq":56,"time":0,"data":{"system":{"keepStart":62,"keepEnd":34,"insert":[]},"tools":{"added":[],"removed":["snapshot_double"],"changed":[]}}}
{"type":"assistant/chunk","seq":57,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":58,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
{"type":"assistant/chunk","seq":60,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
{"type":"assistant/chunk","seq":61,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":62,"time":0,"data":{"turn":1,"step":6,"content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[57,58,59,60,61],"surfaceOp":"append"}
{"type":"step/end","seq":63,"time":0,"data":{"turn":1,"step":6}}
{"type":"turn/end","seq":64,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
+7 -2
View File
@@ -4,7 +4,11 @@
"docs/development.md",
"docs/i18n/README.md",
"docs/i18n/translation-rules.md",
"docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md"
"docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
"docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
"python/README.md",
"python/sdk-runtime/README.md",
"python/sdk/README.md"
],
"excluded": [
"docs/AGENTS.md",
@@ -13,6 +17,7 @@
"docs/tool-catalog.md",
"docs/persistence-catalog.md",
"docs/cordis-catalog/",
"docs/i18n/terminology.md"
"docs/i18n/terminology.md",
"python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/"
]
}
+7 -34
View File
@@ -26,8 +26,9 @@
* Run: `tsx scripts/verify-doc-refs.ts`.
*/
import { existsSync, globSync, readFileSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { findReferenceViolations, uniqueRepoFiles, type ReferenceViolation as Violation } from './repo-files.ts'
const root = resolve(import.meta.dirname, '..')
@@ -46,42 +47,14 @@ const isExcluded = (p: string): boolean =>
*/
const DOC_REF = /\bdocs\/[A-Za-z0-9._/-]+\.md/g
/** A broken doc reference: a root-relative `docs/….md` token with no file. */
interface Violation {
file: string
/** 1-based line where the reference appears. */
line: number
ref: string
}
/** Find every broken `docs/….md` reference in one TypeScript file. */
function findViolations(absPath: string): Violation[] {
const file = relative(root, absPath)
const source = readFileSync(absPath, 'utf8')
const out: Violation[] = []
const lines = source.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line === undefined) continue
for (const m of line.matchAll(DOC_REF)) {
const ref = m[0]
if (!existsSync(resolve(root, ref))) {
out.push({ file, line: i + 1, ref })
}
}
}
return out
return findReferenceViolations(root, absPath, DOC_REF, ref => ref, ref => !existsSync(resolve(root, ref)))
}
const all: Violation[] = []
let checked = 0
for (const pattern of PATTERNS) {
for (const match of globSync(pattern, { cwd: root })) {
if (isExcluded(match)) continue
checked++
all.push(...findViolations(resolve(root, match)))
}
}
const files = uniqueRepoFiles(root, PATTERNS, isExcluded)
const all = files.flatMap(file => findViolations(file.abs))
const checked = files.length
if (all.length === 0) {
console.log(`verify-doc-refs: ${checked} file(s) checked, all docs/*.md references resolve.`)
+9 -26
View File
@@ -32,12 +32,11 @@
* Run: `tsx scripts/verify-md-links.ts`.
*/
import { existsSync, globSync, readFileSync, realpathSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmFromMarkdown } from 'mdast-util-gfm'
import { gfm } from 'micromark-extension-gfm'
import type { Nodes } from 'mdast'
import { parseMarkdown, visitMarkdown } from './markdown.ts'
import { uniqueRepoFiles } from './repo-files.ts'
const root = resolve(import.meta.dirname, '..')
@@ -103,7 +102,7 @@ function findViolations(absPath: string): Violation[] {
const file = relative(root, absPath)
const dir = dirname(absPath)
const source = readFileSync(absPath, 'utf8')
const tree = fromMarkdown(source, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] })
const tree = parseMarkdown(source)
const out: Violation[] = []
const check = (url: string, node: Nodes): void => {
@@ -117,33 +116,17 @@ function findViolations(absPath: string): Violation[] {
}
}
const visit = (node: Nodes): void => {
visitMarkdown(tree, (node: Nodes): void => {
if ((node.type === 'link' || node.type === 'image' || node.type === 'definition') && 'url' in node) {
check(node.url, node)
}
if ('children' in node) {
for (const child of node.children) visit(child)
}
}
visit(tree)
})
return out
}
const seen = new Set<string>()
const all: Violation[] = []
let checked = 0
for (const pattern of PATTERNS) {
for (const match of globSync(pattern, { cwd: root })) {
const abs = resolve(root, match)
// CLAUDE.md symlinks resolve onto AGENTS.md; dedupe by real path so a file
// matched twice (or via symlink) is checked once.
const real = realpathSync(abs)
if (seen.has(real)) continue
seen.add(real)
checked++
all.push(...findViolations(abs))
}
}
const files = uniqueRepoFiles(root, PATTERNS)
const all = files.flatMap(file => findViolations(file.abs))
const checked = files.length
if (all.length === 0) {
console.log(`verify-md-links: ${checked} file(s) checked, all relative cross-links resolve.`)
+10 -27
View File
@@ -29,12 +29,11 @@
* Run: `tsx scripts/verify-md-wrap.ts`.
*/
import { globSync, readFileSync, realpathSync } from 'node:fs'
import { readFileSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { gfmFromMarkdown } from 'mdast-util-gfm'
import { gfm } from 'micromark-extension-gfm'
import type { Nodes } from 'mdast'
import { parseMarkdown, visitMarkdown } from './markdown.ts'
import { uniqueRepoFiles } from './repo-files.ts'
const root = resolve(import.meta.dirname, '..')
@@ -75,10 +74,10 @@ function findViolations(absPath: string): Violation[] {
const file = relative(root, absPath)
const source = readFileSync(absPath, 'utf8')
const parsedSource = maskVitePressStructure(source)
const tree = fromMarkdown(parsedSource, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] })
const tree = parseMarkdown(parsedSource)
const out: Violation[] = []
const visit = (node: Nodes): void => {
visitMarkdown(tree, (node: Nodes): boolean | void => {
if (node.type === 'paragraph' && node.position) {
const { start, end } = node.position
if (end.line > start.line) {
@@ -87,31 +86,15 @@ function findViolations(absPath: string): Violation[] {
}
// A paragraph's children are inline (text/emphasis/…); no nested
// paragraphs to find, so don't descend.
return
return false
}
if ('children' in node) {
for (const child of node.children) visit(child)
}
}
visit(tree)
})
return out
}
const seen = new Set<string>()
const all: Violation[] = []
let checked = 0
for (const pattern of PATTERNS) {
for (const match of globSync(pattern, { cwd: root })) {
const abs = resolve(root, match)
// CLAUDE.md symlinks resolve onto AGENTS.md; dedupe by real path so a file
// matched twice (or via symlink) is checked once.
const real = realpathSync(abs)
if (seen.has(real)) continue
seen.add(real)
checked++
all.push(...findViolations(abs))
}
}
const files = uniqueRepoFiles(root, PATTERNS)
const all = files.flatMap(file => findViolations(file.abs))
const checked = files.length
if (all.length === 0) {
console.log(`verify-md-wrap: ${checked} file(s) checked, no hard-wrapped prose paragraphs.`)
+31 -54
View File
@@ -39,8 +39,9 @@
* Run: `tsx scripts/verify-package-paths.ts`.
*/
import { existsSync, globSync, readdirSync, readFileSync, realpathSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import { existsSync, readdirSync } from 'node:fs'
import { resolve } from 'node:path'
import { findReferenceViolations, uniqueRepoFiles, type ReferenceViolation as Violation } from './repo-files.ts'
const root = resolve(import.meta.dirname, '..')
@@ -89,12 +90,22 @@ const packageNames = realPackageNames()
*/
const PKG_REF = /\bpackages\/[A-Za-z0-9._/-]+/g
/** A broken package reference: a stale root-relative `packages/…` path. */
interface Violation {
file: string
/** 1-based line where the reference appears. */
line: number
ref: string
function isDriftedPackageReference(ref: string): boolean {
if (existsSync(resolve(root, ref))) return false
// A reference INTO a package's built `lib/` is a build OUTPUT, not an
// authored-source location: it does not exist until `pnpm run build` emits
// it, and CI runs this gate BEFORE the build step. Skip it — but ONLY when
// the `packages/<group>/<pkg>` ROOT it sits under is real and on disk, so
// `packages/ui/acp-agent/lib/bin.js` (correct, just not yet built) is
// exempt while a stale `packages/acp-agent/lib/bin.js` (group-less, the
// exact moved-package drift this gate exists to catch) still flags. A bare
// `lib` segment is not a blanket escape hatch.
const parts = ref.split('/')
const libAt = parts.indexOf('lib')
if (libAt === 3 && existsSync(resolve(root, parts.slice(0, 3).join('/')))) return false
// Only a stale path to a REAL (moved) package is a violation; a segment
// matching a live package name is the drift signal.
return ref.split('/').slice(1).some(segment => packageNames.has(segment))
}
/**
@@ -105,54 +116,20 @@ interface Violation {
* skeletons whose segment is not a package.
*/
function findViolations(absPath: string): Violation[] {
const file = relative(root, absPath)
const source = readFileSync(absPath, 'utf8')
const out: Violation[] = []
const lines = source.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line === undefined) continue
for (const m of line.matchAll(PKG_REF)) {
// Trim a trailing path separator or sentence punctuation that the greedy
// class may have swallowed (`packages/core/tools.` / `…/tools/`).
const ref = m[0].replace(/[./]+$/, '')
if (existsSync(resolve(root, ref))) continue
// A reference INTO a package's built `lib/` is a build OUTPUT, not an
// authored-source location: it does not exist until `pnpm run build` emits
// it, and CI runs this gate BEFORE the build step. Skip it — but ONLY when
// the `packages/<group>/<pkg>` ROOT it sits under is real and on disk, so
// `packages/ui/acp-agent/lib/bin.js` (correct, just not yet built) is
// exempt while a stale `packages/acp-agent/lib/bin.js` (group-less, the
// exact moved-package drift this gate exists to catch) still flags. A bare
// `lib` segment is not a blanket escape hatch.
const parts = ref.split('/')
const libAt = parts.indexOf('lib')
if (libAt === 3 && existsSync(resolve(root, parts.slice(0, 3).join('/')))) continue
// Only a stale path to a REAL (moved) package is a violation; a segment
// matching a live package name is the drift signal.
const segments = ref.split('/').slice(1)
if (segments.some(seg => packageNames.has(seg))) {
out.push({ file, line: i + 1, ref })
}
}
}
return out
return findReferenceViolations(
root,
absPath,
PKG_REF,
// Trim a trailing path separator or sentence punctuation that the greedy
// class may have swallowed (`packages/core/tools.` / `…/tools/`).
ref => ref.replace(/[./]+$/, ''),
isDriftedPackageReference,
)
}
const all: Violation[] = []
let checked = 0
const seen = new Set<string>()
for (const pattern of PATTERNS) {
for (const match of globSync(pattern, { cwd: root })) {
if (isExcluded(match)) continue
// Dedup by real path: the root/packages CLAUDE.md are symlinks to AGENTS.md.
const real = realpathSync(resolve(root, match))
if (seen.has(real)) continue
seen.add(real)
checked++
all.push(...findViolations(real))
}
}
const files = uniqueRepoFiles(root, PATTERNS, isExcluded)
const all = files.flatMap(file => findViolations(file.real))
const checked = files.length
if (all.length === 0) {
console.log(`verify-package-paths: ${checked} file(s) checked, all packages/* references resolve.`)
+116
View File
@@ -0,0 +1,116 @@
/**
* Verify that the Python single-exe deploy manifest explicitly supplies every
* required workspace peer of every workspace package in its dependency graph.
*
* `pnpm deploy --config.auto-install-peers=false` cannot repair an incomplete
* runtime root. Keeping the peer at the root also prevents a successful build
* from producing an executable that fails only when Cordis loads the plugin.
*/
import { readFile, readdir } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
interface PackageManifest {
name?: string
dependencies?: Record<string, string>
optionalDependencies?: Record<string, string>
peerDependencies?: Record<string, string>
peerDependenciesMeta?: Record<string, { optional?: boolean }>
}
interface WorkspacePackage {
path: string
manifest: PackageManifest
}
const root = resolve(import.meta.dirname, '..')
const { values } = parseArgs({
args: process.argv.slice(2),
options: { manifest: { type: 'string' } },
})
const runtimeManifestPath = resolve(root, values.manifest ?? 'python/sdk-runtime/package.json')
const runtimeManifest = await loadManifest(runtimeManifestPath)
const runtimeName = runtimeManifest.name ?? 'python/sdk-runtime'
const workspace = await loadWorkspacePackages()
const runtimeDependencies = runtimeManifest.dependencies ?? {}
const parents = new Map<string, string | undefined>()
const queue: string[] = []
for (const dependency of Object.keys(runtimeDependencies).sort()) {
if (!workspace.has(dependency)) continue
parents.set(dependency, undefined)
queue.push(dependency)
}
const failures: string[] = []
for (let index = 0; index < queue.length; index += 1) {
const packageName = queue[index]
if (packageName === undefined) continue
const current = workspace.get(packageName)
if (current === undefined) continue
const peers = current.manifest.peerDependencies ?? {}
const peerMeta = current.manifest.peerDependenciesMeta ?? {}
for (const peer of Object.keys(peers).sort()) {
if (!workspace.has(peer) || peerMeta[peer]?.optional === true) continue
if (runtimeDependencies[peer]?.startsWith('workspace:') === true) continue
failures.push(`${formatChain(runtimeName, packageName, parents)} -> ${peer}`)
}
const dependencies = {
...current.manifest.dependencies,
...current.manifest.optionalDependencies,
}
for (const dependency of Object.keys(dependencies).sort()) {
if (!workspace.has(dependency) || parents.has(dependency)) continue
parents.set(dependency, packageName)
queue.push(dependency)
}
}
if (failures.length > 0) {
console.error('verify-runtime-closure: required workspace peers are missing from python/sdk-runtime dependencies:')
for (const failure of failures) console.error(` ${failure}`)
process.exit(1)
}
console.log(`verify-runtime-closure: ${queue.length} workspace packages form a closed runtime dependency graph.`)
async function loadWorkspacePackages(): Promise<Map<string, WorkspacePackage>> {
const paths: string[] = []
for (const group of await childDirectories(join(root, 'packages'))) {
for (const packageDir of await childDirectories(join(root, 'packages', group))) {
paths.push(join(root, 'packages', group, packageDir, 'package.json'))
}
}
for (const packageDir of await childDirectories(join(root, 'vendor'))) {
paths.push(join(root, 'vendor', packageDir, 'package.json'))
}
const result = new Map<string, WorkspacePackage>()
for (const path of paths) {
const manifest = await loadManifest(path)
if (manifest.name !== undefined) result.set(manifest.name, { path, manifest })
}
return result
}
async function childDirectories(path: string): Promise<string[]> {
const entries = await readdir(path, { withFileTypes: true })
return entries.filter(entry => entry.isDirectory()).map(entry => entry.name).sort()
}
async function loadManifest(path: string): Promise<PackageManifest> {
return JSON.parse(await readFile(path, 'utf8')) as PackageManifest
}
function formatChain(
runtimeName: string,
packageName: string,
parents: ReadonlyMap<string, string | undefined>,
): string {
const chain = [packageName]
let parent = parents.get(packageName)
while (parent !== undefined) {
chain.unshift(parent)
parent = parents.get(parent)
}
return [runtimeName, ...chain].join(' -> ')
}
+2 -2
View File
@@ -55,8 +55,8 @@ const root = resolve(import.meta.dirname, '..')
const listMode = process.argv.includes('--list')
const writeMode = process.argv.includes('--write')
/** Scope of the bilingual contract: the root README and the docs tree. */
const SCOPE_PATTERNS = ['README.md', 'README.zh.md', 'README.i18n.yaml', 'docs/**/*.md', 'docs/**/*.i18n.yaml']
/** Scope of the bilingual contract: the root README, the docs tree, and the Python SDK tree. */
const SCOPE_PATTERNS = ['README.md', 'README.zh.md', 'README.i18n.yaml', 'docs/**/*.md', 'docs/**/*.i18n.yaml', 'python/**/*.md', 'python/**/*.i18n.yaml']
/** The enforcement frontier and the never-paired set (docs/i18n/README.md § Scope). */
interface Manifest {