merge: bring master into feat/pwsh-persistent-pty
This commit is contained in:
commit
bc319d0a3b
2446 files changed
+36532
-9922
No files matched your search
@@ -19,11 +19,32 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SDK_DISTRIBUTION = "deepseek-harness-sdk"
|
||||
RUNTIME_DISTRIBUTION = "deepseek-harness-runtime-bin"
|
||||
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"),
|
||||
}
|
||||
PLATFORM_MANIFEST = ROOT / "python" / "sdk-runtime" / "platforms.json"
|
||||
|
||||
|
||||
def load_platforms(path: Path = PLATFORM_MANIFEST) -> dict[str, tuple[str, str]]:
|
||||
"""Load the release platform tag and executable pairs from the build manifest."""
|
||||
try:
|
||||
payload = json.loads(path.read_text())
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"could not read runtime platform manifest from {path}") from error
|
||||
if not isinstance(payload, dict) or not payload:
|
||||
raise ValueError(f"{path} must contain a non-empty platform object")
|
||||
platforms: dict[str, tuple[str, str]] = {}
|
||||
for name, raw in payload.items():
|
||||
if (
|
||||
not isinstance(name, str)
|
||||
or not isinstance(raw, dict)
|
||||
or set(raw) != {"tag", "executable"}
|
||||
or not isinstance(raw["tag"], str)
|
||||
or not isinstance(raw["executable"], str)
|
||||
):
|
||||
raise ValueError(f"{path} platform entries must contain string tag and executable fields")
|
||||
platforms[name] = (raw["tag"], raw["executable"])
|
||||
return platforms
|
||||
|
||||
|
||||
PLATFORMS = load_platforms()
|
||||
|
||||
|
||||
def runtime_suffixes(executable_name: str) -> tuple[str, ...]:
|
||||
@@ -35,7 +56,7 @@ def main() -> None:
|
||||
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",
|
||||
help="optional python-v<repository-version> release tag; it must match package.json",
|
||||
)
|
||||
parser.add_argument("--output-dir", type=Path, required=True)
|
||||
parser.add_argument("--platform", choices=tuple(PLATFORMS))
|
||||
@@ -146,8 +167,29 @@ def rewrite_version(pyproject: Path, version: str) -> None:
|
||||
pyproject.write_text(text)
|
||||
|
||||
|
||||
def stage_license_files(destination: Path, *, include_notices: bool) -> None:
|
||||
"""Copy legal files and declare them as wheel license payloads."""
|
||||
shutil.copy2(ROOT / "LICENSE", destination / "LICENSE")
|
||||
license_files = '["LICENSE"]'
|
||||
if include_notices:
|
||||
shutil.copy2(ROOT / "THIRD_PARTY_NOTICES.md", destination / "THIRD_PARTY_NOTICES.md")
|
||||
license_files = '["LICENSE", "THIRD_PARTY_NOTICES.md"]'
|
||||
pyproject = destination / "pyproject.toml"
|
||||
text, count = re.subn(
|
||||
r'^(license = "[^"]+")$',
|
||||
rf"\1\nlicense-files = {license_files}",
|
||||
pyproject.read_text(),
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
if count != 1:
|
||||
raise RuntimeError(f"could not declare license files in {pyproject}")
|
||||
pyproject.write_text(text)
|
||||
|
||||
|
||||
def stage_sdk(destination: Path, version: str) -> None:
|
||||
copy_package(ROOT / "python" / "sdk", destination)
|
||||
stage_license_files(destination, include_notices=False)
|
||||
pyproject = destination / "pyproject.toml"
|
||||
rewrite_version(pyproject, version)
|
||||
text, count = re.subn(
|
||||
@@ -163,6 +205,7 @@ def stage_sdk(destination: Path, version: str) -> None:
|
||||
|
||||
def stage_runtime(destination: Path, version: str, executable: Path, executable_name: str) -> None:
|
||||
copy_package(ROOT / "python" / "sdk-runtime", destination)
|
||||
stage_license_files(destination, include_notices=True)
|
||||
rewrite_version(destination / "pyproject.toml", version)
|
||||
runtime_dir = destination / "src" / "deepseek_harness_runtime" / "runtime"
|
||||
runtime_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -191,6 +234,16 @@ def verify_wheel(
|
||||
raise RuntimeError(
|
||||
f"{wheel} has distribution name {metadata.get('Name')}, expected {expected_distribution}"
|
||||
)
|
||||
if metadata.get("License-Expression") != "BSD-3-Clause":
|
||||
raise RuntimeError(
|
||||
f"{wheel} has license expression {metadata.get('License-Expression')}, expected BSD-3-Clause"
|
||||
)
|
||||
expected_license_files = ["LICENSE"] if package == "sdk" else ["LICENSE", "THIRD_PARTY_NOTICES.md"]
|
||||
license_files = [Path(name).name for name in metadata.get_all("License-File") or []]
|
||||
if license_files != expected_license_files:
|
||||
raise RuntimeError(
|
||||
f"{wheel} has license files {license_files}, expected {expected_license_files}"
|
||||
)
|
||||
runtime_files = [
|
||||
name for name in archive.namelist() if "/runtime/dsh-jsonrpc-agent-pkg-" in name
|
||||
]
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reject runtime executables that require newer macOS than their wheel tag."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import runpy
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RELEASE = runpy.run_path(str(ROOT / "scripts" / "build-python-release.py"))
|
||||
MACOS_PLATFORM_TAG = RELEASE["PLATFORMS"]["macos-arm64"][0]
|
||||
|
||||
|
||||
def parse_version(value: str) -> tuple[int, ...]:
|
||||
"""Parse a dot-separated numeric deployment version."""
|
||||
if re.fullmatch(r"\d+(?:\.\d+)*", value) is None:
|
||||
raise ValueError(f"invalid macOS deployment version: {value!r}")
|
||||
return tuple(int(part) for part in value.split("."))
|
||||
|
||||
|
||||
def claimed_version(platform_tag: str) -> tuple[int, ...]:
|
||||
"""Return the minimum macOS version encoded by a wheel platform tag."""
|
||||
match = re.fullmatch(r"macosx_(\d+)_(\d+)_arm64", platform_tag)
|
||||
if match is None:
|
||||
raise ValueError(f"unsupported macOS wheel platform tag: {platform_tag!r}")
|
||||
return int(match.group(1)), int(match.group(2))
|
||||
|
||||
|
||||
def parse_otool_deployment_target(output: str) -> tuple[int, ...]:
|
||||
"""Return the newest deployment target from one or more Mach-O slices."""
|
||||
versions = [
|
||||
parse_version(match.group(1))
|
||||
for match in re.finditer(r"^\s*minos\s+(\d+(?:\.\d+)*)\s*$", output, re.MULTILINE)
|
||||
]
|
||||
if not versions:
|
||||
raise ValueError("otool output contains no LC_BUILD_VERSION deployment target")
|
||||
return max(versions)
|
||||
|
||||
|
||||
def deployment_target(executable: Path) -> tuple[int, ...]:
|
||||
"""Read one Mach-O executable's deployment target with ``otool``."""
|
||||
if not executable.is_file():
|
||||
raise FileNotFoundError(f"runtime executable does not exist: {executable}")
|
||||
result = subprocess.run(
|
||||
["otool", "-l", str(executable)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
try:
|
||||
return parse_otool_deployment_target(result.stdout)
|
||||
except ValueError as error:
|
||||
raise ValueError(f"{executable}: {error}") from error
|
||||
|
||||
|
||||
def ensure_compatible(
|
||||
executable: Path, actual: tuple[int, ...], platform_tag: str
|
||||
) -> None:
|
||||
"""Reject an executable whose deployment target exceeds its wheel claim."""
|
||||
claimed = claimed_version(platform_tag)
|
||||
width = max(len(actual), len(claimed))
|
||||
padded_actual = actual + (0,) * (width - len(actual))
|
||||
padded_claimed = claimed + (0,) * (width - len(claimed))
|
||||
if padded_actual > padded_claimed:
|
||||
rendered = ".".join(str(part) for part in actual)
|
||||
raise RuntimeError(
|
||||
f"{executable} requires macOS {rendered} but the wheel claims {platform_tag}"
|
||||
)
|
||||
|
||||
|
||||
def validate_deployment_targets(
|
||||
executables: list[Path], platform_tag: str = MACOS_PLATFORM_TAG
|
||||
) -> list[tuple[Path, tuple[int, ...]]]:
|
||||
"""Validate every executable and return its measured deployment target."""
|
||||
measured = [(executable, deployment_target(executable)) for executable in executables]
|
||||
for executable, actual in measured:
|
||||
ensure_compatible(executable, actual, platform_tag)
|
||||
return measured
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("executables", type=Path, nargs="+")
|
||||
args = parser.parse_args()
|
||||
for executable, version in validate_deployment_targets(args.executables):
|
||||
rendered = ".".join(str(part) for part in version)
|
||||
print(f"{executable}: macOS {rendered} <= {MACOS_PLATFORM_TAG}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -53,7 +53,9 @@ const releaseMemberDirectory = /^(?:packages\/[^/]+\/[^/]+|apps\/[^/]+|vendor\/[
|
||||
const localArtifactDirs = new Set(['node_modules'])
|
||||
const appPackageFiles: Readonly<Record<string, readonly string[]>> = {
|
||||
'@deepseek-ai/dsh': ['lib/*.js', 'config'],
|
||||
'@deepseek-ai/dsh-frontend': ['dist'],
|
||||
// The Web build emits sourcemaps for browser debugging; publishing them is
|
||||
// what the payload policy forbids, so the bundle ships without them.
|
||||
'@deepseek-ai/dsh-frontend': ['dist', '!dist/**/*.map'],
|
||||
}
|
||||
|
||||
/** The subset of package.json fields this constraint check cares about. */
|
||||
@@ -151,7 +153,6 @@ function sameStringList(actual: readonly string[] | undefined, expected: readonl
|
||||
|
||||
function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
|
||||
const extras = manifest.name ? packageFileExtras[manifest.name] ?? [] : []
|
||||
const typeRTRemoteNavigation = hasTypeRTRemoteNavigation(manifest)
|
||||
return [
|
||||
'lib/index.js',
|
||||
// Every package publishes its invariant ownership companion as a separate
|
||||
@@ -184,13 +185,8 @@ function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
|
||||
...hasExportPair(manifest, './client/typert', './lib/typert.client.d.ts', './lib/typert.client.js')
|
||||
? ['lib/typert.client.js', 'lib/typert.client.d.ts']
|
||||
: [],
|
||||
...typeRTRemoteNavigation
|
||||
? [
|
||||
'lib/typert.remote-client.js',
|
||||
'lib/typert.remote-client.d.ts',
|
||||
'lib/typert.remote-client.d.ts.map',
|
||||
'src',
|
||||
]
|
||||
...hasTypeRTRemoteNavigation(manifest)
|
||||
? ['lib/typert.remote-client.js', 'lib/typert.remote-client.d.ts']
|
||||
: [],
|
||||
]
|
||||
}
|
||||
@@ -269,9 +265,8 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
|
||||
|
||||
if (manifest.name?.startsWith('@deepseek-ai/')) {
|
||||
const allowedSources = publicationSourceAllowlist[manifest.name] ?? []
|
||||
const publicationPolicy = { typeRTRemoteNavigation: hasTypeRTRemoteNavigation(manifest) }
|
||||
for (const file of manifest.files ?? []) {
|
||||
if (isForbiddenPublicationFile(file, publicationPolicy) && !allowedSources.includes(file)) {
|
||||
if (isForbiddenPublicationFile(file) && !allowedSources.includes(file)) {
|
||||
errors.push(`${label}: package.json files must not publish ${JSON.stringify(file)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,73 @@ describe('CI workflow', () => {
|
||||
expect(aggregate.needs).not.toContain('serial-windows')
|
||||
})
|
||||
|
||||
it('exempts push from cancellation, so one master merge does not cancel the running drill', () => {
|
||||
const workflow = loadWorkflow('.github/workflows/ci.yml')
|
||||
if (!isRecord(workflow.jobs) || !isRecord(workflow.concurrency)) {
|
||||
throw new TypeError('CI workflow must define jobs and a workflow-level concurrency block')
|
||||
}
|
||||
|
||||
// Cancellation applies to the whole superseded RUN, so this has to be
|
||||
// decided at workflow level and gated on the event: a job-level group
|
||||
// cannot exempt its job from its run being cancelled. Only push is exempt —
|
||||
// a drill takes longer than the interval between master merges. The negated
|
||||
// form is load-bearing: `== 'pull_request'` would also stop cancelling
|
||||
// workflow_dispatch, and a re-dispatched runner benchmark holds up to 12
|
||||
// larger runners for 15 minutes in this same group on master. The
|
||||
// expression is evaluated against the NEWLY TRIGGERED run, so a dispatch on
|
||||
// master still cancels a mid-flight drill; the runbook records that bound.
|
||||
expect(workflow.concurrency['cancel-in-progress']).toBe("${{ github.event_name != 'push' }}")
|
||||
|
||||
// Neither drill may carry a job-level group: it would not exempt the job
|
||||
// from run-scoped cancellation.
|
||||
for (const name of ['serial-linux-selfhosted', 'serial-windows']) {
|
||||
const job = workflow.jobs[name]
|
||||
if (!isRecord(job)) throw new TypeError(`${name} must be defined`)
|
||||
expect(job.concurrency).toBeUndefined()
|
||||
// Both stay master-push-only; that is what makes the push carve-out safe.
|
||||
expect(job.if).toBe("github.event_name == 'push' && github.ref == 'refs/heads/master'")
|
||||
}
|
||||
|
||||
// What bounds the cost of exempting push: a master push may only carry the
|
||||
// cache seeder and the two drills. Any job reachable on push would start
|
||||
// accumulating uncancelled runs, so the set is pinned here.
|
||||
//
|
||||
// Classification is an exact allowlist of the conditions in use, not a
|
||||
// substring match: `github.event_name != 'pull_request'` mentions
|
||||
// `pull_request` yet IS push-reachable, so matching on the event name alone
|
||||
// would silently misclassify it as gated.
|
||||
const NOT_PUSH_REACHABLE = new Set([
|
||||
"github.event_name == 'pull_request'",
|
||||
"always() && github.event_name == 'pull_request'",
|
||||
"github.event_name == 'workflow_dispatch' && inputs.suite == 'larger-runner-benchmark'",
|
||||
"github.event_name == 'workflow_dispatch' && inputs.suite == 'consolidated-runner-benchmark'",
|
||||
])
|
||||
const pushReachable = Object.entries(workflow.jobs)
|
||||
.filter(([, job]) => {
|
||||
if (!isRecord(job)) return false
|
||||
if (job.if === undefined) return true // unconditional: runs on every event
|
||||
if (job.if === false) return false // `if: false` parses as a boolean
|
||||
if (typeof job.if !== 'string') return true // unrecognized shape: surface it
|
||||
return !NOT_PUSH_REACHABLE.has(job.if.trim())
|
||||
})
|
||||
.map(([name]) => name)
|
||||
.sort()
|
||||
expect(pushReachable).toEqual(['serial-linux-selfhosted', 'serial-windows', 'wine-apt-cache'])
|
||||
|
||||
// Why workflow_dispatch must keep cancelling: each benchmark fans out to a
|
||||
// dozen larger runners at once, in this same group on master. If it stopped
|
||||
// cancelling, a re-dispatch would queue ahead of a drill instead of
|
||||
// replacing the stale measurement.
|
||||
for (const name of ['larger-runner-benchmark', 'consolidated-runner-benchmark']) {
|
||||
const job = workflow.jobs[name]
|
||||
if (!isRecord(job) || !isRecord(job.strategy)) {
|
||||
throw new TypeError(`${name} must define a matrix strategy`)
|
||||
}
|
||||
expect(job.strategy['max-parallel']).toBe(12)
|
||||
expect(job['timeout-minutes']).toBe(15)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps supported LSP source under native Windows coverage', () => {
|
||||
const config = readFileSync(resolve(root, 'vitest.config.ts'), 'utf8')
|
||||
|
||||
@@ -92,6 +159,26 @@ describe('CI workflow', () => {
|
||||
expect(config).not.toContain('packages/lsp/lsp-local/src/instance.ts')
|
||||
})
|
||||
|
||||
it('requires one release-shaped Python runtime target on every pull request', () => {
|
||||
const workflow = loadWorkflow('.github/workflows/ci.yml')
|
||||
const pythonRuntime = workflowJob(workflow, 'python-runtime')
|
||||
const aggregate = workflowJob(workflow, 'all-checks-passed')
|
||||
if (!Array.isArray(aggregate.needs)) {
|
||||
throw new TypeError('CI aggregate must define required job dependencies')
|
||||
}
|
||||
|
||||
expect(pythonRuntime).toMatchObject({
|
||||
if: "github.event_name == 'pull_request'",
|
||||
name: 'python runtime / release-shaped Linux x64',
|
||||
uses: './.github/workflows/build-exe-for-python-sdk.yml',
|
||||
with: {
|
||||
targets: 'node24-linux-x64',
|
||||
ci: true,
|
||||
},
|
||||
})
|
||||
expect(aggregate.needs).toContain('python-runtime')
|
||||
})
|
||||
|
||||
it('keeps every Vitest project process-isolated on native Windows', () => {
|
||||
const config = readFileSync(resolve(root, 'vitest.config.ts'), 'utf8')
|
||||
|
||||
@@ -127,6 +214,142 @@ describe('E2B e2e workflow', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Python release workflows', () => {
|
||||
it('keeps complete wheel validation separate from protected public publication', () => {
|
||||
const workflow = loadWorkflow('.github/workflows/python-release.yml')
|
||||
const dispatch = workflowEvent(workflow, 'workflow_dispatch')
|
||||
const pullRequest = workflowEvent(workflow, 'pull_request')
|
||||
const build = workflowJob(workflow, 'build')
|
||||
const pythonCompat = workflowJob(workflow, 'python-compat')
|
||||
const validate = workflowJob(workflow, 'validate')
|
||||
const publishRuntime = workflowJob(workflow, 'publish-runtime')
|
||||
const publishSdk = workflowJob(workflow, 'publish-sdk')
|
||||
if (!isRecord(dispatch.inputs)
|
||||
|| !isRecord(dispatch.inputs.publish)
|
||||
|| !Array.isArray(pythonCompat.steps)
|
||||
|| !Array.isArray(validate.steps)
|
||||
|| !Array.isArray(publishRuntime.steps)
|
||||
|| !Array.isArray(publishSdk.steps)) {
|
||||
throw new TypeError('Python release workflow must define publish input and release steps')
|
||||
}
|
||||
|
||||
expect(dispatch.inputs.publish).toMatchObject({ type: 'boolean', default: false })
|
||||
expect(pullRequest).toEqual({ types: ['labeled'] })
|
||||
expect(build).toMatchObject({
|
||||
if: "github.event_name == 'workflow_dispatch' || github.event.label.name == 'python-release-dry-run'",
|
||||
uses: './.github/workflows/build-exe-for-python-sdk.yml',
|
||||
with: {
|
||||
targets: 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64',
|
||||
release: true,
|
||||
},
|
||||
})
|
||||
expect(pythonCompat.strategy).toMatchObject({ matrix: { python: ['3.10', '3.14'] } })
|
||||
expect(JSON.stringify(pythonCompat.steps)).toContain('deepseek-harness-sdk==${{ steps.compatibility-version.outputs.version }}')
|
||||
const validateSteps = JSON.stringify(validate.steps)
|
||||
const authorize = validate.steps.filter(isRecord).find(step => step.name === 'Authorize publication request')
|
||||
if (!isRecord(authorize) || typeof authorize.run !== 'string') {
|
||||
throw new TypeError('Python release validation must authorize publication requests')
|
||||
}
|
||||
expect(validateSteps).toContain('PUBLIC_PYPI_RELEASE_ENABLED')
|
||||
expect(authorize).toMatchObject({
|
||||
env: {
|
||||
PYPI_PUBLISHER_REPOSITORY: '${{ vars.PYPI_PUBLISHER_REPOSITORY }}',
|
||||
REPOSITORY: '${{ github.repository }}',
|
||||
},
|
||||
})
|
||||
expect(authorize.run).toContain('[ "$REPOSITORY" = "$PYPI_PUBLISHER_REPOSITORY" ]')
|
||||
expect(validateSteps).toContain('100000000')
|
||||
expect(publishRuntime).toMatchObject({
|
||||
if: "github.event_name == 'workflow_dispatch' && inputs.publish",
|
||||
needs: 'validate',
|
||||
environment: 'pypi-runtime',
|
||||
permissions: { contents: 'read', 'id-token': 'write' },
|
||||
})
|
||||
expect(publishSdk).toMatchObject({
|
||||
if: "github.event_name == 'workflow_dispatch' && inputs.publish",
|
||||
needs: ['validate', 'publish-runtime'],
|
||||
environment: 'pypi',
|
||||
permissions: { contents: 'read', 'id-token': 'write' },
|
||||
})
|
||||
const runtimeSteps = publishRuntime.steps.filter(isRecord)
|
||||
const sdkSteps = publishSdk.steps.filter(isRecord)
|
||||
const runtimePublish = runtimeSteps.find(step => step.name === 'Publish runtime wheels')
|
||||
const sdkPublish = sdkSteps.find(step => step.name === 'Publish SDK wheel')
|
||||
const runtimeHashes = runtimeSteps.find(step => step.name === 'Verify release artifact hashes')
|
||||
const sdkHashes = sdkSteps.find(step => step.name === 'Verify release artifact hashes')
|
||||
expect([...runtimeSteps, ...sdkSteps].some(
|
||||
step => typeof step.uses === 'string' && step.uses.startsWith('actions/checkout@'),
|
||||
)).toBe(false)
|
||||
expect([...runtimeSteps, ...sdkSteps].filter(
|
||||
step => step.uses === 'pypa/gh-action-pypi-publish@release/v1',
|
||||
)).toHaveLength(2)
|
||||
expect(runtimePublish).toMatchObject({
|
||||
with: { 'packages-dir': 'dist/runtime/', attestations: false },
|
||||
})
|
||||
expect(sdkPublish).toMatchObject({
|
||||
with: { 'packages-dir': 'dist/sdk/', attestations: false },
|
||||
})
|
||||
expect(runtimeHashes).toMatchObject({ run: 'cd dist && sha256sum -c SHA256SUMS' })
|
||||
expect(sdkHashes).toMatchObject({ run: 'cd dist && sha256sum -c SHA256SUMS' })
|
||||
})
|
||||
|
||||
it('exposes the native wheel builder to the release caller with normalized versions', () => {
|
||||
const workflow = loadWorkflow('.github/workflows/build-exe-for-python-sdk.yml')
|
||||
const call = workflowEvent(workflow, 'workflow_call')
|
||||
const plan = workflowJob(workflow, 'plan')
|
||||
const build = workflowJob(workflow, 'build')
|
||||
if (!isRecord(call.inputs) || !Array.isArray(plan.steps) || !Array.isArray(build.steps)) {
|
||||
throw new TypeError('Python wheel builder must define workflow_call inputs and plan steps')
|
||||
}
|
||||
|
||||
const buildSteps: unknown[] = build.steps
|
||||
const manylinuxAddon = buildSteps.find(step => isRecord(step) && step.name === 'Rebuild Linux node-pty against manylinux 2.28')
|
||||
const macosCheck = buildSteps.find(step => isRecord(step) && step.name === 'Check macOS deployment target')
|
||||
const manylinuxSmoke = buildSteps.find(step => isRecord(step) && step.name === 'Run wheel in a manylinux 2.28 container')
|
||||
expect(call.inputs).toHaveProperty('targets')
|
||||
expect(call.inputs).toMatchObject({
|
||||
ci: { type: 'boolean', default: false },
|
||||
release: { type: 'boolean', default: false },
|
||||
})
|
||||
expect(workflow.concurrency).toMatchObject({
|
||||
group: 'build-single-exe-${{ github.workflow }}-${{ github.ref }}',
|
||||
})
|
||||
expect(plan.if).toContain('inputs.ci')
|
||||
expect(plan.if).toContain('inputs.release')
|
||||
expect(JSON.stringify(plan.steps)).toContain('pep440_version')
|
||||
expect(JSON.stringify(workflow)).toContain('macosx_14_0_arm64')
|
||||
expect(manylinuxAddon).toMatchObject({ if: "runner.os == 'Linux'" })
|
||||
expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_x86_64')
|
||||
expect(JSON.stringify(manylinuxAddon)).toContain('manylinux_2_28_aarch64')
|
||||
expect(JSON.stringify(manylinuxAddon)).toContain('$HOME/setup-pnpm:$HOME/setup-pnpm:ro')
|
||||
expect(JSON.stringify(manylinuxAddon)).toContain('node-pty-glibc-versions.txt')
|
||||
expect(JSON.stringify(manylinuxAddon)).toContain('le 2.28')
|
||||
expect(macosCheck).toMatchObject({ if: "runner.os == 'macOS'" })
|
||||
expect(JSON.stringify(macosCheck)).toContain('scripts/check-macos-deployment-target.py')
|
||||
expect(JSON.stringify(macosCheck)).toContain('$EXE-spawn-helper')
|
||||
expect(manylinuxSmoke).toMatchObject({ if: "runner.os == 'Linux'" })
|
||||
expect(JSON.stringify(manylinuxSmoke)).toContain('-e DSH_TELEMETRY_DISABLED')
|
||||
})
|
||||
|
||||
it('uses the shared macOS deployment-target check in GitLab', () => {
|
||||
const workflow = loadWorkflow('.gitlab-ci.yml')
|
||||
const runtimeWheel = workflow['.runtime-wheel']
|
||||
if (!isRecord(runtimeWheel) || !Array.isArray(runtimeWheel.script)) {
|
||||
throw new TypeError('GitLab CI must define the runtime wheel script')
|
||||
}
|
||||
const runtimeScript: unknown[] = runtimeWheel.script
|
||||
const macosCheck = runtimeScript.find(
|
||||
step => typeof step === 'string' && step.includes('PLATFORM" = macos-arm64'),
|
||||
)
|
||||
if (typeof macosCheck !== 'string') {
|
||||
throw new TypeError('GitLab CI must check the macOS deployment target')
|
||||
}
|
||||
|
||||
expect(macosCheck).toContain('scripts/check-macos-deployment-target.py')
|
||||
expect(macosCheck).toContain('"$EXE" "$EXE-spawn-helper"')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Issue lifecycle workflow', () => {
|
||||
it('uses explicit review handoff events without rerunning when a draft becomes ready', () => {
|
||||
const lifecycle = loadWorkflow('.github/workflows/issue-lifecycle.yml')
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"docs/testing.md": 1150,
|
||||
"examples/AGENTS.md": 310,
|
||||
"packages/AGENTS.md": 675,
|
||||
"packages/README.md": 980
|
||||
"packages/README.md": 994
|
||||
}
|
||||
@@ -133,6 +133,7 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
|
||||
models: 'client-side interface-typed browser service — packages/client/ui-model/README.md owns the API',
|
||||
modules: 'client-side interface-typed browser service — packages/client/modules/README.md owns the API',
|
||||
remote: 'client-side interface-typed gateway accessor (ClientRemote) — packages/api/gateway/README.md owns the API',
|
||||
sessionExport: 'client-side browser download controller — packages/session-query/session-export/README.md owns the API',
|
||||
slash: 'client-side interface-typed browser service — packages/client/ui-slash/README.md owns the API',
|
||||
slots: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
|
||||
theme: 'client-side interface-typed browser service — packages/client/ui-theme/README.md owns the API',
|
||||
@@ -178,6 +179,7 @@ export const EVENT_SCOPE_PAGE: Record<string, string> = {
|
||||
* exemption cannot mask another declaration in that scope.
|
||||
*/
|
||||
export const EVENT_WALK_EXEMPTIONS: Record<string, string> = {
|
||||
'command/executed': 'client-face local command acknowledgment — packages/client/ui-command/README.md owns the API',
|
||||
'connection/reset': 'client-face transport signal — packages/client/runtime/README.md owns the API',
|
||||
'locale/change': 'client-face locale switch signal — packages/client/locale/README.md owns the API',
|
||||
'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
|
||||
|
||||
@@ -1195,7 +1195,7 @@ function renderLifecycle(): string {
|
||||
const maintenance = 'curated Mermaid sequence; exact event signatures live in the generated Cordis catalog'
|
||||
return [
|
||||
...generatedHeader('Agent Turn And Step Lifecycle'),
|
||||
'This sequence is the visual companion to [architecture.md](architecture.md#default-loop-lifecycle). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.',
|
||||
'This sequence is the visual companion to [architecture.md](architecture.md#turn-flow). It keeps durable replay facts on `session/event` and live control/status on `agent/*`.',
|
||||
'',
|
||||
'```mermaid',
|
||||
'sequenceDiagram',
|
||||
@@ -1213,15 +1213,15 @@ function renderLifecycle(): string {
|
||||
` Agent-->>SDK: ${mermaidCode('agent/inbox/inserted')} { message }`,
|
||||
' Agent->>Driver: queued work wakes driver',
|
||||
` Driver-->>SDK: ${mermaidCode('agent/status')} running`,
|
||||
` Driver->>Session: ${mermaidCode('turn/start')}`,
|
||||
' Note over Agent,Driver: claim pending next-step input plus one queued prompt',
|
||||
` Driver-->>SDK: ${mermaidCode('agent/inbox/spliced')} pure deletion`,
|
||||
` Driver-->>SDK: ${mermaidCode('agent/inbox/claimed')} { message, turn } per message`,
|
||||
` Driver->>Hooks: ${mermaidCode('agent/pre-step')} waterfall`,
|
||||
' Hooks-->>Driver: authoritative reject or enter(messages)',
|
||||
' alt proposed step rejected or pre-step failed',
|
||||
' Driver-->>Driver: claimed batch stays removed, no turn opens',
|
||||
' Driver-->>Driver: claimed batch stays removed, the open turn spends no step',
|
||||
' else enter proposed step',
|
||||
` Driver->>Session: ${mermaidCode('turn/start')}`,
|
||||
` Driver->>Session: ${mermaidCode('step/start')}`,
|
||||
` Driver->>Session: ${mermaidCode('user/message')} per entered message`,
|
||||
` Driver->>Prompt: ${mermaidCode('system-prompt/assemble')} waterfall`,
|
||||
@@ -1258,8 +1258,8 @@ function renderLifecycle(): string {
|
||||
' Hooks-->>Driver: authoritative reject or enter(messages)',
|
||||
' end',
|
||||
' end',
|
||||
` Driver->>Session: ${mermaidCode('turn/end')}`,
|
||||
' end',
|
||||
` Driver->>Session: ${mermaidCode('turn/end')}`,
|
||||
` Driver-->>SDK: ${mermaidCode('agent/status')} idle`,
|
||||
'```',
|
||||
'',
|
||||
|
||||
@@ -40,6 +40,7 @@ const LINK_MAP: Record<string, string> = {
|
||||
CallId: 'core.md',
|
||||
ContentBlock: 'core.md',
|
||||
MessageSource: 'core.md',
|
||||
ScheduleChange: 'schedule.md',
|
||||
StreamChunk: 'llm-streaming.md',
|
||||
TokenUsage: 'llm-streaming.md',
|
||||
TodoItem: 'session.md',
|
||||
|
||||
@@ -51,6 +51,7 @@ import * as ToolStrReplaceEditor from '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
import PtyService from '@deepseek-ai/dsh-pty'
|
||||
import * as ToolPty from '@deepseek-ai/dsh-tool-pty'
|
||||
import * as ToolGoal from '@deepseek-ai/dsh-tool-goal'
|
||||
import * as ToolSchedule from '@deepseek-ai/dsh-tool-schedule'
|
||||
import Lsp from '@deepseek-ai/dsh-lsp'
|
||||
import * as ToolLsp from '@deepseek-ai/dsh-tool-lsp'
|
||||
import * as ToolSkill from '@deepseek-ai/dsh-tool-skill'
|
||||
@@ -115,15 +116,18 @@ const catalogChildScopes = new WeakMap<Context, Agent>()
|
||||
* schema harvest, without starting a model, Agent loop, or persistence backend.
|
||||
* @param ctx - catalog context owning the scope.
|
||||
* @param mountScoped - package installer for the scoped context.
|
||||
* @param key - agent-like scope key exposed to the package's scope selector.
|
||||
* @param inject - services the package installer must await before mounting.
|
||||
*/
|
||||
async function mountCatalogChildScope(
|
||||
ctx: Context,
|
||||
mountScoped: (childCtx: Context) => void,
|
||||
key: Agent = { id: SessionId('tool-catalog-child') } as Agent,
|
||||
inject: string[] = ['tools', 'systemPrompt', 'subagents'],
|
||||
): Promise<void> {
|
||||
const key = { id: SessionId('tool-catalog-child') } as Agent
|
||||
await ctx.plugin(Object.assign((inner: Context) => {
|
||||
mountScoped(createScope(inner, key).ctx)
|
||||
}, { inject: ['tools', 'systemPrompt', 'subagents'] }))
|
||||
}, { inject }))
|
||||
catalogChildScopes.set(ctx, key)
|
||||
}
|
||||
|
||||
@@ -362,6 +366,27 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
note:
|
||||
'create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-schedule',
|
||||
dir: 'tool-schedule',
|
||||
source: 'packages/schedule/tool-schedule/src/tools.ts',
|
||||
requires: ['ctx.tools', 'ctx.sessions', 'Session persistence', 'a future live root Agent'],
|
||||
writes: ['tool/call', 'schedule/change create or delete', 'tool/result'],
|
||||
async mount(ctx) {
|
||||
await ctx.plugin(SessionStore)
|
||||
const session = ctx.sessions.create(SessionId('tool-catalog-schedule'))
|
||||
const agent = { id: session.id, session } as Agent
|
||||
await mountCatalogChildScope(ctx, (childCtx) => {
|
||||
ToolSchedule.registerScheduleTools(ctx, childCtx, agent, () => {})
|
||||
}, agent, ['tools', 'systemPrompt'])
|
||||
},
|
||||
scope: ctx => catalogChildScopes.get(ctx) as Agent,
|
||||
note:
|
||||
'Registered only inside live root Agent scopes created after the opt-in Schedule plugin loads. '
|
||||
+ 'Version 1 accepts after_seconds, explicit absolute at, and bounded fixed-rate every_seconds, '
|
||||
+ 'and discloses session-local delivery; '
|
||||
+ 'management reads and mutations require the shared Session persistence barrier.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-lsp',
|
||||
dir: 'tool-lsp',
|
||||
@@ -425,7 +450,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
pkg: '@deepseek-ai/dsh-tool-subagent',
|
||||
dir: 'tool-subagent',
|
||||
source: 'packages/subagent/tool-subagent/src/index.ts',
|
||||
requires: ['ctx.tools', 'ctx.subagents'],
|
||||
requires: ['ctx.tools', 'ctx.subagents', 'ctx.systemPrompt'],
|
||||
writes: ['tool/call', 'tool/result', 'child session events through the chosen provider'],
|
||||
shippedNames: ['subagent', 'subagent_fork'],
|
||||
async mount(ctx) {
|
||||
@@ -434,7 +459,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
await ctx.plugin(ToolSubagent, { provider: 'mock' })
|
||||
},
|
||||
note:
|
||||
'The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped compositions load this package once per subagent backend, so the model additionally sees `subagent_fork` bound to the fork backend. Each instance\'s description and `run_in_background` parameter follow its own `backgroundMode` and `enableRunInBackground`, so the two shipped schemas are not identical: `subagent` is `continuable`, while `subagent_fork` stays `one-shot` — see `packages/bundle/base/cordis.patch.yml` and `examples/acp-agent/cordis.yml`.',
|
||||
'The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped compositions load this package once per subagent backend, so the model additionally sees `subagent_fork` bound to the fork backend. Each instance\'s description, `run_in_background` parameter, and system-prompt policy follow its own `backgroundMode` and `enableRunInBackground`, so the two shipped schemas are not identical: `subagent` is `continuable` and defaults omitted calls to background with automatic settlement delivery, while `subagent_fork` stays `one-shot` and defaults them to foreground — see `packages/bundle/base/cordis.patch.yml` and `examples/acp-agent/cordis.yml`.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-subagent-control',
|
||||
|
||||
@@ -53,7 +53,9 @@ describe('Oxlint executable contract', () => {
|
||||
['host package source', 'packages/fs/fs-policy/src', 'packages/fs/fs-policy/tsconfig.json'],
|
||||
['host package test', 'packages/fs/fs-policy/tests', 'tsconfig.host.json'],
|
||||
['client package source', 'packages/client/ui-primitives/src', 'packages/client/ui-primitives/tsconfig.json'],
|
||||
['client package test', 'packages/client/ui-trajectory/tests', 'tsconfig.client.json'],
|
||||
// A test under packages/client states its face in the filename, so the
|
||||
// probe carries the Client suffix to reach the Client aggregate.
|
||||
['client package test', 'packages/client/ui-trajectory/tests', 'tsconfig.client.json', '.client.ts'],
|
||||
['example', 'examples/headless-agent/tests', 'tsconfig.host.json'],
|
||||
['website', 'website', 'tsconfig.host.json'],
|
||||
] as const
|
||||
@@ -66,8 +68,8 @@ probePromise()
|
||||
|
||||
try {
|
||||
const paths: Array<readonly [label: string, path: string, tsconfig: string]> = []
|
||||
for (const [label, parent, tsconfig] of probes) {
|
||||
const path = join(repositoryRoot, parent, `oxlint-contract-${suffix}.ts`)
|
||||
for (const [label, parent, tsconfig, extension = '.ts'] of probes) {
|
||||
const path = join(repositoryRoot, parent, `oxlint-contract-${suffix}${extension}`)
|
||||
await writeFile(path, source)
|
||||
paths.push([label, relative(repositoryRoot, path), tsconfig])
|
||||
}
|
||||
@@ -98,7 +100,8 @@ probePromise()
|
||||
expect(output).not.toContain('Unmatched file:')
|
||||
} finally {
|
||||
await Promise.all([
|
||||
...probes.map(([, parent]) => rm(join(repositoryRoot, parent, `oxlint-contract-${suffix}.ts`), { force: true })),
|
||||
...probes.map(([, parent, , extension = '.ts']) =>
|
||||
rm(join(repositoryRoot, parent, `oxlint-contract-${suffix}${extension}`), { force: true })),
|
||||
rm(configPath, { force: true }),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, realpathSyn
|
||||
import { tmpdir } from 'node:os'
|
||||
import { basename, join, resolve } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { docsPages, type DocsPage } from '../website/docs.ts'
|
||||
import { docsPages, landingLink, routeLink, sectionSpec, type DocsPage } from '../website/docs.ts'
|
||||
import {
|
||||
addProjectionFrontmatter, projectedPageContent, publishableImage, rewriteMarkdown,
|
||||
} from './project-doc-site.ts'
|
||||
@@ -307,7 +307,7 @@ describe('docsPages locale routes', () => {
|
||||
const translated = rootPages.filter(page => page.contentLocale === 'zh-CN')
|
||||
const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US')
|
||||
|
||||
expect(translated).toHaveLength(42)
|
||||
expect(translated).toHaveLength(43)
|
||||
expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true)
|
||||
expect(fallbacks).toEqual([])
|
||||
})
|
||||
@@ -364,6 +364,63 @@ describe('docsPages locale routes', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('sidebar ordering', () => {
|
||||
it('places every section a sidebar collection owns', () => {
|
||||
for (const page of docsPages) {
|
||||
if (page.sidebar === null) continue
|
||||
expect(() => sectionSpec(page.locale, page.section), page.route).not.toThrow()
|
||||
}
|
||||
})
|
||||
|
||||
it('refuses a section with no declared placement', () => {
|
||||
expect(() => sectionSpec('root', '数据结构'))
|
||||
.toThrow('Sidebar section "数据结构" has no placement in the root locale.')
|
||||
})
|
||||
|
||||
it('declares placements per locale rather than in one shared list', () => {
|
||||
// `SDK` labels a group in both locales, so one shared list would have to
|
||||
// rank it against `入门` and against `Guide` at the same position.
|
||||
expect(sectionSpec('root', 'SDK').index).toBeGreaterThan(sectionSpec('root', '入门').index)
|
||||
expect(sectionSpec('en', 'SDK').index).toBeGreaterThan(sectionSpec('en', 'Guide').index)
|
||||
expect(() => sectionSpec('en', '入门')).toThrow()
|
||||
expect(() => sectionSpec('root', 'Guide')).toThrow()
|
||||
})
|
||||
|
||||
it('lands every navigation item on a page the manifest publishes', () => {
|
||||
// The navigation bar named `/guide/` while the manifest published the guide's
|
||||
// first page at `guide/quickstart.md`, so the item served a 404.
|
||||
const collections = [
|
||||
['root', 'zh-guide'], ['root', 'zh-develop'], ['root', 'zh-reference'],
|
||||
['en', 'en-guide'], ['en', 'en-develop'], ['en', 'en-reference'],
|
||||
] as const
|
||||
const published = new Set(docsPages.map(page => routeLink(page.route)))
|
||||
for (const [locale, collection] of collections) {
|
||||
expect(published, `${locale}/${collection}`).toContain(landingLink(locale, collection))
|
||||
}
|
||||
})
|
||||
|
||||
it('collapses the subsystem groups and leaves the smaller ones open', () => {
|
||||
expect(sectionSpec('root', '执行与工具').collapsed).toBe(true)
|
||||
expect(sectionSpec('en', 'Execution and tools').collapsed).toBe(true)
|
||||
expect(sectionSpec('root', '概念').collapsed).toBeUndefined()
|
||||
})
|
||||
|
||||
it('gives each page its own position within a section', () => {
|
||||
// Sidebar entries sort by order alone, so a shared value leaves the two
|
||||
// pages ranked by whichever manifest block happens to be concatenated
|
||||
// first rather than by an intent the manifest states.
|
||||
const taken = new Map<string, string>()
|
||||
const collisions: string[] = []
|
||||
for (const page of docsPages) {
|
||||
const slot = `${page.locale}/${String(page.sidebar)}/${page.section}#${page.order}`
|
||||
const holder = taken.get(slot)
|
||||
if (holder === undefined) taken.set(slot, page.label)
|
||||
else collisions.push(`${slot}: ${holder} / ${page.label}`)
|
||||
}
|
||||
expect(collisions).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('addProjectionFrontmatter', () => {
|
||||
it('adds frontmatter to an ordinary Markdown page', () => {
|
||||
expect(addProjectionFrontmatter('# Guide\n', { source: 'docs/guide.md' })).toBe(
|
||||
@@ -411,6 +468,25 @@ describe('projectedPageContent', () => {
|
||||
expect(projectedPageContent(markdown, page('zh-guide'))).toBe(markdown)
|
||||
})
|
||||
|
||||
it('drops the language switcher the navigation bar already offers', () => {
|
||||
expect(projectedPageContent('# Guide\n\nEnglish | [中文](./en/guide)\n\nBody.\n', page('zh-guide')))
|
||||
.toBe('# Guide\n\nBody.\n')
|
||||
expect(projectedPageContent('# 指南\n\n[English](./en/guide) | 中文\n\n正文。\n', page('zh-guide')))
|
||||
.toBe('# 指南\n\n正文。\n')
|
||||
})
|
||||
|
||||
it('drops the repository badge every page links from its footer', () => {
|
||||
const badge = '[](https://github.com/deepseek-ai/deepseek-harness)'
|
||||
expect(projectedPageContent(`# Guide\n\nBody.\n\n${badge}\n`, page('zh-guide')))
|
||||
.toBe('# Guide\n\nBody.\n')
|
||||
})
|
||||
|
||||
it('keeps a switcher-shaped line that is not the page header', () => {
|
||||
// A tutorial showing the convention must still render the example.
|
||||
const sample = '# Guide\n\nA\n\nB\n\nC\n\nD\n\nE\n\nEnglish | [中文](./x)\n'
|
||||
expect(projectedPageContent(sample, page('zh-guide'))).toBe(sample)
|
||||
})
|
||||
|
||||
it('rejects a locale home source without frontmatter', () => {
|
||||
expect(() => projectedPageContent('# Harness\n', page(null)))
|
||||
.toThrow('locale home source "docs/index.zh.md" must start with YAML frontmatter')
|
||||
|
||||
@@ -292,6 +292,37 @@ export function addProjectionFrontmatter(markdown: string, page: Pick<DocsPage,
|
||||
return `---\n${fields}\n---\n\n${markdown}`
|
||||
}
|
||||
|
||||
/** The switcher line a canonical page carries so its GitHub reader can reach the other language. */
|
||||
const LANGUAGE_SWITCHER = /^(?:English \| \[中文\]\([^)]*\)|\[English\]\([^)]*\) \| 中文)$/
|
||||
|
||||
/** The repository badge a canonical page carries for its GitHub reader. */
|
||||
const REPOSITORY_BADGE = /^\[!\[[^\]]*\]\(https:\/\/img\.shields\.io\/[^)]*\)\]\([^)]*\)$/
|
||||
|
||||
/**
|
||||
* Drop the lines that address a canonical page's GitHub reader.
|
||||
*
|
||||
* The site carries a locale switcher in its navigation bar and links the
|
||||
* repository from every page, so projecting these lines would repeat both — the
|
||||
* switcher as the first element under each heading.
|
||||
*
|
||||
* @param markdown Rewritten canonical Markdown content.
|
||||
* @returns The content without the switcher line or the repository badge.
|
||||
*/
|
||||
function withoutRepositoryChrome(markdown: string): string {
|
||||
const lines = markdown.split('\n')
|
||||
const switcher = lines.findIndex(line => LANGUAGE_SWITCHER.test(line))
|
||||
// Only the switcher introducing the page qualifies; further down the same
|
||||
// text is prose or a sample rather than the page's own header.
|
||||
if (switcher !== -1 && switcher < 8) {
|
||||
lines.splice(switcher, lines[switcher + 1] === '' ? 2 : 1)
|
||||
}
|
||||
const badge = lines.findLastIndex(line => REPOSITORY_BADGE.test(line))
|
||||
if (badge !== -1) {
|
||||
lines.splice(lines[badge - 1] === '' ? badge - 1 : badge, lines[badge - 1] === '' ? 2 : 1)
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the Markdown rendered for one published page.
|
||||
*
|
||||
@@ -300,7 +331,7 @@ export function addProjectionFrontmatter(markdown: string, page: Pick<DocsPage,
|
||||
* @returns Full Markdown for ordinary pages or frontmatter-only Markdown for a locale home page.
|
||||
*/
|
||||
export function projectedPageContent(markdown: string, page: DocsPage): string {
|
||||
if (page.sidebar !== null) return markdown
|
||||
if (page.sidebar !== null) return withoutRepositoryChrome(markdown)
|
||||
if (!markdown.startsWith('---\n')) {
|
||||
throw new Error(`project-doc-site: locale home source ${JSON.stringify(page.source)} must start with YAML frontmatter.`)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ describe('publication payload policy', () => {
|
||||
String.raw`src\index.ts`,
|
||||
'lib/types/index.d.ts.map',
|
||||
'./lib/types/index.d.ts.map',
|
||||
'lib/typert.remote-client.d.ts.map',
|
||||
'lib/client.js.map',
|
||||
'./lib/client.js.map',
|
||||
])('rejects static manifest path %s', (file) => {
|
||||
expect(isForbiddenPublicationFile(file)).toBe(true)
|
||||
})
|
||||
@@ -40,11 +43,19 @@ describe('publication payload policy', () => {
|
||||
])).toThrow('fixture.tgz publishes source file package/src/index.ts')
|
||||
})
|
||||
|
||||
it('rejects declaration maps in packed tarballs', () => {
|
||||
it('rejects source maps in packed tarballs', () => {
|
||||
expect(validateFixtureTarball([
|
||||
'package/package.json',
|
||||
'package/lib/types/index.d.ts.map',
|
||||
])).toThrow('fixture.tgz publishes declaration map package/lib/types/index.d.ts.map')
|
||||
])).toThrow('fixture.tgz publishes source map package/lib/types/index.d.ts.map')
|
||||
expect(validateFixtureTarball([
|
||||
'package/package.json',
|
||||
'package/lib/typert.remote-client.d.ts.map',
|
||||
])).toThrow('fixture.tgz publishes source map package/lib/typert.remote-client.d.ts.map')
|
||||
expect(validateFixtureTarball([
|
||||
'package/package.json',
|
||||
'package/lib/client.js.map',
|
||||
])).toThrow('fixture.tgz publishes source map package/lib/client.js.map')
|
||||
})
|
||||
|
||||
it('accepts a clean packed tarball', () => {
|
||||
@@ -56,19 +67,6 @@ describe('publication payload policy', () => {
|
||||
])).not.toThrow()
|
||||
})
|
||||
|
||||
it('allows only the TypeRT declaration map and its navigable source tree when requested', () => {
|
||||
const policy = { typeRTRemoteNavigation: true }
|
||||
expect(isForbiddenPublicationFile('src/index.ts', policy)).toBe(false)
|
||||
expect(isForbiddenPublicationFile('lib/typert.remote-client.d.ts.map', policy)).toBe(false)
|
||||
expect(isForbiddenPublicationFile('lib/types/index.d.ts.map', policy)).toBe(true)
|
||||
expect(() => {
|
||||
validateTarballPayload([
|
||||
'package/lib/typert.remote-client.d.ts.map',
|
||||
'package/src/index.ts',
|
||||
], 'fixture.tgz', policy)
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it('recognizes only the canonical Host-for-Client export pair', () => {
|
||||
expect(hasTypeRTRemoteNavigation({
|
||||
exports: {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/** Publication payload policy shared by static manifests and packed tarballs. */
|
||||
|
||||
/** Publication exceptions required for TypeRT declaration-map navigation. */
|
||||
export interface PublicationPayloadPolicy {
|
||||
readonly typeRTRemoteNavigation?: boolean
|
||||
}
|
||||
|
||||
/** Whether a package manifest exports generated Host-for-Client metadata with source navigation. */
|
||||
/**
|
||||
* Whether a package manifest exports generated Host-for-Client metadata.
|
||||
* @param manifest - parsed package manifest to inspect.
|
||||
* @returns whether the canonical `./remote` export pair is present.
|
||||
*/
|
||||
export function hasTypeRTRemoteNavigation(manifest: unknown): boolean {
|
||||
if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest)) return false
|
||||
const exportsField = (manifest as Record<string, unknown>).exports
|
||||
@@ -23,35 +22,34 @@ function payloadPath(file: string): string {
|
||||
return normalized.startsWith('package/') ? normalized.slice('package/'.length) : normalized
|
||||
}
|
||||
|
||||
/** Whether a package payload path exposes source or declaration-map intermediates. */
|
||||
export function isForbiddenPublicationFile(
|
||||
file: string,
|
||||
policy: PublicationPayloadPolicy = {},
|
||||
): boolean {
|
||||
/**
|
||||
* Whether a package payload path exposes source or map intermediates. Maps
|
||||
* serve editor navigation during development, where a workspace consumer
|
||||
* resolves their source through the package link; a published map resolves
|
||||
* nothing, so no payload publishes one.
|
||||
* @param file - manifest path or tarball member to classify.
|
||||
* @returns whether publishing this path is forbidden.
|
||||
*/
|
||||
export function isForbiddenPublicationFile(file: string): boolean {
|
||||
const normalized = payloadPath(file)
|
||||
if (policy.typeRTRemoteNavigation === true
|
||||
&& (normalized === 'src'
|
||||
|| normalized.startsWith('src/')
|
||||
|| normalized === 'lib/typert.remote-client.d.ts.map')) {
|
||||
return false
|
||||
}
|
||||
return normalized === 'src'
|
||||
|| normalized.startsWith('src/')
|
||||
|| normalized.endsWith('.d.ts.map')
|
||||
|| normalized.endsWith('.js.map')
|
||||
}
|
||||
|
||||
/** Reject source and declaration-map members in a packed npm tarball. */
|
||||
export function validateTarballPayload(
|
||||
files: readonly string[],
|
||||
context: string,
|
||||
policy: PublicationPayloadPolicy = {},
|
||||
): void {
|
||||
/**
|
||||
* Reject source and map members in a packed npm tarball.
|
||||
* @param files - tarball members to validate.
|
||||
* @param context - tarball identity named in the failure.
|
||||
*/
|
||||
export function validateTarballPayload(files: readonly string[], context: string): void {
|
||||
for (const file of files) {
|
||||
if (!isForbiddenPublicationFile(file, policy)) continue
|
||||
if (!isForbiddenPublicationFile(file)) continue
|
||||
const normalized = payloadPath(file)
|
||||
if (normalized === 'src' || normalized.startsWith('src/')) {
|
||||
throw new Error(`${context} publishes source file ${file}`)
|
||||
}
|
||||
throw new Error(`${context} publishes declaration map ${file}`)
|
||||
throw new Error(`${context} publishes source map ${file}`)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep
|
||||
import { createInterface } from 'node:readline/promises'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { parseArgs } from 'node:util'
|
||||
import { hasTypeRTRemoteNavigation, validateTarballPayload } from './publication-payload.ts'
|
||||
import { validateTarballPayload } from './publication-payload.ts'
|
||||
|
||||
const DEFAULT_REGISTRY = 'https://registry.npm.harnessment.com'
|
||||
const DEFAULT_OUTPUT_DIRECTORY = '.artifacts/npm-baseline'
|
||||
@@ -323,9 +323,7 @@ class ReleaseBundle {
|
||||
throw new Error(`unexpected or duplicate packed package: ${artifact.name}`)
|
||||
}
|
||||
if (expected.origin === 'harness') {
|
||||
validateTarballPayload(artifact.files, tarball, {
|
||||
typeRTRemoteNavigation: hasTypeRTRemoteNavigation(artifact.manifest),
|
||||
})
|
||||
validateTarballPayload(artifact.files, tarball)
|
||||
}
|
||||
if (artifact.version !== version) {
|
||||
throw new Error(`${tarball} has version ${artifact.version}; expected ${version}`)
|
||||
@@ -401,9 +399,7 @@ class ReleaseBundle {
|
||||
}
|
||||
const artifact = inspectTarball(path, runner)
|
||||
if (pkg.origin === 'harness') {
|
||||
validateTarballPayload(artifact.files, pkg.tarball, {
|
||||
typeRTRemoteNavigation: hasTypeRTRemoteNavigation(artifact.manifest),
|
||||
})
|
||||
validateTarballPayload(artifact.files, pkg.tarball)
|
||||
}
|
||||
if (artifact.name !== pkg.name || artifact.version !== this.manifest.version) {
|
||||
throw new Error(`tarball identity mismatch: ${pkg.tarball}`)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import { globSync, readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { hasTypeRTRemoteNavigation, validateTarballPayload } from '../publication-payload.ts'
|
||||
import { validateTarballPayload } from '../publication-payload.ts'
|
||||
|
||||
/** Dependency sections that constrain publish order: a consumer must publish after its dependency. */
|
||||
const ORDER_SECTIONS = ['dependencies', 'optionalDependencies'] as const
|
||||
@@ -225,9 +225,7 @@ class DshFamily extends ReleaseFamily {
|
||||
* @param files - every path inside its tarball.
|
||||
*/
|
||||
validatePayload(member: ReleaseMember, files: readonly string[]): void {
|
||||
validateTarballPayload(files, member.name, {
|
||||
typeRTRemoteNavigation: hasTypeRTRemoteNavigation(member.manifest),
|
||||
})
|
||||
validateTarballPayload(files, member.name)
|
||||
}
|
||||
|
||||
readonly installedEntry = { packageName: '@deepseek-ai/dsh', binPath: 'lib/bin.js' }
|
||||
|
||||
@@ -89,9 +89,9 @@ const GENERIC_SKIPS: readonly GenericSkip[] = [
|
||||
// the creator flow stages and which id the roster reports.
|
||||
{ file: 'packages/client/ui-agent-preset/src/client/AgentPresetSection.tsx', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/src/client/index.ts', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/apply.spec.ts', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/locales.spec.ts', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/section.spec.tsx', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/apply.client.spec.ts', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/locales.client.spec.ts', upstream: ['cordis'] },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/section.client.spec.tsx', upstream: ['cordis'] },
|
||||
{ file: 'apps/cli/tests/web-agent-presets.e2e.ts', upstream: ['cordis'] },
|
||||
{ file: 'apps/web/tests/agent-preset-authoring.e2e.ts', upstream: ['cordis'] },
|
||||
{ file: 'packages/preset/agent-presets/tests/session.spec.ts', upstream: ['cordis'] },
|
||||
@@ -123,12 +123,12 @@ const POSTCONDITIONS: readonly PostCondition[] = [
|
||||
{ file: 'scripts/check-workspace-constraints.ts', text: '?.[\'@deepseek-ai/cordis\']', count: 2 },
|
||||
{ file: 'packages/boot/app-boot/tsdown.config.ts', text: '[\'@deepseek-ai/cordis-plugin-include\']', count: 1 },
|
||||
{ file: 'tsconfig.base.json', text: '"@deepseek-ai/cordis-plugin-loader": ["./vendor/loader/src"]', count: 1 },
|
||||
// One insertion, once: a duplicated log entry is what a non-idempotent apply produced.
|
||||
// The vendored README owns this required entry; reject its deletion or duplication.
|
||||
{ file: 'vendor/README.md', text: '17. **`@deepseek-ai` rescope**', count: 1 },
|
||||
{ file: 'knip.json', text: '@cordisjs', count: 0 },
|
||||
{ file: 'pnpm-workspace.yaml', text: '[email protected]', count: 0 },
|
||||
// The preset ids in this table are product data, not package names.
|
||||
{ file: 'packages/client/ui-agent-preset/tests/locales.spec.ts', text: '[\'cordis\', \'presetCordisName\'', count: 1 },
|
||||
{ file: 'packages/client/ui-agent-preset/tests/locales.client.spec.ts', text: '[\'cordis\', \'presetCordisName\'', count: 1 },
|
||||
// The preset id the shipped composition documents to its own model.
|
||||
{ file: 'apps/cli/config/agent-presets/cordis/agent.cordis.yml', text: 'The `cordis` agent preset', count: 1 },
|
||||
{ file: 'apps/cli/config/agent-presets/cordis/agent.cordis.yml', text: 'corrupting the `cordis` preset', count: 1 },
|
||||
@@ -241,13 +241,6 @@ const EXACT_EDITS: readonly ExactEdit[] = [
|
||||
replace: '| Directory | npm name | Upstream name | Version | Upstream repo | Commit |\n|---|---|---|---|---|---|',
|
||||
expect: 1,
|
||||
},
|
||||
{
|
||||
id: 'vendor-readme-local-modification-log',
|
||||
file: 'vendor/README.md',
|
||||
find: '\n16. **`cordis/package.json` publishes `src`**',
|
||||
replace: '\n16. **`cordis/package.json` publishes `src`**: added `src` to the `files` list, joining the other eight vendored packages. Cordis declares `"./src/*": "./src/*"` in its exports, so a tarball without `src` publishes an export map pointing at absent files; the release change judgement also reads `files` to decide whether a diff reaches the payload, and a package whose only published paths are build output has no tracked path to match.\n17. **`@deepseek-ai` rescope**: every vendored manifest `name`, every internal dependency entry among the vendored set, and every module specifier that reaches them use the scoped names in the manifest table\'s `npm name` column. Directory names, version numbers, and dependency ranges are unchanged, and no upstream runtime identifier is renamed — `Symbol.for(\'schemastery\')` and Schemastery\'s `vendor:` metadata field keep their upstream values. Re-apply with `pnpm run rescope-vendor --apply` after a sync; the table\'s two name columns are the mapping, restated for consumers in [docs/rescope.md](../docs/rescope.md).',
|
||||
expect: 1,
|
||||
},
|
||||
{
|
||||
// A plain fence listing the bundle's mounted tree: a bare token, no quotes.
|
||||
id: 'agent-spine-demo-mounted-tree',
|
||||
@@ -332,7 +325,7 @@ const VENDORED_LIBRARY = /^@deepseek-ai\\/(cosmokit|schemastery)(\\/|$)/
|
||||
{
|
||||
// The real package references in files whose other `cordis` strings are preset ids.
|
||||
id: 'agent-preset-spec-framework-import',
|
||||
file: 'packages/client/ui-agent-preset/tests/apply.spec.ts',
|
||||
file: 'packages/client/ui-agent-preset/tests/apply.client.spec.ts',
|
||||
find: "import { Context } from 'cordis'",
|
||||
replace: "import { Context } from '@deepseek-ai/cordis'",
|
||||
expect: 1,
|
||||
|
||||
@@ -155,15 +155,16 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
|
||||
return text_chunks(WORKFLOW_WORKER_TEXT)
|
||||
raise AssertionError(f"unexpected tool follow-up: {tool_name}")
|
||||
|
||||
user_prompts = [
|
||||
message_text(message.get("content"))
|
||||
for message in reversed(messages)
|
||||
if isinstance(message, dict) and message.get("role") == "user"
|
||||
]
|
||||
minimal_prompt = next(
|
||||
(
|
||||
message_text(message.get("content"))
|
||||
for message in reversed(messages)
|
||||
if isinstance(message, dict)
|
||||
and message.get("role") == "user"
|
||||
and message_text(message.get("content")).startswith(
|
||||
f"{MINIMAL_PROMPT}\n{MINIMAL_EDITOR_PATH_PREFIX}"
|
||||
)
|
||||
prompt
|
||||
for prompt in user_prompts
|
||||
if prompt.startswith(f"{MINIMAL_PROMPT}\n{MINIMAL_EDITOR_PATH_PREFIX}")
|
||||
),
|
||||
None,
|
||||
)
|
||||
@@ -183,7 +184,17 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
|
||||
"bash",
|
||||
{"command": MINIMAL_BASH_COMMAND},
|
||||
)
|
||||
prompt = message_text(latest.get("content"))
|
||||
scenario_prompts = {
|
||||
SNAPSHOT_DIRECT_CHILD_PROMPT,
|
||||
SNAPSHOT_WORKFLOW_CHILD_PROMPT,
|
||||
SNAPSHOT_PROMPT,
|
||||
CODE_PROMPT,
|
||||
WORKFLOW_PROMPT,
|
||||
}
|
||||
prompt = next(
|
||||
(candidate for candidate in user_prompts if candidate in scenario_prompts),
|
||||
message_text(latest.get("content")),
|
||||
)
|
||||
if prompt == SNAPSHOT_DIRECT_CHILD_PROMPT:
|
||||
return text_chunks("DIRECT_CHILD_OK")
|
||||
if prompt == SNAPSHOT_WORKFLOW_CHILD_PROMPT:
|
||||
@@ -781,6 +792,7 @@ def build_snapshot_files(
|
||||
) -> dict[str, str]:
|
||||
"""Render the SDK result and three persisted logs into stable expected outputs."""
|
||||
replacements = [(str(cwd), "{{cwd}}"), (SNAPSHOT_SESSION_ID, "{{parent}}")]
|
||||
replacements.append((snapshot_workflow_run_id(result), "{{workflow-run}}"))
|
||||
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)
|
||||
@@ -813,6 +825,21 @@ def build_snapshot_files(
|
||||
return files
|
||||
|
||||
|
||||
def snapshot_workflow_run_id(result: "RunResult") -> str:
|
||||
"""Return the one workflow run id emitted by the advanced scenario."""
|
||||
run_ids: set[str] = set()
|
||||
for event in result.events:
|
||||
event_type = event.get("type")
|
||||
data = event.get("data")
|
||||
if not isinstance(event_type, str) or not event_type.startswith("tool-workflow/"):
|
||||
continue
|
||||
if isinstance(data, dict) and isinstance(data.get("runId"), str):
|
||||
run_ids.add(data["runId"])
|
||||
if len(run_ids) != 1:
|
||||
raise AssertionError(f"advanced snapshot expected one workflow run id: {sorted(run_ids)}")
|
||||
return next(iter(run_ids))
|
||||
|
||||
|
||||
def snapshot_agent_id(result: "RunResult", child_id: str) -> str:
|
||||
"""Find the successful subagent id paired with one child session."""
|
||||
for notification in result.notifications:
|
||||
|
||||
@@ -874,9 +874,49 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool/result",
|
||||
"type": "tool-workflow/run-start",
|
||||
"seq": 48,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"name": "advanced-exe-snapshot"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool-workflow/agent-start",
|
||||
"seq": 49,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"seq": 1,
|
||||
"label": "workflow-child",
|
||||
"phase": "Delegate",
|
||||
"childId": "{{child-2}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool-workflow/agent-end",
|
||||
"seq": 50,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"seq": 1,
|
||||
"outcome": "completed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool-workflow/run-end",
|
||||
"seq": 51,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"stopReason": "completed"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool/result",
|
||||
"seq": 52,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
"step": 4,
|
||||
@@ -909,7 +949,7 @@
|
||||
},
|
||||
{
|
||||
"type": "step/end",
|
||||
"seq": 49,
|
||||
"seq": 53,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -918,7 +958,7 @@
|
||||
},
|
||||
{
|
||||
"type": "step/start",
|
||||
"seq": 50,
|
||||
"seq": 54,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -927,7 +967,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 51,
|
||||
"seq": 55,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -941,7 +981,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 52,
|
||||
"seq": 56,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -957,7 +997,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 53,
|
||||
"seq": 57,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -976,7 +1016,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 54,
|
||||
"seq": 58,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -992,7 +1032,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 55,
|
||||
"seq": 59,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1007,7 +1047,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/message",
|
||||
"seq": 56,
|
||||
"seq": 60,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1035,17 +1075,17 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
},
|
||||
{
|
||||
"type": "tool/call",
|
||||
"seq": 57,
|
||||
"seq": 61,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1057,7 +1097,7 @@
|
||||
},
|
||||
{
|
||||
"type": "tool/result",
|
||||
"seq": 58,
|
||||
"seq": 62,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1085,13 +1125,13 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
57
|
||||
61
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
},
|
||||
{
|
||||
"type": "step/end",
|
||||
"seq": 59,
|
||||
"seq": 63,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1100,7 +1140,7 @@
|
||||
},
|
||||
{
|
||||
"type": "step/start",
|
||||
"seq": 60,
|
||||
"seq": 64,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1109,7 +1149,7 @@
|
||||
},
|
||||
{
|
||||
"type": "request/header",
|
||||
"seq": 61,
|
||||
"seq": 65,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"header": {
|
||||
@@ -1141,7 +1181,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 62,
|
||||
"seq": 66,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1155,7 +1195,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 63,
|
||||
"seq": 67,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1169,7 +1209,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 64,
|
||||
"seq": 68,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1186,7 +1226,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 65,
|
||||
"seq": 69,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1202,7 +1242,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/chunk",
|
||||
"seq": 66,
|
||||
"seq": 70,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1217,7 +1257,7 @@
|
||||
},
|
||||
{
|
||||
"type": "assistant/message",
|
||||
"seq": 67,
|
||||
"seq": 71,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1243,17 +1283,17 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
},
|
||||
{
|
||||
"type": "step/end",
|
||||
"seq": 68,
|
||||
"seq": 72,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -1262,7 +1302,7 @@
|
||||
},
|
||||
{
|
||||
"type": "turn/end",
|
||||
"seq": 69,
|
||||
"seq": 73,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2334,9 +2374,42 @@
|
||||
"payload": {
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "session/title",
|
||||
"type": "user/message",
|
||||
"seq": 6,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"kind": "plugin",
|
||||
"plugin": "@deepseek-ai/dsh-system-prompt",
|
||||
"form": "snapshot",
|
||||
"sections": [
|
||||
{
|
||||
"name": "subagent:delegation",
|
||||
"text": "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."
|
||||
}
|
||||
]
|
||||
},
|
||||
"role": "user",
|
||||
"id": "{{messageId}}"
|
||||
},
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "session/title",
|
||||
"seq": 7,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"title": "Reply with exactly DIRECT_CHILD_OK and",
|
||||
"messageSeqs": [
|
||||
@@ -2355,7 +2428,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "request/header",
|
||||
"seq": 7,
|
||||
"seq": 8,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"header": {
|
||||
@@ -2394,7 +2467,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "request/context",
|
||||
"seq": 8,
|
||||
"seq": 9,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"provider": "deepseek-official",
|
||||
@@ -2410,7 +2483,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 9,
|
||||
"seq": 10,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2430,7 +2503,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 10,
|
||||
"seq": 11,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2450,7 +2523,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 11,
|
||||
"seq": 12,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2473,7 +2546,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 12,
|
||||
"seq": 13,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2495,7 +2568,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 13,
|
||||
"seq": 14,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2516,7 +2589,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "assistant/message",
|
||||
"seq": 14,
|
||||
"seq": 15,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2542,11 +2615,11 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13
|
||||
13,
|
||||
14
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
@@ -2558,7 +2631,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "step/end",
|
||||
"seq": 15,
|
||||
"seq": 16,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2573,7 +2646,7 @@
|
||||
"sessionId": "{{child-1}}",
|
||||
"event": {
|
||||
"type": "turn/end",
|
||||
"seq": 16,
|
||||
"seq": 17,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -2850,6 +2923,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool-workflow/run-start",
|
||||
"seq": 48,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"name": "advanced-exe-snapshot"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "subagent.started",
|
||||
"payload": {
|
||||
@@ -2986,9 +3074,42 @@
|
||||
"payload": {
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "session/title",
|
||||
"type": "user/message",
|
||||
"seq": 6,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"kind": "plugin",
|
||||
"plugin": "@deepseek-ai/dsh-system-prompt",
|
||||
"form": "snapshot",
|
||||
"sections": [
|
||||
{
|
||||
"name": "subagent:delegation",
|
||||
"text": "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."
|
||||
}
|
||||
]
|
||||
},
|
||||
"role": "user",
|
||||
"id": "{{messageId}}"
|
||||
},
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "session/title",
|
||||
"seq": 7,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"title": "Reply with exactly WORKFLOW_CHILD_OK and",
|
||||
"messageSeqs": [
|
||||
@@ -3007,7 +3128,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "request/header",
|
||||
"seq": 7,
|
||||
"seq": 8,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"header": {
|
||||
@@ -3046,7 +3167,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "request/context",
|
||||
"seq": 8,
|
||||
"seq": 9,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"provider": "deepseek-official",
|
||||
@@ -3056,13 +3177,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool-workflow/agent-start",
|
||||
"seq": 49,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"seq": 1,
|
||||
"label": "workflow-child",
|
||||
"phase": "Delegate",
|
||||
"childId": "{{child-2}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 9,
|
||||
"seq": 10,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3082,7 +3221,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 10,
|
||||
"seq": 11,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3102,7 +3241,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 11,
|
||||
"seq": 12,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3125,7 +3264,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 12,
|
||||
"seq": 13,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3147,7 +3286,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 13,
|
||||
"seq": 14,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3168,7 +3307,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "assistant/message",
|
||||
"seq": 14,
|
||||
"seq": 15,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3194,11 +3333,11 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13
|
||||
13,
|
||||
14
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
@@ -3210,7 +3349,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "step/end",
|
||||
"seq": 15,
|
||||
"seq": 16,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3225,7 +3364,7 @@
|
||||
"sessionId": "{{child-2}}",
|
||||
"event": {
|
||||
"type": "turn/end",
|
||||
"seq": 16,
|
||||
"seq": 17,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3260,13 +3399,44 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool-workflow/agent-end",
|
||||
"seq": 50,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"seq": 1,
|
||||
"outcome": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool-workflow/run-end",
|
||||
"seq": 51,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"runId": "{{workflow-run}}",
|
||||
"stopReason": "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "session.event",
|
||||
"payload": {
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool/result",
|
||||
"seq": 48,
|
||||
"seq": 52,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3306,7 +3476,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "step/end",
|
||||
"seq": 49,
|
||||
"seq": 53,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3321,7 +3491,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "step/start",
|
||||
"seq": 50,
|
||||
"seq": 54,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3336,7 +3506,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 51,
|
||||
"seq": 55,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3356,7 +3526,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 52,
|
||||
"seq": 56,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3378,7 +3548,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 53,
|
||||
"seq": 57,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3403,7 +3573,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 54,
|
||||
"seq": 58,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3425,7 +3595,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 55,
|
||||
"seq": 59,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3446,7 +3616,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/message",
|
||||
"seq": 56,
|
||||
"seq": 60,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3474,11 +3644,11 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
@@ -3490,7 +3660,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool/call",
|
||||
"seq": 57,
|
||||
"seq": 61,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3508,7 +3678,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "tool/result",
|
||||
"seq": 58,
|
||||
"seq": 62,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3536,7 +3706,7 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
57
|
||||
61
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
@@ -3548,7 +3718,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "step/end",
|
||||
"seq": 59,
|
||||
"seq": 63,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3563,7 +3733,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "step/start",
|
||||
"seq": 60,
|
||||
"seq": 64,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3578,7 +3748,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "request/header",
|
||||
"seq": 61,
|
||||
"seq": 65,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"header": {
|
||||
@@ -3616,7 +3786,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 62,
|
||||
"seq": 66,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3636,7 +3806,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 63,
|
||||
"seq": 67,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3656,7 +3826,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 64,
|
||||
"seq": 68,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3679,7 +3849,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 65,
|
||||
"seq": 69,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3701,7 +3871,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/chunk",
|
||||
"seq": 66,
|
||||
"seq": 70,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3722,7 +3892,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "assistant/message",
|
||||
"seq": 67,
|
||||
"seq": 71,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3748,11 +3918,11 @@
|
||||
}
|
||||
},
|
||||
"sourceEventSeqs": [
|
||||
62,
|
||||
63,
|
||||
64,
|
||||
65,
|
||||
66
|
||||
66,
|
||||
67,
|
||||
68,
|
||||
69,
|
||||
70
|
||||
],
|
||||
"surfaceOp": "append"
|
||||
}
|
||||
@@ -3764,7 +3934,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "step/end",
|
||||
"seq": 68,
|
||||
"seq": 72,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
@@ -3779,7 +3949,7 @@
|
||||
"sessionId": "{{parent}}",
|
||||
"event": {
|
||||
"type": "turn/end",
|
||||
"seq": 69,
|
||||
"seq": 73,
|
||||
"time": 0,
|
||||
"data": {
|
||||
"turn": 1,
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
{"type":"subagent/descriptor","seq":3,"time":0,"data":{"version":2,"mode":"one-shot","provider":"spawn","label":"Check direct child"}}
|
||||
{"type":"step/start","seq":4,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","seq":5,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":6,"time":0,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":7,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
|
||||
{"type":"request/context","seq":8,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
|
||||
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":14,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":15,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","seq":16,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"user/message","seq":6,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":7,"time":0,"data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":8,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
|
||||
{"type":"request/context","seq":9,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
|
||||
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"DIRECT_CHILD_OK"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","seq":17,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -5,14 +5,15 @@
|
||||
{"type":"subagent/descriptor","seq":3,"time":0,"data":{"version":2,"mode":"one-shot","provider":"spawn"}}
|
||||
{"type":"step/start","seq":4,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","seq":5,"time":0,"data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":6,"time":0,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":7,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
|
||||
{"type":"request/context","seq":8,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
|
||||
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":14,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":15,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","seq":16,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"user/message","seq":6,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":7,"time":0,"data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[5],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":8,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","snapshot_double","subagent","task_kill","task_list","task_output","workflow"]},"reason":"initial"}}
|
||||
{"type":"request/context","seq":9,"time":0,"data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}}
|
||||
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"text-delta","index":0,"text":"WORKFLOW_CHILD_OK"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":15,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":16,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"turn/end","seq":17,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -47,25 +47,29 @@
|
||||
{"type":"assistant/chunk","seq":45,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":46,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","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\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[41,42,43,44,45],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":47,"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":48,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[47],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":49,"time":0,"data":{"turn":1,"step":4}}
|
||||
{"type":"step/start","seq":50,"time":0,"data":{"turn":1,"step":5}}
|
||||
{"type":"assistant/chunk","seq":51,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":52,"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":53,"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":54,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":55,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":56,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[51,52,53,54,55],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":57,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
|
||||
{"type":"tool/result","seq":58,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[57],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":59,"time":0,"data":{"turn":1,"step":5}}
|
||||
{"type":"step/start","seq":60,"time":0,"data":{"turn":1,"step":6}}
|
||||
{"type":"request/header","seq":61,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","subagent","task_kill","task_list","task_output","workflow"]},"reason":"change"}}
|
||||
{"type":"assistant/chunk","seq":62,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":63,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
|
||||
{"type":"assistant/chunk","seq":64,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
|
||||
{"type":"assistant/chunk","seq":65,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":66,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":67,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[62,63,64,65,66],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":68,"time":0,"data":{"turn":1,"step":6}}
|
||||
{"type":"turn/end","seq":69,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
{"type":"tool-workflow/run-start","seq":48,"time":0,"data":{"runId":"{{workflow-run}}","name":"advanced-exe-snapshot"}}
|
||||
{"type":"tool-workflow/agent-start","seq":49,"time":0,"data":{"runId":"{{workflow-run}}","seq":1,"label":"workflow-child","phase":"Delegate","childId":"{{child-2}}"}}
|
||||
{"type":"tool-workflow/agent-end","seq":50,"time":0,"data":{"runId":"{{workflow-run}}","seq":1,"outcome":"completed"}}
|
||||
{"type":"tool-workflow/run-end","seq":51,"time":0,"data":{"runId":"{{workflow-run}}","stopReason":"completed"}}
|
||||
{"type":"tool/result","seq":52,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"advanced-workflow"},"content":[{"type":"tool-result","toolCallId":"advanced-workflow","content":[{"type":"text","text":"workflow \"advanced-exe-snapshot\" completed (1 agent).\nReturn value:\n{\n \"reply\": \"WORKFLOW_CHILD_OK\"\n}"}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[47],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":53,"time":0,"data":{"turn":1,"step":4}}
|
||||
{"type":"step/start","seq":54,"time":0,"data":{"turn":1,"step":5}}
|
||||
{"type":"assistant/chunk","seq":55,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":56,"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":57,"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":58,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":59,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":60,"time":0,"data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[55,56,57,58,59],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":61,"time":0,"data":{"turn":1,"step":5,"callId":"advanced-unmount","name":"cordis_unmount","arguments":"{\"id\": \"dyn-1\"}"}}
|
||||
{"type":"tool/result","seq":62,"time":0,"data":{"turn":1,"step":5,"message":{"source":{"kind":"tool","callId":"advanced-unmount"},"content":[{"type":"tool-result","toolCallId":"advanced-unmount","content":[{"type":"text","text":"Temporary Plugin dyn-1 was unmounted and removed."}],"isError":false}],"role":"user","id":"{{messageId}}"}},"sourceEventSeqs":[61],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":63,"time":0,"data":{"turn":1,"step":5}}
|
||||
{"type":"step/start","seq":64,"time":0,"data":{"turn":1,"step":6}}
|
||||
{"type":"request/header","seq":65,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"system":"{{system}}","tools":["cordis_inspect","cordis_mount","cordis_unmount","run_code","subagent","task_kill","task_list","task_output","workflow"]},"reason":"change"}}
|
||||
{"type":"assistant/chunk","seq":66,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":67,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"text-delta","index":0,"text":"ADVANCED_EXECUTABLE_OK"}}}
|
||||
{"type":"assistant/chunk","seq":68,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}}}}
|
||||
{"type":"assistant/chunk","seq":69,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
|
||||
{"type":"assistant/chunk","seq":70,"time":0,"data":{"turn":1,"step":6,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":71,"time":0,"data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"text","text":"ADVANCED_EXECUTABLE_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model"},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[66,67,68,69,70],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":72,"time":0,"data":{"turn":1,"step":6}}
|
||||
{"type":"turn/end","seq":73,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -4,39 +4,39 @@
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "# Translation Prompt\n\nYou are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the complete source document from English to Chinese, producing natural, professional technical prose.\n\nRead each complete semantic unit, understand it, and restate it as a native technical author would write it in the target language. Do not mechanically preserve source-language syntax. Then verify the translation against the source clause by clause: preserve every proposition and add none. Fluency never justifies losing or altering meaning, and completeness never justifies unnatural word-for-word prose.\n\n## Priority\n\nApply these authorities in order:\n\n1. Preserve the source meaning and the required document structure, protected content, and formatting.\n2. Follow the injected terminology table exactly.\n3. Use the injected whole-document gold pairs to calibrate target-language voice and phrasing.\n4. Apply the general writing guidance and illustrative examples in this prompt.\n\nA lower-priority rule may refine but never override a higher-priority requirement. Gold pairs calibrate voice; they are not a translation memory. No style preference, gold-pair phrasing, or embedded example may override source meaning, required structure, protected content, or the terminology table.\n\n## Quality Requirements\n\n### Structure and Format Preservation\n- Output a complete translated document that maintains the same document frame as the source: heading hierarchy and order, list kinds and item counts, ordered-list starts, table rows and columns, link targets, and code blocks.\n- Paragraph boundaries may change within the same structural unit when the target language needs different semantic grouping. Do not merge or move content across headings, list items, table cells, or other independent structural units.\n- Keep each prose paragraph on one physical line. Use paragraph breaks, not hard-wrapped lines inside a paragraph.\n- Fenced code blocks must be byte-identical to the source, including info strings, whitespace, and ALL comments inside them. Do NOT translate or reformat any content inside code blocks. This is a hard rule with no exceptions.\n- Inline code spans must be kept verbatim. This includes commands, flags, paths, identifiers, API and event names, config keys, protocol values, version numbers, and other machine-readable tokens. Never translate or reformat them.\n- Every relative link must point to the same target as in the source. Translate link text; do not change link targets.\n- Language switcher line: when an English source contains `English | [中文](source-filename.zh.md)`, write `[English](source-filename.md) | 中文`. When a Chinese source contains `[English](source-filename.md) | 中文`, write `English | [中文](source-filename.zh.md)`. Do NOT copy the source switcher unchanged. If the source has no switcher, do not invent a filename or switcher; the pipeline inserts the canonical target switcher after parsing `<final>`.\n- Preserve emphasis marker types and the semantic spans they cover. Do not add, remove, move, or change bold and italic markers.\n\n### Faithfulness\n- Preserve every proposition in the source and add none. Every sentence, list item, note, FIXME, warning, example, caveat, prerequisite, and guarantee must have an equivalent in the translation. Count list items on both sides.\n- Preserve actors, objects, conditions, exceptions, negation, modality, causal relationships, and distinctions between concepts.\n- Preserve the exact strength and orientation of contracts. Completion and lifecycle conditions, failure behavior, directions and data flow, normal and exceptional result channels, ownership changes, and quantitative bounds must not be weakened, strengthened, reversed, or merged.\n- Translate ideas rather than source-language idioms, but never use fluency as a reason to omit or alter meaning.\n\n### Tone and Style\n- The translation must read as if originally written in the target language by a native technical author. If an expression sounds like a word-for-word rendering from the source language, rephrase it.\n- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions.\n- Name an actor when the target language would otherwise obscure an actor that the source states or unambiguously implies. Never invent responsibility merely to avoid a passive construction.\n- Prefer established target-language engineering terms over literal renderings. Replace metaphors with direct descriptions that preserve the source meaning.\n- Use polite imperative forms where the text instructs the reader to do something. In Chinese, address the reader as `你`, not `您`.\n- Keep the author's register: concise stays concise, detailed stays detailed.\n\n### Sentence Structure\n- Break long sentences where the target language needs a pause. Avoid run-on sentences.\n- UseLine truncated
|
||||
"content": "# Translation Prompt\n\nYou are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the complete source document from English to Chinese, producing natural, professional technical prose.\n\nRead each complete semantic unit, understand it, and restate it as a native technical author would write it in the target language. Do not mechanically preserve source-language syntax. Then verify the translation against the source clause by clause: preserve every proposition and add none. Fluency never justifies losing or altering meaning, and completeness never justifies unnatural word-for-word prose.\n\n## Priority\n\nApply these authorities in order:\n\n1. Preserve the source meaning and the required document structure, protected content, and formatting.\n2. Follow the injected terminology table exactly.\n3. Use the injected whole-document gold pairs to calibrate target-language voice and phrasing.\n4. Apply the general writing guidance and illustrative examples in this prompt.\n\nA lower-priority rule may refine but never override a higher-priority requirement. Gold pairs calibrate voice; they are not a translation memory. No style preference, gold-pair phrasing, or embedded example may override source meaning, required structure, protected content, or the terminology table.\n\n## Quality Requirements\n\n### Structure and Format Preservation\n- Output a complete translated document that maintains the same document frame as the source: heading hierarchy and order, list kinds and item counts, ordered-list starts, table rows and columns, link targets, and code blocks.\n- Paragraph boundaries may change within the same structural unit when the target language needs different semantic grouping. Do not merge or move content across headings, list items, table cells, or other independent structural units.\n- Keep each prose paragraph on one physical line. Use paragraph breaks, not hard-wrapped lines inside a paragraph.\n- Fenced code blocks must be byte-identical to the source, including info strings, whitespace, and ALL comments inside them. Do NOT translate or reformat any content inside code blocks. This is a hard rule with no exceptions.\n- Inline code spans must be kept verbatim. This includes commands, flags, paths, identifiers, API and event names, config keys, protocol values, version numbers, and other machine-readable tokens. Never translate or reformat them.\n- Every relative link must point to the same target as in the source. Translate link text; do not change link targets.\n- Language switcher line: when an English source contains `English | [中文](source-filename.zh.md)`, write `[English](source-filename.md) | 中文`. When a Chinese source contains `[English](source-filename.md) | 中文`, write `English | [中文](source-filename.zh.md)`. Do NOT copy the source switcher unchanged. If the source has no switcher, do not invent a filename or switcher; the pipeline inserts the canonical target switcher after parsing `<final>`.\n- Preserve emphasis marker types and the semantic spans they cover. Do not add, remove, move, or change bold and italic markers.\n\n### Faithfulness\n- Preserve every proposition in the source and add none. Every sentence, list item, note, FIXME, warning, example, caveat, prerequisite, and guarantee must have an equivalent in the translation. Count list items on both sides.\n- Preserve actors, objects, conditions, exceptions, negation, modality, causal relationships, and distinctions between concepts.\n- Preserve the exact strength and orientation of contracts. Completion and lifecycle conditions, failure behavior, directions and data flow, normal and exceptional result channels, ownership changes, and quantitative bounds must not be weakened, strengthened, reversed, or merged.\n- Translate ideas rather than source-language idioms, but never use fluency as a reason to omit or alter meaning.\n\n### Tone and Style\n- The translation must read as if originally written in the target language by a native technical author. If an expression sounds like a word-for-word rendering from the source language, rephrase it.\n- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions.\n- Name an actor when the target language would otherwise obscure an actor that the source states or unambiguously implies. Never invent responsibility merely to avoid a passive construction.\n- Prefer established target-language engineering terms over literal renderings. Replace metaphors with direct descriptions that preserve the source meaning.\n- Use polite imperative forms where the text instructs the reader to do something. In Chinese, address the reader as `你`, not `您`.\n- Keep the author's register: concise stays concise, detailed stays detailed.\n\n### Sentence Structure\n- Break long sentences where the target language needs a pause. Avoid run-on sentences.\n- UseLine truncated
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "# DeepSeek Harness\n\nEnglish | [中文](README.zh.md)\n\nDeepSeek Harness (`dsh`) is an open-source coding agent built on the DeepSeek Harness SDK.\n\nIt uses an architecture where **everything is a plugin**.\n\n## Internal testing notice\n\nDeepSeek Harness is under internal testing. Features and interfaces may change.\n\nThe internal build uploads all Session Logs by default to help diagnose reported problems. Set `DSH_TELEMETRY_DISABLED=1` to disable telemetry. Send feedback through the internal WeChat group.\n\n## Run from source\n\nClone this repo, complete the [dependency and API-key setup](docs/user/guide/quickstart.md#step-1-install-and-configure-the-api-key), then run:\n\n```sh\npnpm dsh web\n```\n\n## Use DeepSeek Harness\n\n### Web UI\n\nStart the recommended local interface from the repository root:\n\n```sh\npnpm dsh web\n```\n\nThe command builds the repository before starting the Web UI, which is served at `http://127.0.0.1:3080` by default.\n\n### Profiles\n\nThe source CLI boots profiles — ordered stacks of plugin-bundle patch layers under your own overrides in `$DSH_HOME/profiles/<name>`:\n\n```sh\npnpm dsh --profile web # the browser UI\npnpm dsh plugin --profile tui add <package> # install a plugin into a custom profile\npnpm dsh --profile tui # boot it\n```\n\nThe [CLI reference](apps/cli/README.md#profiles) describes profile layout, layer semantics, and config dump commands.\n\n### Headless\n\nRun one task, print the final answer, and exit:\n\n```sh\npnpm dsh --profile headless \"summarize this workspace\"\n```\n\n### Automation and SDKs\n\nFrom a source checkout with `DEEPSEEK_API_KEY` in the environment or its root `.env`, start the ACP automation server:\n\n```sh\npnpm run demo:acp\n```\n\nThe [Python SDK](python/README.md) drives a bundled JSON-RPC runtime. The [examples](examples/README.md) cover the runnable headless, ACP, JSON-RPC, Code Mode, and self-referential compositions.\n\n## Why DeepSeek Harness\n\nBuilt-in capabilities cover file reading, editing, and search; shell and persistent PTY execution; reusable skills; task tracking, goals, plans, todos, and background tasks; subagents and workflows; sandboxing and approvals; settings and credentials; persistent, resumable, forkable, and queryable sessions; LSP and web access; context compaction; and telemetry. Each composition selects the subset appropriate to its surface. The Web UI includes Plan Mode.\n\n- **Everything is a plugin.** Models, tools, policies, storage, context management, and interfaces are composable [Cordis plugins](docs/user/develop/basic/index.md), so deployments can extend or replace behavior without forking the agent loop. See the [architecture](docs/architecture.md) for the underlying design.\n- **Runs are reconstructable.** Anything visible to the model is logged in the authoritative session stream; persistence, resume/fork/query, replay, telemetry, and UIs derive from the same events. See the [session-log architecture](docs/architecture.md#session-log).\n- **Code Mode (opt-in).** It exposes a `run_code` tool and a generated TypeScript SDK; only program output re-enters model context. See [Code Mode](packages/core/tools/README.md#code-mode).\n- **Self-referential Cordis tools are opt-in.** They let the agent inspect its live runtime and mount or unmount plugins while it runs. See the [Cordis tools](packages/self-modification/tool-cordis/README.md).\n\n## Community\n\nFollow <a href=\"https://x.com/Deepseekharness\">DeepSeek Harness on Twitter</a> for project updates.\n\n## Development\n\nStart with the [development guide](docs/development.md) and read the [architecture](docs/architecture.md) before changing packages.\n\nFor agents, follow [AGENTS.md](AGENTS.md).\n\nDeepSeek Harness is currently in internal testing.\n\n## License\n\n[BSD 3-Clause](LICENSE)\n\nThird-party dependencies and their licenses are disclosed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n\n## Contributing\n\nRead [CONTRIBUTING.md](CONTRIBUTING.md) before contributing to this repository.\n"
|
||||
"content": "# DeepSeek Harness\n\nEnglish | [中文](README.zh.md)\n\nDeepSeek Harness (`dsh`) is an open-source coding agent built on the DeepSeek Harness SDK.\n\nIt uses an architecture where **everything is a plugin**.\n\n## Internal testing notice\n\nDeepSeek Harness is under internal testing. Features and interfaces may change.\n\nSession Logs stay local by default. Set `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` to share a Session Log only when submitting feedback, or `DSH_TELEMETRY_MODE=FULL` to upload continuously; `FULL` also enables dsh-sdk command telemetry reporting an anonymous ID, the command result, and redacted project configuration. Send feedback through the internal WeChat group.\n\n## Run\n\nInstall Node.js ^22.19 or >= 24 and pnpm 11, then run the published package:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\nThe command initializes the Web profile and prints the Web UI URL, which is `http://127.0.0.1:3080` by default. Open it, add a DeepSeek API key under **Settings → Models**, then start a session. The invoking directory is the default workspace; try `Summarize this repository and identify its main packages.`\n\nContinue with the [Web UI guide](docs/user/guide/index.md).\n\n### Run from source\n\nTo run a repository checkout instead:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` prepares the repository artifacts. `pnpm dsh web` starts the Web UI without rebuilding and opens the same path.\n\n## Profiles and plugins\n\nA profile is an ordered list of plugin bundles. The shipped `web` profile powers `dsh web`. Manage a profile with `dsh plugin --profile <name> <pnpm args>`, which forwards the remaining arguments to pnpm in that profile's directory:\n\n```sh\nnpx -p @deepseek-ai/dsh dsh plugin --profile web add <package>\nnpx -p @deepseek-ai/dsh dsh plugin --profile web remove <package>\n```\n\n`add`, `remove`, `update`, `why`, and other pnpm commands work unchanged. The command initializes a missing profile before changing its packages and updates its bundle list from installed packages that declare `dsh.bundle`. See the [CLI reference](apps/cli/reference/README.md#plugin-management) for the exact behavior.\n\nThe [CLI reference](apps/cli/README.md) covers headless execution and custom profiles. The [Python SDK](python/README.md) and [examples](examples/README.md) cover programmatic and custom compositions.\n\n## Community\n\nFollow <a href=\"https://x.com/Deepseekharness\">DeepSeek Harness on Twitter</a> for project updates.\n\n## Development\n\nStart with the [development guide](docs/development.md) and read the [architecture](docs/architecture.md) before changing packages.\n\nFor agents, follow [AGENTS.md](AGENTS.md).\n\n## License\n\n[BSD 3-Clause](LICENSE)\n\nThird-party dependencies and their licenses are disclosed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n\n## Contributing\n\nRead [CONTRIBUTING.md](CONTRIBUTING.md) before contributing to this repository.\n"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 coding agent(智能体)。\n\n它采用了**一切皆插件**的架构。\n\n## 内测声明\n\nDeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。\n\n为帮助诊断上报的问题,内测版本默认上传所有会话日志。设置 `DSH_TELEMETRY_DISABLED=1` 可关闭遥测。请通过内部企业微信群反馈问题和建议。\n\n## 从源码运行\n\n克隆本仓库,完成[依赖安装和 API 密钥配置](docs/user/guide/quickstart.md#step-1-install-and-configure-the-api-key),然后运行:\n\n```sh\npnpm dsh web\n```\n\n## 使用 DeepSeek Harness\n\n### Web UI\n\n请从仓库根目录启动推荐的本地界面:\n\n```sh\npnpm dsh web\n```\n\n该命令会先构建仓库,再启动 Web UI。Web UI 默认通过 `http://127.0.0.1:3080` 提供服务。\n\n### Profile\n\n源码 CLI(命令行界面)会启动 profile:按序叠放的插件组合包 patch 层,之上再叠加你在 `$DSH_HOME/profiles/<name>` 中的自有覆盖层:\n\n```sh\npnpm dsh --profile web # the browser UI\npnpm dsh plugin --profile tui add <package> # install a plugin into a custom profile\npnpm dsh --profile tui # boot it\n```\n\nprofile 布局、层语义与配置输出命令详见 [CLI(命令行界面)参考](apps/cli/README.md#profiles)。\n\n### Headless\n\n运行一项任务,打印最终答案后退出:\n\n```sh\npnpm dsh --profile headless \"summarize this workspace\"\n```\n\n### 自动化与 SDK\n\n在源码检出中通过环境变量或根目录 `.env` 设置 `DEEPSEEK_API_KEY`,然后启动 ACP(Agent Client Protocol)自动化服务器:\n\n```sh\npnpm run demo:acp\n```\n\n[Python SDK](python/README.md) 驱动随附的 JSON-RPC 运行时。[示例](examples/README.md)涵盖可运行的 headless、ACP、JSON-RPC、Code Mode 和自指组合。\n\n## 为什么选择 DeepSeek Harness\n\n内置功能涵盖文件读取、编辑与搜索、shell 和持久 PTY 执行、可复用 skill(技能)、任务跟踪、目标、计划、待办事项与后台任务、subagent 与工作流、沙箱与审批、设置与凭据、可持久化、恢复、fork 与查询的会话、LSP 与 Web 访问、上下文压缩(context compaction),以及遥测。每个组合只选用适合其使用方式的能力子集。Web UI 包含 Plan Mode。\n\n- **一切皆插件。** 模型、工具、策略、存储、上下文管理和界面均为可组合的 [Cordis 插件](docs/user/develop/basic/index.md),部署方无需 fork agent loop(智能体循环)即可扩展或替换行为。底层设计见[架构文档](docs/architecture.md)。\n- **运行可重建。** 凡是模型可见的内容,都会记录在权威会话流中;持久化、恢复/fork/查询、回放、遥测和 UI 均从同一组事件派生。参见[会话日志架构](docs/architecture.md#session-log)。\n- **Code Mode(需显式启用)。** 它会提供 `run_code` 工具和生成的 TypeScript SDK,只有程序输出会重新进入模型上下文。参见 [Code Mode](packages/core/tools/README.md#code-mode)。\n- **自指 Cordis 工具需显式启用。** 这些工具可让 agent 检查自身的实时运行时,并在运行中挂载或卸载插件。参见 [Cordis 工具](packages/self-modification/tool-cordis/README.md)。\n\n## 社区\n\n扫描二维码,或打开 <a href=\"https://wj.qq.com/s2/27234598/03eb/\">DeepSeek Harness 微信社区申请页面</a> 申请加入。\n\n<p>\n <img src=\"assets/community-wecom-survey.png\" alt=\"DeepSeek Harness 微信社区二维码\" width=\"240\">\n</p>\n\n## 开发\n\n请先阅读[开发指南](docs/development.md);修改包之前,请阅读[架构文档](docs/architecture.md)。\n\n面向 agent:遵循 [AGENTS.md](AGENTS.md)。\n\nDeepSeek Harness 目前处于内测阶段。\n\n## 许可证\n\n[BSD 3-Clause](LICENSE)\n\n第三方依赖及其许可证在 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) 中披露。\n\n## 参与贡献\n\n向本仓库贡献前,请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。\n"
|
||||
"content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 coding agent(编程智能体)。\n\n它采用了**一切皆插件**的架构。\n\n## 内测说明\n\nDeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。\n\nSession Log 默认留在本地。设置 `DSH_TELEMETRY_MODE=FEEDBACK_ONLY` 可仅在提交反馈时共享 Session Log,设置 `DSH_TELEMETRY_MODE=FULL` 可持续上传;`FULL` 同时会启用 dsh-sdk 命令遥测,上报匿名 ID、命令结果以及脱敏后的项目配置。请通过内部企业微信群反馈问题和建议。\n\n\n## 运行\n\n请先安装 Node.js(版本要求:`^22.19` 或 `>=24`)和 pnpm 11,然后运行已发布的包:\n\n```sh\nnpx @deepseek-ai/dsh web\n```\n\n该命令会初始化 Web profile 并打印 Web UI 地址,默认地址为 `http://127.0.0.1:3080`。打开该地址,在**设置 → 模型**中添加 DeepSeek API 密钥,然后启动一个会话。运行命令时所在的目录将作为默认工作区;你可以尝试输入 `Summarize this repository and identify its main packages.`。\n\n下一步请阅读 [Web UI 指南](docs/user/guide/index.md)。\n\n### 从源码运行\n\n如需从仓库源码运行:\n\n```sh\ngit clone https://github.com/deepseek-ai/deepseek-harness.git\ncd deepseek-harness\npnpm install\npnpm run build\npnpm dsh web\n```\n\n`pnpm run build` 会准备仓库产物。`pnpm dsh web` 不会重新构建,而是直接启动同一个 Web UI。\n\n## Profile 与插件\n\nprofile 由一组按顺序排列的插件组合包构成。随附的 `web` profile 用于运行 `dsh web`。使用 `dsh plugin --profile <name> <pnpm args>` 管理 profile;该命令会在对应 profile 目录中将剩余参数转发给 pnpm:\n\n```sh\nnpx -p @deepseek-ai/dsh dsh plugin --profile web add <package>\nnpx -p @deepseek-ai/dsh dsh plugin --profile web remove <package>\n```\n\n`add`、`remove`、`update`、`why` 等 pnpm 命令均可直接使用。该命令会先初始化不存在的 profile,再修改其中的包,并根据声明了 `dsh.bundle` 的已安装包更新 bundle 列表。具体行为见 [CLI 参考](apps/cli/reference/README.md#plugin-management)。\n\n[CLI(命令行界面)参考](apps/cli/README.md)介绍 headless 执行与自定义 profile。[Python SDK](python/README.md) 和[示例](examples/README.md)介绍程序化组合与自定义组合。\n\n## 社区\n\n扫描二维码,或打开 <a href=\"https://wj.qq.com/s2/27234598/03eb/\">DeepSeek Harness 微信社区申请页面</a> 申请加入。\n\n<p>\n <img src=\"assets/community-wecom-survey.png\" alt=\"DeepSeek Harness 微信社区二维码\" width=\"240\">\n</p>\n\n## 开发\n\n请先阅读[开发指南](docs/development.md);修改包之前,请阅读[架构文档](docs/architecture.md)。\n\n面向 agent:遵循 [AGENTS.md](AGENTS.md)。\n\n## 许可证\n\n[BSD 3-Clause](LICENSE)\n\n第三方依赖及其许可证见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。\n\n## 参与贡献\n\n向本仓库贡献前,请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThe setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI organization. Design rationale and implementation details belong to the linked Agent Notes and scripts.\n\n## Setup tutorial\n\n### Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests.\n\n### First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also configures worktree-local Lefthook hooks and the `dsh-translation-pairing` Git merge driver through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract; the [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the merge driver.\n\nIf either integration is missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nIf the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nSetup is complete when `pnpm run typecheck` exits successfully.\n\n## Contributor reference\n\n### TypeScript project layout\n\nThe repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |\n| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler settings (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |\n\nHost and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.\n- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.\n- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.\n\n`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face:Line truncated
|
||||
"content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThe setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI organization. Design rationale and implementation details belong to the linked Agent Notes and scripts.\n\n## Setup tutorial\n\n### Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests.\n\n### First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also configures worktree-local Lefthook hooks and the `dsh-translation-pairing` Git merge driver through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract; the [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the merge driver.\n\nIf either integration is missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nIf the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nSetup is complete when `pnpm run typecheck` exits successfully.\n\n## Contributor reference\n\n### TypeScript project layout\n\nThe repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |\n| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler settings (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |\n\nHost and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.\n- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.\n- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.\n\n`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face:Line truncated
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# 开发指南\n\n[English](development.md) | 中文\n\n搭建教程引导新贡献者从准备前置条件开始,直到检出通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 组织方式。设计依据与实现细节属于链接的 Agent Note 和脚本。\n\n## 搭建教程\n\n### 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n### 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子和 `dsh-translation-pairing` Git 合并驱动。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 负责钩子路径的安全约定;[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责合并驱动。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致任一集成缺失,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n如果包装脚本拒绝现有 Git 配置或报告陈旧锁,请遵循其诊断和所链接的 Agent Note,不要凭猜测编辑 worktree 元数据。移动检出目录后,请重新运行包装脚本以重新生成自有路径。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n`pnpm run typecheck` 成功退出即表示搭建完成。\n\n## 贡献者参考\n\n### TypeScript 项目布局\n\n仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |\n| `tsconfig.host.json` | Host aggregate:Host package、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |\n| `tsconfig.client.json` | Client aggregate:`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译设置(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |\n\nHost 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。\n- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。\n- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。\n\n`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf;该门禁按「两个 leaf 配置同时存在」自动发现拆分包,Line truncated
|
||||
"content": "# 开发指南\n\n[English](development.md) | 中文\n\n搭建教程引导新贡献者从准备前置条件开始,直到检出目录通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 组织方式。设计依据与实现细节属于链接的 Agent Note 和脚本。\n\n## 搭建教程\n\n### 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n### 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子和 `dsh-translation-pairing` Git 合并驱动。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 负责钩子路径的安全约定;[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责合并驱动。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致任一集成缺失,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n如果包装脚本拒绝现有 Git 配置或报告陈旧锁,请遵循其诊断和所链接的 Agent Note,不要凭猜测编辑 worktree 元数据。移动检出目录后,请重新运行包装脚本以重新生成自有路径。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n`pnpm run typecheck` 成功退出即表示搭建完成。\n\n## 贡献者参考\n\n### TypeScript 项目布局\n\n仓库使用相互隔离的 Host 与 Client aggregate。普通包只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |\n| `tsconfig.host.json` | Host aggregate:Host 包、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |\n| `tsconfig.client.json` | Client aggregate:`packages/client/*` 包及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译设置(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` 包 extends。 | 否 |\n\nHost 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。\n- 构造全仓 `ts.Program` 的脚本显式以 `tsconfig.host.json` 或 `tsconfig.client.json` 为种子——根 solution 永不作为种子,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。\n- 新包只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client 插件的两份运行时产物都在 Client 构建阶段生成。\n\n`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf;该门禁按「两个 leaf 配置同时存在」自动发现拆分包,所以Line truncated
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "# Bilingual documentation\n\nEnglish | [中文](README.zh.md)\n\nThis repo's documentation is read by people and agents both inside and outside the company, so every document in scope is maintained in English and Simplified Chinese. This page defines the pairing contract, checks, scope, and exclusions; [translation-rules.md](translation-rules.md) defines how to translate; [terminology.md](terminology.md) is the terminology source of truth. Routine agent work follows the lightweight path in [docs/AGENTS.md](../AGENTS.md); the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow is available only through explicit user invocation.\n\n## The pairing contract\n\n- **Both languages carry equal authority.** A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.\n- **A pair is three sibling files.** The English `foo.md`, the Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files.\n- **The consistency record.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. `--write` stores those snapshots in the local Git object database before recording them, including uncommitted working-tree contents, and pins every distinct stored blob under a content-addressed `refs/dsh/translation-pairing/snapshots/` ref so garbage collection cannot invalidate a recorded recovery pointer. The recorded hashes therefore recover the exact last-confirmed text of either side, so an out-of-sync pair is updated by patching the counterpart minimally against the edited side's diff — never by re-translating whole files. Routine work makes that patch directly; when the user explicitly invokes the extended workflow, `pnpm run gen-translation-brief <pair>` can instead assemble the update at the narrowest safely aligned granularity and `--apply` can splice a code-fence-only change after structural validation ([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md)). After bringing the pair back in line, `pnpm run verify-translation-pairing --write <pair>` re-records both hashes; that yaml diff is the reviewable act of confirming consistency, which is why `--write` requires naming the pairs you confirmed (`--write --all` is the explicit corpus-wide form).\n\n When two branches contain valid confirmations of the same pair, the installed `dsh-translation-pairing` Git merge driver composes a new record only if Git's default text merge succeeds for both recorded owner-blob triplets and the merged pair retains its required switchers and structural signature. The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. Any structure the driver cannot verify remains an ordinary conflict; `pnpm run resolve-translation-pairing-conflicts` applies the same fail-closed operation to a merge that has already stopped, stages every safe pairing record, and exits unsuccessfully when other pairing conflicts remain. The [automatic pairing merges Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the mechanism and alternatives.\n- **Language switcher.** The Chinese file always links back immediately after its H1 heading with `[English](foo.md) | 中文`. An authored English file reciprocates there with `English | [中文](foo.zh.md)`; a listed generated English source omits that line so it remains byte-identical to generator output.\n- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, link targets, and verbatim code blocks match one to one across the pair — see [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).\n\n## The gate: verify-translation-pairing\n\n`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:\n\n1. Every document in scope has a complete pair. README discovery iLine truncated
|
||||
"content": "# Bilingual documentation\n\nEnglish | [中文](README.zh.md)\n\nThis repo's documentation is read by people and agents both inside and outside the company, so every document in scope is maintained in English and Simplified Chinese. This page defines the pairing contract, checks, scope, and exclusions; [translation-rules.md](translation-rules.md) defines how to translate; [terminology.md](terminology.md) is the terminology source of truth. Routine agent work follows the lightweight path in [docs/AGENTS.md](../AGENTS.md); the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow is available only through explicit user invocation.\n\n## The pairing contract\n\n- **Both languages carry equal authority.** A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.\n- **A pair is three sibling files.** The English `foo.md`, the Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files.\n- **The consistency record.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. `--write` stores those snapshots in the local Git object database before recording them, including uncommitted working-tree contents, and pins every distinct stored blob under a content-addressed `refs/dsh/translation-pairing/snapshots/` ref so garbage collection cannot invalidate a recorded recovery pointer. The recorded hashes therefore recover the exact last-confirmed text of either side, so an out-of-sync pair is updated by patching the counterpart minimally against the edited side's diff — never by re-translating whole files. Routine work makes that patch directly; when the user explicitly invokes the extended workflow, `pnpm run gen-translation-brief <pair>` can instead assemble the update at the narrowest safely aligned granularity and `--apply` can splice a code-fence-only change after structural validation ([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md)). After bringing the pair back in line, `pnpm run verify-translation-pairing --write <pair>` re-records both hashes; that yaml diff is the reviewable act of confirming consistency, which is why `--write` requires naming the pairs you confirmed (`--write --all` is the explicit corpus-wide form).\n\n When two branches contain valid confirmations of the same pair, the installed `dsh-translation-pairing` Git merge driver composes a new record only if Git's default text merge succeeds for both recorded owner-blob triplets and the merged pair retains its required switchers and structural signature. The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. Any structure the driver cannot verify remains an ordinary conflict; `pnpm run resolve-translation-pairing-conflicts` applies the same fail-closed operation to a merge that has already stopped, stages every safe pairing record, and exits unsuccessfully when other pairing conflicts remain. The [automatic pairing merges Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the mechanism and alternatives.\n- **Language switcher.** The Chinese file always links back immediately after its H1 heading with `[English](foo.md) | 中文`. An authored English file reciprocates there with `English | [中文](foo.zh.md)`; a listed generated English source omits that line so it remains byte-identical to generator output. A README published outside GitHub, such as PyPI project metadata, may use the canonical `https://github.com/deepseek-ai/deepseek-harness/blob/master/<repository-path>` URL to the same counterpart so the switcher still resolves there.\n- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, link targets, and verbatim code blocks match one to one across the pair — see [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).\n\n## The gate: verify-translation-pairing\n\n`pnpm run verLine truncated
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# 双语文档\n\n[English](README.md) | 中文\n\n本仓库的文档会被公司内外的人和 agent(智能体)阅读,因此范围内的每篇文档都以英文和简体中文维护。本页定义配对约定、检查、范围与排除规则;[translation-rules.md](translation-rules.md) 定义如何翻译;[terminology.md](terminology.md) 是术语真源。agent 的日常工作遵循 [docs/AGENTS.md](../AGENTS.md) 中的轻量路径;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时可用。\n\n## 配对约定\n\n- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。\n- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。\n- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 git blob hash:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n 用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。`--write` 会先把这些快照存入本地 Git 对象库再写下记录,未提交的工作树内容也不例外;它还会在内容寻址的 `refs/dsh/translation-pairing/snapshots/` ref 下固定每个不同的已存 blob,使垃圾回收无法让已记录的恢复指针失效。因此记录的 hash 能还原任一侧上次确认时的确切文本,所以失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译。日常工作会直接完成这份修补;用户显式调用扩展工作流时,可改由 `pnpm run gen-translation-brief <pair>` 以能安全对齐的最窄粒度汇集这次更新,并由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md))。两侧对齐后,`pnpm run verify-translation-pairing --write <pair>` 重新记录两个 hash;那份 yaml diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。\n\n 当两个分支都包含同一配对的有效确认时,已安装的 `dsh-translation-pairing` Git 合并驱动只会在 Git 默认文本合并能分别干净合并记录所指向的英文三方 blob 与中文三方 blob,且合并后的配对仍保留必需的语言切换行和结构签名时,组合出一份新记录。中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。任何合并驱动无法验证的结构都保留为普通冲突;`pnpm run resolve-translation-pairing-conflicts` 会对已经停止的合并执行同一套遇错即保留冲突的操作,暂存每份可安全生成的配对记录,并在还有其他配对冲突时以非零状态退出。[自动配对合并 Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责记录该机制与备选方案。\n- **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。\n- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、链接目标与逐字节一致的代码块在配对两侧一一对应;完整保持规则见 [translation-rules.md](translation-rules.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。\n\n## 门禁:verify-translation-pairing\n\n`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份约定:\n\n1. 范围内的每篇文档都有完整配对。发现 README 时,basename 不区分大小写,因此 `missions/readme.md` 与其他文档根一样属于范围。\n2. 任何已存在的配对产物都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)Line truncated
|
||||
"content": "# 双语文档\n\n[English](README.md) | 中文\n\n本仓库的文档会被公司内外的人和 agent(智能体)阅读,因此范围内的每篇文档都以英文和简体中文维护。本页定义配对约定、检查、范围与排除规则;[translation-rules.md](translation-rules.md) 定义如何翻译;[terminology.md](terminology.md) 是术语真源。agent 的日常工作遵循 [docs/AGENTS.md](../AGENTS.md) 中的轻量路径;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时可用。\n\n## 配对约定\n\n- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。\n- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。\n- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 Git blob hash:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n 用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。`--write` 会先把这些快照存入本地 Git 对象库再写下记录,未提交的 worktree 内容也不例外;它还会在内容寻址的 `refs/dsh/translation-pairing/snapshots/` ref 下固定每个不同的已存 blob,使垃圾回收无法让已记录的恢复指针失效。因此记录的 hash 能还原任一侧上次确认时的确切文本,所以失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译。日常工作会直接完成这份修补;用户显式调用扩展工作流时,可改由 `pnpm run gen-translation-brief <pair>` 以能安全对齐的最窄粒度汇集这次更新,并由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md))。两侧对齐后,`pnpm run verify-translation-pairing --write <pair>` 重新记录两个 hash;那份 YAML diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。\n\n 当两个分支都包含同一配对的有效确认时,已安装的 `dsh-translation-pairing` Git 合并驱动只会在 Git 默认文本合并能分别干净合并记录所指向的英文三方 blob 与中文三方 blob,且合并后的配对仍保留必需的语言切换行和结构签名时,组合出一份新记录。中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。任何合并驱动无法验证的结构都保留为普通冲突;`pnpm run resolve-translation-pairing-conflicts` 会对已经停止的合并执行同一套遇错即保留冲突的操作,暂存每份可安全生成的配对记录,并在还有其他配对冲突时以非零状态退出。[自动配对合并 Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责记录该机制与备选方案。\n- **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。发布到 GitHub 以外位置的 README(例如 PyPI 项目元数据)可以改用指向同一对侧文件的规范 `https://github.com/deepseek-ai/deepseek-harness/blob/master/<repository-path>` URL,使切换行在该位置仍可访问。\n- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、链接目标与逐字节一致的代码块在配对两侧一一对应;完整保持规则见 [translation-rules.md](translation-rules.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。\n\n## 门禁:verify-translation-pairing\n\n`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份约定:\n\n1. 范围内的每篇文档都有完整配对。发现 README 时,basename 不区分大小写Line truncated
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "# Translation rules\n\nEnglish | [中文](translation-rules.zh.md)\n\nHow to translate between the two sides of a documentation pair in this repo. Both languages carry equal authority ([README.md](README.md)): a change is authored in either language, and that side is the source for that update — these rules govern producing or updating the counterpart. They bind humans and agents equally. Routine agent work translates the changed content directly in one terminology-guided pass; the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow runs only when the user explicitly invokes it. Rule levels follow RFC 2119 usage: **MUST** / **MUST NOT** are gate- or review-blocking; **SHOULD** needs a stated reason to deviate; **MAY** is discretionary.\n\n## Faithfulness\n\n- The counterpart *MUST* say what the authored side says — no added behavior, prerequisites, warnings, version claims, or examples, and no dropped ones. If the pair disagrees on substance, neither language wins by default: fix the side that is wrong, then bring the other along in the same change.\n- The counterpart *SHOULD* read as natural technical writing in its own language, not word-by-word gloss. Translate meaning, restructure sentences where the target grammar wants it, and keep the author's register — terse stays terse.\n- Do not translate the untranslatable: if a sentence resists natural rendering because it leans on an idiom of the source language, translate the idea, not the idiom.\n\n## Voice\n\n- The register is calibrated by [style-samples.md](style-samples.md) — human-approved gold pairs, one per document genre. The counterpart MUST match the target-language side of the nearest sample; where its voice and a prose voice rule disagree, the sample wins. Chinese targets use institutional technical Chinese; English targets use concise professional developer prose.\n- Write as a native technical author restating the content, not as a translator transposing sentences, while preserving every source clause: nothing added, nothing dropped — fluency never justifies losing a clause.\n- Give sentences an explicit actor when the target language would otherwise obscure it; for Chinese, replace vague passives or abstract subjects with the actual actor (系统、门禁、评审人).\n- Prefer established target-language engineering idiom over calques (误报/漏检 for false positive/negative, 执行红线 for enforcement frontier); localize metaphors instead of transplanting them, and unpack noun chains where the target language requires it.\n- Split long paragraphs by semantic unit — one idea per paragraph. Paragraph boundaries MAY differ from the source; the structural signature does not count paragraphs.\n- When translating into Chinese, category nouns use Chinese with a first-mention English annotation (实操手册(cookbook)); when translating into English, use the conventional English category name. Literal directory or file references stay code-formatted English.\n\n## Structure preservation\n\nThe pairing gate checks heading depths, fenced code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, and link targets. Preserve the rest of the frame manually; the paired files MUST match one to one in:\n\n- heading hierarchy (same levels, same order — heading TEXT is translated),\n- list shape and numbering,\n- tables (same columns, same row order; header cells translated per terminology),\n- fenced code blocks — **byte-identical, including comments**; the pairing signature compares their info strings and contents, and ` ```ts ` blocks compile under `doc-typecheck`,\n- inline code spans (commands, flags, config keys, file paths, event names, API names, version numbers) — verbatim, never translated or reformatted,\n- links and anchors: every relative link MUST point at the same target in both files — by convention the `.md` path, not the `.zh.md` sibling — so links never dangle when one pair lands before its neighbors. The ONLY zh-specific link is the language switcher. Link TEXT is translated; the target is not.\n\nThe repo's Markdown conventions apply to `.zh.md` files unchanged: one physical line per paragraph (`verify-md-wrap`), resolving relative links (`verify-md-links`), exactly one trailing newline.\n\n## Terminology\n\n- [terminology.md](terminology.md) is the source of truth in both directions. Before translating, load it; every listed term MUST follow its row and its \"不要译作\" prohibitions. A Chinese target uses the \"中文\" column and its \"首次出现\" annotation; an English target uses the \"English\" column without adding a Chinese gloss.\n- For a Chinese target, an unlisted technical term MAY use an established rendering from a major Chinese-language OSS or vendor source (K8s/Vue/MDN Chinese docs, 微软简中风格指南, big-tech project docs), cited in the PR. Without such precedent it MUST sLine truncated
|
||||
"content": "# Translation rules\n\nEnglish | [中文](translation-rules.zh.md)\n\nHow to translate between the two sides of a documentation pair in this repo. Both languages carry equal authority ([README.md](README.md)): a change is authored in either language, and that side is the source for that update — these rules govern producing or updating the counterpart. They bind humans and agents equally. Routine agent work translates the changed content directly in one terminology-guided pass; the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow runs only when the user explicitly invokes it. Rule levels follow RFC 2119 usage: **MUST** / **MUST NOT** are gate- or review-blocking; **SHOULD** needs a stated reason to deviate; **MAY** is discretionary.\n\n## Faithfulness\n\n- The counterpart *MUST* say what the authored side says — no added behavior, prerequisites, warnings, version claims, or examples, and no dropped ones. If the pair disagrees on substance, neither language wins by default: fix the side that is wrong, then bring the other along in the same change.\n- The counterpart *SHOULD* read as natural technical writing in its own language, not word-by-word gloss. Translate meaning, restructure sentences where the target grammar wants it, and keep the author's register — terse stays terse.\n- Do not translate the untranslatable: if a sentence resists natural rendering because it leans on an idiom of the source language, translate the idea, not the idiom.\n\n## Voice\n\n- The register is calibrated by [style-samples.md](style-samples.md) — human-approved gold pairs, one per document genre. The counterpart MUST match the target-language side of the nearest sample; where its voice and a prose voice rule disagree, the sample wins. Chinese targets use institutional technical Chinese; English targets use concise professional developer prose.\n- Write as a native technical author restating the content, not as a translator transposing sentences, while preserving every source clause: nothing added, nothing dropped — fluency never justifies losing a clause.\n- Give sentences an explicit actor when the target language would otherwise obscure it; for Chinese, replace vague passives or abstract subjects with the actual actor (系统、门禁、评审人).\n- Prefer established target-language engineering idiom over calques (误报/漏检 for false positive/negative, 执行红线 for enforcement frontier); localize metaphors instead of transplanting them, and unpack noun chains where the target language requires it.\n- Split long paragraphs by semantic unit — one idea per paragraph. Paragraph boundaries MAY differ from the source; the structural signature does not count paragraphs.\n- When translating into Chinese, category nouns use Chinese with a first-mention English annotation (实操手册(cookbook)); when translating into English, use the conventional English category name. Literal directory or file references stay code-formatted English.\n\n## Structure preservation\n\nThe pairing gate checks heading depths, fenced code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, and link targets. Preserve the rest of the frame manually; the paired files MUST match one to one in:\n\n- heading hierarchy (same levels, same order — heading TEXT is translated),\n- list shape and numbering,\n- tables (same columns, same row order; header cells translated per terminology),\n- fenced code blocks — **byte-identical, including comments**; the pairing signature compares their info strings and contents, and ` ```ts ` blocks compile under `doc-typecheck`,\n- inline code spans (commands, flags, config keys, file paths, event names, API names, version numbers) — verbatim, never translated or reformatted,\n- links and anchors: every relative link MUST point at the same target in both files — by convention the `.md` path, not the `.zh.md` sibling — so links never dangle when one pair lands before its neighbors. The ONLY zh-specific link is the language switcher. A README rendered outside GitHub MAY use the canonical public repository URL to its exact counterpart as documented in [README.md](README.md). Link TEXT is translated; the target is not.\n\nThe repo's Markdown conventions apply to `.zh.md` files unchanged: one physical line per paragraph (`verify-md-wrap`), resolving relative links (`verify-md-links`), exactly one trailing newline.\n\n## Terminology\n\n- [terminology.md](terminology.md) is the source of truth in both directions. Before translating, load it; every listed term MUST follow its row and its \"不要译作\" prohibitions. A Chinese target uses the \"中文\" column and its \"首次出现\" annotation; an English target uses the \"English\" column without adding a Chinese gloss.\n- For a Chinese target, an unlisted technical term MAY use an established rendering from a major Chinese-language OSS Line truncated
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# 翻译规则\n\n[English](translation-rules.md) | 中文\n\n本文规定:如何在本仓库文档配对的中英文两种语言之间进行翻译。两种语言同权(见 [README.md](README.md)):每次变更可以用任一语言撰写,被编辑的一侧即为本次更新的源;本文的规则约束如何产出或更新对侧文件。这些规则对人类和 agent(智能体)同等生效。日常工作中,agent 会在术语指导下直接一次完成有改动内容的翻译;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时运行。规则级别沿用 RFC 2119 的用法:**必须(MUST)** / **禁止(MUST NOT)** 会卡门禁或评审;**应当(SHOULD)** 偏离时要说明理由;**可以(MAY)** 自行裁量。\n\n## 忠实性\n\n- 对侧文件*必须*传达与撰写侧相同的内容:不添加行为、前置条件、警告、版本声明或示例,也不漏掉任何一项。如果两侧在实质内容上不一致,没有哪种语言默认获胜;请修正错误的一侧,并在同一个变更里同步更新另一侧。\n- 对侧文件读起来*应当*是其语言自然的技术文字,而非逐词对照的译文。请根据语义翻译,在目标语言语法需要时重组句子,并保持原作者的语域(比如:简练的保持简练)。\n- 不要翻译不可译的内容:如果一句话依赖源语言的习语、无法自然转换,请翻译它的意思,而非习语本身。\n\n## 行文\n\n- 语体以 [style-samples.md](style-samples.md) 为校准锚点。人工定稿的金标样例按文体各一组,译文必须参照文体最接近的样例,采用其中目标语言一侧的语体;如果样例与本文的行文规则冲突,以样例为准。译成中文时,采用规范的技术制度文;译成英文时,采用简洁、专业的开发者文档语体。\n- 以母语技术作者的身份重述内容,而不是以译者身份逐句转写,同时保留原文的每个语义成分:不添加、不遗漏——流畅永远不是丢掉语义成分的理由。\n- 如果直译会让执行主体含糊,请明确写出实际执行者;译成中文时,应由「系统、门禁、评审人」等实际执行者作主语,避免含糊的被动句或抽象主语。\n- 优先采用目标语言中通行的工程表达,避免生硬直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻应自然改写,名词链则按目标语言的习惯拆开。\n- 长段按语义单元拆分,一段一件事。段落边界可以与原文不同;结构签名不比对段落数。\n- 翻译为中文时,类别名词使用中文并在首现括注英文(实操手册(cookbook));翻译为英文时,使用通行的英文类别名。指目录或文件本身时保留代码体英文。\n\n## 结构保持\n\n���对门禁会检查标题深度、围栏代码块、表格行列数、列表类型、有序列表起始编号、列表项数量与链接目标;门禁未覆盖的结构仍需人工核对。两个配对文件必须在以下方面一一对应:\n\n- 标题层级(相同级别、相同顺序;标题的**文字**要翻译);\n- 列表形态与编号;\n- 表格(相同的列、相同的行序;表头单元格按术语表翻译);\n- 围栏代码块:**逐字节一致,包括注释**。配对签名比对信息字符串与内容,` ```ts ` 块还要通过 `doc-typecheck` 编译;\n- 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号):原样保留,从不翻译或重排;\n- 链接与锚点:每个相对链接在两个文件中必须指向相同的目标(按约定是 `.md` 路径而非 `.zh.md` 兄弟文件),这样即使某对文档先于相邻文件落地,链接也不会悬空。唯一的 zh 特有链接是语言切换行。链接**文字**翻译;链接目标不翻。\n\n本仓库的 Markdown 约定对 `.zh.md` 文件原样生效:一个段落一个物理行(`verify-md-wrap`)、相对链接必须可解析(`verify-md-links`)、文件末尾恰好一个换行。\n\n## 术语\n\n- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;表内术语必须遵守对应行与「不要译作」禁项。译成中文时,采用「中文」列,并按「首次出现」列括注;译成英文时,采用「English」列,不加中文括注。\n- 译成中文时,术语表未收录的技术术语只有在主流中文 OSS 文档或厂商资料中已有通行译法时才可以翻译(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档),并须在 PR 中注明出处;否则必须保留英文,并在 PR 描述的「待定术语」中给出建议译法。\n- 译成英文时,采用通行的英文技术术语。如果源术语没有明确的通行对应词,则保留原词、附上简Line truncated
|
||||
"content": "# 翻译规则\n\n[English](translation-rules.md) | 中文\n\n本文规定:如何在本仓库文档配对的中英文两种语言之间进行翻译。两种语言同权(见 [README.md](README.md)):每次变更可以用任一语言撰写,被编辑的一侧即为本次更新的源;本文的规则约束如何产出或更新对侧文件。这些规则对人类和 agent(智能体)同等生效。日常工作中,agent 会在术语指导下直接一次完成有改动内容的翻译;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时运行。规则级别沿用 RFC 2119 的用法:**必须(MUST)** / **禁止(MUST NOT)** 会卡门禁或评审;**应当(SHOULD)** 偏离时要说明理由;**可以(MAY)** 自行裁量。\n\n## 忠实性\n\n- 对侧文件*必须*传达与撰写侧相同的内容:不添加行为、前置条件、警告、版本声明或示例,也不漏掉任何一项。如果两侧在实质内容上不一致,没有哪种语言默认获胜;请修正错误的一侧,并在同一个变更里同步更新另一侧。\n- 对侧文件读起来*应当*是其语言自然的技术文字,而非逐词对照的译文。请根据语义翻译,在目标语言语法需要时重组句子,并保持原作者的语域(比如:简练的保持简练)。\n- 不要翻译不可译的内容:如果一句话依赖源语言的习语、无法自然转换,请翻译它的意思,而非习语本身。\n\n## 行文\n\n- 语体以 [style-samples.md](style-samples.md) 为校准锚点。人工定稿的金标样例按文体各一组,译文必须参照文体最接近的样例,采用其中目标语言一侧的语体;如果样例与本文的行文规则冲突,以样例为准。译成中文时,采用规范的技术制度文;译成英文时,采用简洁、专业的开发者文档语体。\n- 以母语技术作者的身份重述内容,而不是以译者身份逐句转写,同时保留原文的每个语义成分:不添加、不遗漏——流畅永远不是丢掉语义成分的理由。\n- 如果直译会让执行主体含糊,请明确写出实际执行者;译成中文时,应由「系统、门禁、评审人」等实际执行者作主语,避免含糊的被动句或抽象主语。\n- 优先采用目标语言中通行的工程表达,避免生硬直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻应自然改写,名词链则按目标语言的习惯拆开。\n- 长段按语义单元拆分,一段一件事。段落边界可以与原文不同;结构签名不比对段落数。\n- 翻译为中文时,类别名词使用中文并在首现括注英文(实操手册(cookbook));翻译为英文时,使用通行的英文类别名。指目录或文件本身时保留代码体英文。\n\n## 结构保持\n\n���对门禁会检查标题深度、围栏代码块、表格行列数、列表类型、有序列表起始编号、列表项数量与链接目标;门禁未覆盖的结构仍需人工核对。两个配对文件必须在以下方面一一对应:\n\n- 标题层级(相同级别、相同顺序;标题的**文字**要翻译);\n- 列表形态与编号;\n- 表格(相同的列、相同的行序;表头单元格按术语表翻译);\n- 围栏代码块:**逐字节一致,包括注释**。配对签名比对信息字符串与内容,` ```ts ` 块还要通过 `doc-typecheck` 编译;\n- 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号):原样保留,从不翻译或重排;\n- 链接与锚点:每个相对链接在两个文件中必须指向相同的目标(按约定是 `.md` 路径而非 `.zh.md` 兄弟文件),这样即使某对文档先于相邻文件落地,链接也不会悬空。唯一的 zh 特有链接是语言切换行。在 GitHub 以外位置渲染的 README 可以按 [README.md](README.md) 的规定,使用指向确切对侧文件的规范公开仓库 URL。链接**文字**翻译;链接目标不翻。\n\n本仓库的 Markdown 约定对 `.zh.md` 文件原样生效:一个段落一个物理行(`verify-md-wrap`)、相对链接必须可解析(`verify-md-links`)、文件末尾恰好一个换行。\n\n## 术语\n\n- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;表内术语必须遵守对应行与「不要译作」禁项。译成中文时,采用「中文」列,并按「首次出现」列括注;译成英文时,采用「English」列,不加中文括注。\n- 译成中文时,术语表未收录的技术术语只有在主流中文 OSS 文档或厂商资料中已有通行译法时才可以翻译(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档),并须在 PR 中注明出处;否则必须保留英文,并在 PR 描述的「待定术语」中给出建Line truncated
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "# Agent Note: 通过配对兄弟文件与配对门禁实现双语文档\n\nStatus: implemented\n\n[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文\n\n## 问题\n\n本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁会注意到。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。\n\n## 决策\n\n- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。\n- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write <pair>`,要求点名所确认的配对;批量重新记录是显式的 `--write --all`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。\n- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两个 hash 匹配、中文侧和所有普通撰写的英文源都带切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。\n- **全语料统一要求。** 范围内的每篇文档从创建起就必须有完整配对;政策没有逐文件推进状态、日期分界或 README 专用类别。README 发现会覆盖 vendor 源码、依赖目录与被忽略的构建产物目录之外所有文件名不区分大小写匹配 README 的文件,包括今后新增的顶层目录。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。\n- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。\n- **翻译是 agent 的工作,由人评审。** 常规改动采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.md)确立的直接单遍路径。[扩展翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)保留委派翻译和其他较重机制,供用户显式调用;两条路径均以文档契约为真源。\n\n## 验证\n\n验证约定分别覆盖每个边界。`verify-translation-pairing` 固定配对完整性、hash、切换行和结构;[`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) 固定已发布配对按 locale 选择对应源文件;[`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) 固定 Loader YAML 的发现以及翻译记录的排除;[翻译提示词可运行快照](../../../../scripts/translation-prompt.snapshot.ts)则固定渲染后的系统消息、五对经评审的示例、源请求和响应消费结果。这些检查共同使配对漂移、发布漂移、配置误分Line truncated
|
||||
"content": "# Agent Note: 通过配对兄弟文件与配对门禁实现双语文档\n\nStatus: implemented\n\n[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文\n\n## 问题\n\n本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁能够发现。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。\n\n## 决策\n\n- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。\n- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 Git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write <pair>`,要求点名所确认的配对;批量重新记录是显式的 `--write --all`)会产生一份可评审的 YAML diff:确认一致在 PR 中是一个显式、可见的动作。\n- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两侧的 hash 均与记录匹配、中文侧和所有人工撰写的英文源都带语言切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。\n- **全语料统一要求。** 范围内的每篇文档从创建起就必须有完整配对;政策没有逐文件推进状态、日期分界或 README 专用类别。README 发现会覆盖 vendor 源码、依赖目录与被忽略的构建产物目录之外所有文件名不区分大小写匹配 README 的文件,包括今后新增的顶层目录。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。\n- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。\n- **翻译是 agent 的工作,由人评审。** 常规改动采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.md)确立的直接单遍路径。[扩展翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)保留委派翻译和其他较重机制,供用户显式调用;两条路径均以文档契约为真源。\n\n## 验证\n\n验证约定分别覆盖每个边界。`verify-translation-pairing` 固定配对完整性、hash、语言切换行和结构;[`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) 固定已发布配对按 locale 选择对应源文件;[`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) 固定 Loader YAML 的发现以及翻译记录的排除;[翻译提示词可运行快照](../../../../scripts/translation-prompt.snapshot.ts)则固定渲染后的系统消息、五对经评审的示例、源请求和所消费的响应。这些检查共Line truncated
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
storeGitBlob,
|
||||
} from './translation-pairing-git.ts'
|
||||
import {
|
||||
linksTo,
|
||||
isTranslationScopeFile,
|
||||
languageSwitcherTargets,
|
||||
linksTo,
|
||||
parseTranslationMarkdown,
|
||||
requiresSourceLanguageSwitcher,
|
||||
translationStructureDiff,
|
||||
@@ -164,15 +165,17 @@ function loadRecordOwners(
|
||||
function assertMergedPairStructure(paths: TranslationPairPaths, source: Buffer, zh: Buffer): void {
|
||||
const sourceTree = parseTranslationMarkdown(source.toString('utf8'))
|
||||
const zhTree = parseTranslationMarkdown(zh.toString('utf8'))
|
||||
if (requiresSourceLanguageSwitcher(paths.source) && !linksTo(sourceTree, basename(paths.zh))) {
|
||||
const sourceSwitcherTargets = languageSwitcherTargets(paths.source)
|
||||
const zhSwitcherTargets = languageSwitcherTargets(paths.zh)
|
||||
if (requiresSourceLanguageSwitcher(paths.source) && !linksTo(sourceTree, zhSwitcherTargets)) {
|
||||
throw new Error(`${paths.source} clean merge lost its language-switcher link to ${basename(paths.zh)}`)
|
||||
}
|
||||
if (!linksTo(zhTree, basename(paths.source))) {
|
||||
if (!linksTo(zhTree, sourceSwitcherTargets)) {
|
||||
throw new Error(`${paths.zh} clean merge lost its language-switcher link to ${basename(paths.source)}`)
|
||||
}
|
||||
const divergences = translationStructureDiff(
|
||||
translationStructureSignature(sourceTree, basename(paths.zh)),
|
||||
translationStructureSignature(zhTree, basename(paths.source)),
|
||||
translationStructureSignature(sourceTree, zhSwitcherTargets),
|
||||
translationStructureSignature(zhTree, sourceSwitcherTargets),
|
||||
)
|
||||
if (divergences.length > 0) {
|
||||
throw new Error(`${paths.source} and ${paths.zh} clean merges diverge structurally: ${divergences.join('; ')}`)
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
import {
|
||||
blobHash,
|
||||
isTranslationScopeFile,
|
||||
languageSwitcherTargets,
|
||||
linksTo,
|
||||
pairAnchorOfArgument,
|
||||
parseTranslationMarkdown,
|
||||
parseTranslationPairingCliArgs,
|
||||
@@ -151,6 +153,20 @@ describe('translation pairing switchers', () => {
|
||||
expect(requiresSourceLanguageSwitcher('docs/architecture.md')).toBe(true)
|
||||
expect(requiresSourceLanguageSwitcher('packages/core/session/README.md')).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts only the canonical public URL for an absolute switcher', () => {
|
||||
const targets = languageSwitcherTargets('python/sdk/README.zh.md')
|
||||
const canonical = parseTranslationMarkdown(
|
||||
'[中文](https://github.com/deepseek-ai/deepseek-harness/blob/master/python/sdk/README.zh.md)',
|
||||
)
|
||||
const wrongPath = parseTranslationMarkdown(
|
||||
'[中文](https://github.com/deepseek-ai/deepseek-harness/blob/master/other/README.zh.md)',
|
||||
)
|
||||
|
||||
expect(linksTo(canonical, targets)).toBe(true)
|
||||
expect(translationStructureSignature(canonical, targets).links).toEqual([])
|
||||
expect(linksTo(wrongPath, targets)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('translation pairing records', () => {
|
||||
|
||||
@@ -302,11 +302,19 @@ export function parseTranslationMarkdown(content: string): Nodes {
|
||||
return fromMarkdown(content, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] })
|
||||
}
|
||||
|
||||
/** Whether the tree contains a link to exactly `target`. */
|
||||
export function linksTo(tree: Nodes, target: string): boolean {
|
||||
const PUBLIC_REPOSITORY_BLOB_ROOT = 'https://github.com/deepseek-ai/deepseek-harness/blob/master/'
|
||||
|
||||
/** Return the accepted relative and public-repository links to one counterpart. */
|
||||
export function languageSwitcherTargets(counterpart: string): string[] {
|
||||
return [basename(counterpart), `${PUBLIC_REPOSITORY_BLOB_ROOT}${counterpart}`]
|
||||
}
|
||||
|
||||
/** Whether the tree contains a link to any accepted target. */
|
||||
export function linksTo(tree: Nodes, targets: string | readonly string[]): boolean {
|
||||
const accepted = new Set(typeof targets === 'string' ? [targets] : targets)
|
||||
let found = false
|
||||
const visit = (node: Nodes): void => {
|
||||
if (node.type === 'link' && node.url === target) found = true
|
||||
if (node.type === 'link' && accepted.has(node.url)) found = true
|
||||
if ('children' in node) for (const child of node.children) visit(child)
|
||||
}
|
||||
visit(tree)
|
||||
@@ -335,8 +343,14 @@ export function requiresSourceLanguageSwitcher(source: string): boolean {
|
||||
].includes(source)
|
||||
}
|
||||
|
||||
/** Collect the ordered structural signature, skipping one switcher target. */
|
||||
export function translationStructureSignature(tree: Nodes, switcherTarget: string): TranslationStructureSignature {
|
||||
/** Collect the ordered structural signature, skipping accepted switcher targets. */
|
||||
export function translationStructureSignature(
|
||||
tree: Nodes,
|
||||
switcherTargets: string | readonly string[],
|
||||
): TranslationStructureSignature {
|
||||
const acceptedSwitchers = new Set(
|
||||
typeof switcherTargets === 'string' ? [switcherTargets] : switcherTargets,
|
||||
)
|
||||
const sig: TranslationStructureSignature = { headings: [], code: [], tables: [], lists: [], links: [] }
|
||||
const visit = (node: Nodes): void => {
|
||||
switch (node.type) {
|
||||
@@ -355,7 +369,7 @@ export function translationStructureSignature(tree: Nodes, switcherTarget: strin
|
||||
: `bullet:items=${node.children.length}`)
|
||||
break
|
||||
case 'link':
|
||||
if (node.url !== switcherTarget) sig.links.push(node.url)
|
||||
if (!acceptedSwitchers.has(node.url)) sig.links.push(node.url)
|
||||
break
|
||||
default:
|
||||
// Every other node kind is prose or a container, not part of the signature.
|
||||
|
||||
@@ -221,10 +221,90 @@
|
||||
"symbol": "GoalChanged",
|
||||
"source": "packages/goal/goal/src/domain.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "AfterScheduleRecord",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "AtScheduleRecord",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "EveryScheduleRecord",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "LocalAtInput",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "AtInput",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "OneShotScheduleRecord",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleRecord",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleCreateChange",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleDeleteChange",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "OneShotScheduleDispatchChange",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "EveryScheduleDispatchChange",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleDispatchChange",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleChange",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleState",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleDeliveryMode",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/schedule.md",
|
||||
"symbol": "ScheduleView",
|
||||
"source": "packages/schedule/tool-schedule/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/commands.md",
|
||||
"symbol": "CommandInputDescriptor",
|
||||
"source": "packages/interaction/commands/src/index.ts"
|
||||
"source": "packages/interaction/commands/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/commands.md",
|
||||
@@ -239,12 +319,12 @@
|
||||
{
|
||||
"doc": "docs/subsystems/commands.md",
|
||||
"symbol": "CommandResult",
|
||||
"source": "packages/interaction/commands/src/index.ts"
|
||||
"source": "packages/interaction/commands/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/commands.md",
|
||||
"symbol": "CommandDescriptor",
|
||||
"source": "packages/interaction/commands/src/index.ts"
|
||||
"source": "packages/interaction/commands/src/types.ts"
|
||||
},
|
||||
{
|
||||
"doc": "docs/subsystems/commands.md",
|
||||
|
||||
@@ -35,6 +35,7 @@ const root = resolve(import.meta.dirname, '..')
|
||||
// specifiers resolve from apps/cli rather than the examples workspace.
|
||||
const appOverlayFiles = new Set([
|
||||
'examples/web-cordis/cordis.yml',
|
||||
'examples/web-schedule/cordis.yml',
|
||||
...globSync('examples/mcp-memory/*.cordis.yml', { cwd: root }),
|
||||
])
|
||||
const metadataFields = ['id', 'name', 'group', 'inject', 'intercept', 'isolate'] as const
|
||||
@@ -43,14 +44,17 @@ const metadataFields = ['id', 'name', 'group', 'inject', 'intercept', 'isolate']
|
||||
const CHOOSER_PACKAGE = '@deepseek-ai/dsh-host-directory-picker-auto'
|
||||
|
||||
/**
|
||||
* The backends the chooser mounts by runtime string (mirror of its exported
|
||||
* `BACKEND_PACKAGES`), invisible to yml-row scanning: a composition mounting
|
||||
* the chooser must resolve both, or keyless Linux CI (which only ever
|
||||
* resolves `browse`) hides a dropped `-native` dependency until a macOS boot.
|
||||
* The packages the chooser mounts by runtime string (mirror of its exported
|
||||
* `BACKEND_PACKAGES` and `SURFACE_PACKAGES`), invisible to yml-row scanning: a
|
||||
* composition mounting the chooser must resolve every one, or keyless Linux CI
|
||||
* (which only ever resolves `browse`) hides a dropped `-native` dependency
|
||||
* until a macOS boot.
|
||||
*/
|
||||
const CHOOSER_BACKEND_PACKAGES = [
|
||||
'@deepseek-ai/dsh-host-directory-picker-native',
|
||||
'@deepseek-ai/dsh-host-directory-picker-browse',
|
||||
'@deepseek-ai/dsh-client-ui-directory-picker',
|
||||
'@deepseek-ai/dsh-client-ui-directory-picker-native',
|
||||
]
|
||||
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
kind: 'scalar',
|
||||
|
||||
@@ -71,6 +71,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-feedback': { kind: 'none', reason: 'Browser-side controls over the message-feedback sidecar; ratings and notes never enter the Session log, model context, or telemetry.' },
|
||||
'packages/client/ui-tool': { kind: 'none', reason: 'Browser-side Tool presentation layer; renders logged calls without changing model context.' },
|
||||
'packages/client/ui-deliverables': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-task': { kind: 'none', reason: 'Browser-side read-only projection of ctx.tasks records; dsh-tool-tasks owns the model-facing behavior.' },
|
||||
@@ -85,10 +86,13 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/client/ui-question': { kind: 'indirect', reason: 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' },
|
||||
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-workspace': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-directory-picker': { kind: 'none', reason: 'Browser-side directory-browsing surface; registers nothing model-facing.' },
|
||||
'packages/client/ui-directory-picker-native': { kind: 'none', reason: 'Browser-side surface driving the host OS chooser; registers nothing model-facing.' },
|
||||
'packages/client/ui-theme': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-settings': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-settings-general': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-models': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/ui-plugins': { kind: 'none', reason: 'Browser-side inventory projection; registers nothing model-facing.' },
|
||||
'packages/client/locale': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
|
||||
'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
|
||||
@@ -103,6 +107,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/host/directory-picker-native': { kind: 'none', reason: 'The GUI-host picking backend registers nothing model-facing.' },
|
||||
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers nothing model-facing.' },
|
||||
'packages/host/frontend-static': { kind: 'none', reason: 'The SPA dist server answers browser asset requests and registers nothing model-facing.' },
|
||||
'packages/host/plugin-inventory': { kind: 'none', reason: 'Host-side read-only Loader projection; registers nothing model-facing.' },
|
||||
'packages/bundle/base': { kind: 'indirect', reason: 'The bundle is a patch-list carrier; each inserted row\'s package owns its model-facing behavior.' },
|
||||
'packages/bundle/headless': { kind: 'none', reason: 'The one-shot runner submits the task as an ordinary user message; prompts and tools belong to the composed base and headless bundles.' },
|
||||
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },
|
||||
@@ -118,6 +123,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/sdk/protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own model-facing behavior.' },
|
||||
'packages/session/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers nothing model-facing.' },
|
||||
'packages/session/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers nothing model-facing.' },
|
||||
'packages/session/session-stats': { kind: 'none', reason: 'The sessionStats unit folds already-logged step boundaries into a client-facing read model and registers nothing model-facing.' },
|
||||
'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers nothing model-facing.' },
|
||||
'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers nothing model-facing.' },
|
||||
'packages/settings/settings': { kind: 'indirect', reason: 'The seam stores and resolves user settings; consumer plugins own any model-facing content fed by a value.' },
|
||||
@@ -127,7 +133,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers nothing model-facing.' },
|
||||
'packages/session/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers nothing model-facing.' },
|
||||
'packages/session/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers nothing model-facing.' },
|
||||
'packages/session/user-id': { kind: 'none', reason: 'The shared identifier appears only in telemetry metadata and a direct human command response; it registers nothing model-facing.' },
|
||||
'packages/session/user-id': { kind: 'none', reason: 'The shared identifier reaches DeepSeek only as model-hidden HTTP metadata; it registers nothing model-facing.' },
|
||||
'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' },
|
||||
'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' },
|
||||
'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
translationPairPaths,
|
||||
} from './translation-pairing-record.ts'
|
||||
import {
|
||||
languageSwitcherTargets,
|
||||
linksTo,
|
||||
parseTranslationMarkdown,
|
||||
parseTranslationPairingCliArgs,
|
||||
@@ -252,15 +253,17 @@ for (const source of [...pairAnchors].sort()) {
|
||||
|
||||
const sourceTree = parseTranslationMarkdown(sourceContent.toString('utf8'))
|
||||
const zhTree = parseTranslationMarkdown(zhContent.toString('utf8'))
|
||||
if (!linksTo(zhTree, basename(source))) {
|
||||
const sourceSwitcherTargets = languageSwitcherTargets(source)
|
||||
const zhSwitcherTargets = languageSwitcherTargets(zh)
|
||||
if (!linksTo(zhTree, sourceSwitcherTargets)) {
|
||||
errors.push(`${zh}: missing language switcher — no link to ${basename(source)}`)
|
||||
}
|
||||
if (requiresSourceLanguageSwitcher(source) && !linksTo(sourceTree, basename(zh))) {
|
||||
if (requiresSourceLanguageSwitcher(source) && !linksTo(sourceTree, zhSwitcherTargets)) {
|
||||
errors.push(`${source}: missing language switcher — no link back to ${basename(zh)}`)
|
||||
}
|
||||
for (const divergence of translationStructureDiff(
|
||||
translationStructureSignature(sourceTree, basename(zh)),
|
||||
translationStructureSignature(zhTree, basename(source)),
|
||||
translationStructureSignature(sourceTree, zhSwitcherTargets),
|
||||
translationStructureSignature(zhTree, sourceSwitcherTargets),
|
||||
)) {
|
||||
errors.push(`${source} ↔ ${zh}: ${divergence}`)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user