Преглед на файлове

Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2300

# Conflicts:
#	python/sdk-runtime/package.json
_Kerman преди 2 седмици
родител
ревизия
18b0edb664
променени са 37 файла, в които са добавени 1025 реда и са изтрити 138 реда
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
  2. 3 1
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
  3. 3 1
      .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
  4. 12 7
      .github/workflows/build-exe-for-python-sdk.yml
  5. 7 2
      .github/workflows/python-release.yml
  6. 2 1
      .gitlab-ci.yml
  7. 2 2
      packages/fs/tool-fs-search/README.i18n.yaml
  8. 3 3
      packages/fs/tool-fs-search/README.md
  9. 3 3
      packages/fs/tool-fs-search/README.zh.md
  10. 11 7
      packages/fs/tool-fs-search/src/search-core.ts
  11. 52 0
      packages/fs/tool-fs-search/tests/rg-sidecar.spec.ts
  12. 2 2
      packages/sdk/server/README.i18n.yaml
  13. 1 1
      packages/sdk/server/README.md
  14. 1 1
      packages/sdk/server/README.zh.md
  15. 6 0
      packages/sdk/server/src/index.ts
  16. 59 1
      packages/sdk/server/tests/plugin-apply.spec.ts
  17. 21 0
      pnpm-lock.yaml
  18. 2 2
      python/development.i18n.yaml
  19. 10 2
      python/development.md
  20. 10 2
      python/development.zh.md
  21. 2 2
      python/sdk-runtime/README.i18n.yaml
  22. 5 3
      python/sdk-runtime/README.md
  23. 5 3
      python/sdk-runtime/README.zh.md
  24. 1 1
      python/sdk-runtime/hatch_build.py
  25. 7 0
      python/sdk-runtime/package.json
  26. 13 6
      python/sdk-runtime/src/deepseek_harness_runtime/__init__.py
  27. 4 0
      python/sdk/tests/test_release_version.py
  28. 17 1
      python/sdk/tests/test_runtime_resolution.py
  29. 41 0
      python/sdk/tests/test_smoke_model.py
  30. 28 3
      scripts/build-exe-for-python-sdk.ts
  31. 2 1
      scripts/build-python-release.py
  32. 12 2
      scripts/ci-workflow.spec.ts
  33. 54 0
      scripts/cordis-yaml.ts
  34. 272 2
      scripts/smoke-python-runtime.py
  35. 7 25
      scripts/verify-cordis-config.ts
  36. 165 0
      scripts/verify-runtime-closure.spec.ts
  37. 178 49
      scripts/verify-runtime-closure.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
-2026-07-10-single-file-executable-sdk-runtime-distribution.md: fa2f86893b730aa1ba020bd568d268ec8d9d6239
-2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 509bec18edb9923dd4d60d4ecf30d4fbcd9cc6d5
+2026-07-10-single-file-executable-sdk-runtime-distribution.md: 40433d99e5d1aa569c3fdf094a280d3de62ad588
+2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 54030fa4b0742fbc282bc327b0ca22747e6a20bd

Файловите разлики са ограничени, защото са твърде много
+ 3 - 1
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md


Файловите разлики са ограничени, защото са твърде много
+ 3 - 1
.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md


+ 12 - 7
.github/workflows/build-exe-for-python-sdk.yml

@@ -261,17 +261,20 @@ jobs:
           name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
           path: dist-python
 
-      - name: Install only the SDK into a clean venv and run zero-config
+      - name: Install local SDK and runtime wheels into a clean venv
         env:
-          VERSION: ${{ needs.plan.outputs.version }}
+          RUNTIME_WHEEL: ${{ steps.runtime.outputs.wheel }}
+          SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
         run: |
           set -euo pipefail
           python -m venv "$RUNNER_TEMP/dsh-sdk-smoke"
           "$RUNNER_TEMP/dsh-sdk-smoke/bin/python" -m pip install \
-            --find-links dist-python \
-            deepseek-harness-sdk=="$VERSION"
+            "dist-python/$SDK_WHEEL" \
+            "dist-python/$RUNTIME_WHEEL"
           "$RUNNER_TEMP/dsh-sdk-smoke/bin/python" scripts/smoke-python-runtime.py \
             --scenario sdk-default
+          "$RUNNER_TEMP/dsh-sdk-smoke/bin/python" scripts/smoke-python-runtime.py \
+            --scenario sdk-mcp
 
       - name: Check Linux GLIBC requirements
         if: runner.os == 'Linux'
@@ -297,7 +300,8 @@ jobs:
         if: runner.os == 'Linux'
         env:
           RUNNER_ARCH: ${{ runner.arch }}
-          VERSION: ${{ needs.plan.outputs.version }}
+          RUNTIME_WHEEL: ${{ steps.runtime.outputs.wheel }}
+          SDK_WHEEL: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl
         run: |
           set -euo pipefail
           case "$RUNNER_ARCH" in
@@ -305,10 +309,11 @@ jobs:
             ARM64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
             *) echo "::error::Unsupported Linux runner architecture $RUNNER_ARCH"; exit 1 ;;
           esac
-          docker run --rm -e VERSION -e DSH_TELEMETRY_DISABLED -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c '
+          docker run --rm -e RUNTIME_WHEEL -e SDK_WHEEL -e DSH_TELEMETRY_DISABLED -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c '
             /opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk
-            /tmp/dsh-sdk/bin/python -m pip install --find-links /work/dist-python deepseek-harness-sdk=="$VERSION"
+            /tmp/dsh-sdk/bin/python -m pip install "/work/dist-python/$SDK_WHEEL" "/work/dist-python/$RUNTIME_WHEEL"
             /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default
+            /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-mcp
           '
 
       - uses: actions/upload-artifact@v7

+ 7 - 2
.github/workflows/python-release.yml

@@ -68,10 +68,15 @@ jobs:
           print(f"version={release['pep440_version'](repository_version)}")
           PY
 
-      - name: Install and run the published entry path
+      - name: Install local release wheels and run the public entry path
+        env:
+          VERSION: ${{ steps.compatibility-version.outputs.version }}
         run: |
-          python -m pip install --find-links dist "deepseek-harness-sdk==${{ steps.compatibility-version.outputs.version }}"
+          python -m pip install \
+            "dist/deepseek_harness_sdk-$VERSION-py3-none-any.whl" \
+            "dist/deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl"
           python scripts/smoke-python-runtime.py --scenario sdk-default
+          python scripts/smoke-python-runtime.py --scenario sdk-mcp
 
   validate:
     name: Validate release candidate

+ 2 - 1
.gitlab-ci.yml

@@ -45,6 +45,7 @@ sdk-wheel:
     - python -m venv .wheel-smoke
     - .wheel-smoke/bin/python -m pip install --find-links "release/$PLATFORM" --find-links release/sdk deepseek-harness-sdk=="$DSH_WHEEL_VERSION"
     - .wheel-smoke/bin/python scripts/smoke-python-runtime.py --scenario sdk-default
+    - .wheel-smoke/bin/python scripts/smoke-python-runtime.py --scenario sdk-mcp
     - |
       if [ "${PLATFORM#linux-}" != "$PLATFORM" ]; then
         readelf --version-info "$EXE" > glibc-versions.txt
@@ -56,7 +57,7 @@ sdk-wheel:
           linux-arm64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
           *) echo "Unsupported Linux platform $PLATFORM"; exit 1 ;;
         esac
-        docker run --rm -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c "/opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk && /tmp/dsh-sdk/bin/python -m pip install --find-links /work/release/$PLATFORM --find-links /work/release/sdk deepseek-harness-sdk==$DSH_WHEEL_VERSION && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default"
+        docker run --rm -v "$PWD:/work" -w /work "$image" bash -euxo pipefail -c "/opt/python/cp310-cp310/bin/python -m venv /tmp/dsh-sdk && /tmp/dsh-sdk/bin/python -m pip install --find-links /work/release/$PLATFORM --find-links /work/release/sdk deepseek-harness-sdk==$DSH_WHEEL_VERSION && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-default && /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-mcp"
       fi
     - |
       if [ "$PLATFORM" = macos-arm64 ]; then

+ 2 - 2
packages/fs/tool-fs-search/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/fs/tool-fs-search/README.md
-README.md: 83290df98260e977a8cd3ea808f491ea63e75857
-README.zh.md: b33fe002fc4ccc2bfe5fefec342f2044e3511078
+README.md: 84a3adc31f9c1580b90c038b0902e88b050f0340
+README.zh.md: 06ee6c7c1c1296c7c23b2c65cf244c1449def313

+ 3 - 3
packages/fs/tool-fs-search/README.md

@@ -2,7 +2,7 @@
 
 English | [中文](README.zh.md)
 
-The **model-facing filesystem discovery tools**—`glob`, `grep`—are backed by the **packaged ripgrep binary** (`@vscode/ripgrep`), not by `ctx.fs` provider methods and not by a system `rg` install. Registration is unconditional: the binary ships inside the npm dependency, so there is no load-time availability probe. Each call spawns the binary through the `ctx.subprocess` seam with a fixed argv vector (`--no-config` prepended so a host `RIPGREP_CONFIG_PATH` cannot inject a `--pre` preprocessor into the unconfined spawn; model-controlled values are plain argv elements — no shell layer exists, so no quoting applies), parses the raw `rg` output, and returns a workdir-relative canonical value. The package injects `tools`, `systemPrompt`, and `subprocess`—deliberately **not** `fs`; `ctx.spillStore` is read opportunistically with `ctx.get()` because formatted-result spill is optional.
+The **model-facing filesystem discovery tools**—`glob`, `grep`—are backed by a packaged ripgrep binary, not by `ctx.fs` provider methods and not by a system `rg` install. Ordinary Node deployments resolve the platform binary from `@vscode/ripgrep`; a pkg single-file runtime resolves the executable's co-located `-rg` sidecar and falls back to the dependency binary when that sidecar is absent. Registration is unconditional because both carriers package ripgrep, so there is no load-time availability probe. Each call spawns the resolved binary through the `ctx.subprocess` seam with a fixed argv vector (`--no-config` prepended so a host `RIPGREP_CONFIG_PATH` cannot inject a `--pre` preprocessor into the unconfined spawn; model-controlled values are plain argv elements — no shell layer exists, so no quoting applies), parses the raw `rg` output, and returns a workdir-relative canonical value. The package injects `tools`, `systemPrompt`, and `subprocess`—deliberately **not** `fs`; `ctx.spillStore` is read opportunistically with `ctx.get()` because formatted-result spill is optional.
 
 ```ts ignore-check
 // A deployment chooses how over-cap glob pages are selected.
@@ -16,7 +16,7 @@ Why spawn-backed: local workspace discovery is naturally a process-backed `rg` w
 
 ## Deployment requirement: no host rg, co-located workdir/filesystem
 
-The binary ships with the package on every supported platform (macOS/Linux/Windows, x64/arm64), so no host `rg` install is required and the tools register on every deployment. Returned paths are displayed relative to the resolved workdir (the calling agent's session cwd when present, else `process.cwd()`) and are follow-up-readable with `read` only when that workdir and the filesystem root are the same workspace. That co-location requirement carries no runtime cross-service validation; remote or virtual filesystem search waits for a shared workspace contract or a provider-specific search backend.
+Node deployments receive the `@vscode/ripgrep` platform package on supported macOS, Linux, and Windows x64/arm64 targets. Python SDK Linux and macOS wheels copy the target-native binary beside the single-file runtime as `<runtime>-rg`; `deepseek_harness_runtime.bundled_runtime_path()` rejects an incomplete wheel before launch. No carrier requires a host `rg` install. Returned paths are displayed relative to the resolved workdir (the calling agent's session cwd when present, else `process.cwd()`) and are follow-up-readable with `read` only when that workdir and the filesystem root are the same workspace. That co-location requirement carries no runtime cross-service validation; remote or virtual filesystem search waits for a shared workspace contract or a provider-specific search backend.
 
 ## Config
 
@@ -129,6 +129,6 @@ Append-only; newly visible content follows the reusable request prefix and does
 ## Known Limitations and Deferred Work
 
 - **Search and file access have no shared-workspace proof** — returned paths are follow-up-readable only when the workdir and filesystem root denote the same workspace; the package performs no runtime cross-service validation.
-- **The packaged binary is fixed at dependency version** — `@vscode/ripgrep` covers the platforms it ships (macOS/Linux/Windows, x64/arm64); an unsupported platform or a corrupted install fails calls with `SEARCH_FAILED`. Remote or virtual filesystems need a co-located workspace or another search consumer.
+- **The packaged binary is fixed at dependency version** — Node deployments use the version selected by `@vscode/ripgrep`; Python single-file runtimes copy that target-native version into the required `-rg` sidecar. An unsupported platform or a corrupted installation fails with `SEARCH_FAILED`, while the Python runtime package rejects a missing sidecar before launch. Remote or virtual filesystems need a co-located workspace or another search consumer.
 - **The schemas expose one bounded page** — offset pagination, case-mode switches, alternate output modes, and provider-backed discovery remain outside this package; capped complete output requires a spill backend.
 - **Sampling, when enabled, groups by first path segment beneath the search root only** — an over-cap `glob` page balances across those top-level entries, so a result concentrated deeper (one busy directory inside an otherwise even tree) is still shown unevenly below that level; recursive balancing is deferred.

+ 3 - 3
packages/fs/tool-fs-search/README.zh.md

@@ -2,7 +2,7 @@
 
 [English](README.md) | 中文
 
-**面向模型的文件系统发现工具**(`glob`、`grep`)由 **打包的 ripgrep 二进制**(`@vscode/ripgrep`)支持,而不是由 `ctx.fs` 提供方方法或系统 `rg` 安装支持。注册是无条件的:二进制随 NPM 依赖一起交付,因此没有加载期可用性探针。每次调用都通过 `ctx.subprocess` seam 以固定 argv 向量 spawn 二进制(前缀 `--no-config`,使宿主的 `RIPGREP_CONFIG_PATH` 无法向不受约束的 spawn 注入 `--pre` 预处理器;模型控制的值是普通 argv 元素——不存在 shell 层,因此不涉及 shell 引号处理),解析原始 `rg` 输出,并返回相对于工作目录的规范值。本包注入 `tools`、`systemPrompt` 和 `subprocess`,有意**不**注入 `fs`;格式化结果 spill 为可选功能,因此机会性读取 `ctx.spillStore`,调用方式为 `ctx.get()`。
+**面向模型的文件系统发现工具**(`glob`、`grep`)由打包的 ripgrep 二进制支持,而不是由 `ctx.fs` 提供方方法或系统 `rg` 安装支持。普通 Node 部署从 `@vscode/ripgrep` 解析平台二进制;pkg 单文件运行时解析与可执行程序共置的 `-rg` 伴随文件,伴随文件缺失时回退到依赖中的二进制。两种载体均打包 ripgrep,因此注册是无条件的,没有加载期可用性探针。每次调用都通过 `ctx.subprocess` seam 以固定 argv 向量 spawn 解析出的二进制(前缀 `--no-config`,使宿主的 `RIPGREP_CONFIG_PATH` 无法向不受约束的 spawn 注入 `--pre` 预处理器;模型控制的值是普通 argv 元素——不存在 shell 层,因此不涉及 shell 引号处理),解析原始 `rg` 输出,并返回相对于工作目录的规范值。本包注入 `tools`、`systemPrompt` 和 `subprocess`,有意**不**注入 `fs`;格式化结果 spill 为可选功能,因此机会性读取 `ctx.spillStore`,调用方式为 `ctx.get()`。
 
 ```ts ignore-check
 // A deployment chooses how over-cap glob pages are selected.
@@ -16,7 +16,7 @@ await ctx.plugin(LocalSpillStore)                           // @deepseek-ai/dsh-
 
 ## 部署要求:无需宿主 rg,但工作目录与文件系统需共置
 
-二进制随包交付,覆盖所有受支持平台(macOS/Linux/Windows,x64/arm64),因此无需宿主 `rg` 安装,工具在每个部署上都注册。返回路径会相对于解析后的工作目录显示(调用方 agent(智能体)有会话 cwd 时使用该 cwd,否则使用 `process.cwd()`);只有该工作目录与文件系统根目录是同一工作区时,才能用 `read` 继续读取。这项共置要求不附带运行时跨服务校验;远程或虚拟文件系统搜索需等待共享工作区约定或特定提供方的搜索后端。
+Node 部署在受支持的 macOS、Linux 与 Windows x64/arm64 目标上获得 `@vscode/ripgrep` 平台包。Python SDK 的 Linux 与 macOS wheel 将目标原生二进制复制到单文件运行时旁,命名为 `<runtime>-rg`;`deepseek_harness_runtime.bundled_runtime_path()` 会在启动前拒绝不完整的 wheel。两种载体均不要求宿主安装 `rg`。返回路径会相对于解析后的工作目录显示(调用方 agent(智能体)有会话 cwd 时使用该 cwd,否则使用 `process.cwd()`);只有该工作目录与文件系统根目录是同一工作区时,才能用 `read` 继续读取。这项共置要求不附带运行时跨服务校验;远程或虚拟文件系统搜索需等待共享工作区约定或特定提供方的搜索后端。
 
 ## 配置
 
@@ -129,6 +129,6 @@ glob 描述声明了配置的超过上限排序方式。生成的 [`glob` 和 `g
 ## 已知限制与暂缓事项
 
 - **搜索与文件访问没有共享工作区证明**——只有当工作目录与文件系统根目录指向同一工作区时,返回路径才可继续读取;本包不执行运行时跨服务校验。
-- **打包二进制固定在依赖版本上**——`@vscode/ripgrep` 覆盖其随附的平台(macOS/Linux/Windows,x64/arm64);不支持的平台或损坏的安装会以 `SEARCH_FAILED` 使调用失败。远程或虚拟文件系统需要共置的工作区或另一个搜索消费方。
+- **打包二进制固定在依赖版本上**——Node 部署使用 `@vscode/ripgrep` 选择的版本;Python 单文件运行时将对应目标的原生版本复制为必需的 `-rg` 伴随文件。不支持的平台或损坏的安装会以 `SEARCH_FAILED` 使调用失败,Python 运行时包则会在启动前拒绝缺少伴随文件的安装。远程或虚拟文件系统需要共置的工作区或另一个搜索消费方。
 - **schema 只暴露一个有界页面**——偏移分页、大小写开关、替代输出模式与提供方支撑的发现仍不在本包范围内;达到上限的完整输出需要 spill 后端。
 - **启用采样时仅按搜索根正下方的第一段路径分组**——超过上限的 `glob` 页面在这些顶层条目之间平衡,因此集中在更深处的结果(一棵均匀树里某个繁忙目录)在该层级之下仍会呈现不均;递归平衡被延期。

+ 11 - 7
packages/fs/tool-fs-search/src/search-core.ts

@@ -19,6 +19,7 @@
  * @module @deepseek-ai/dsh-tool-fs-search/search-core
  */
 
+import { existsSync } from 'node:fs'
 import { isAbsolute, relative, sep } from 'node:path'
 import type { Context } from '@deepseek-ai/cordis'
 import { HarnessError } from '@deepseek-ai/dsh-llm'
@@ -158,18 +159,21 @@ let rgPathPromise: Promise<string> | undefined
 /**
  * The packaged ripgrep binary path, resolved lazily once per process.
  *
- * `@vscode/ripgrep` resolves its platform package (`@vscode/ripgrep-<platform>
- * -<arch>`) at module evaluation, so a static import would turn a missing or
- * corrupt platform package (`pnpm install --omit=optional`, partial install)
- * into a failure of the whole Loader composition. Resolving at the call
- * boundary keeps that failure at the first search call as `SEARCH_FAILED` —
- * the package's documented no-load-time-probe contract.
+ * A single-file runtime uses the executable's `-rg` sidecar because a native
+ * helper cannot be spawned from pkg's virtual filesystem. Node-mode builds
+ * fall back to the platform package selected by `@vscode/ripgrep`. Resolving
+ * at the call boundary keeps a missing or corrupt binary at the first search
+ * call as `SEARCH_FAILED`, rather than failing the Loader composition.
  *
  * @returns the packaged binary's absolute path; the memoized promise rejects
  *   when the platform package cannot be resolved.
  */
 export function resolveRgPath(): Promise<string> {
-  rgPathPromise ??= import('@vscode/ripgrep').then(module => module.rgPath)
+  rgPathPromise ??= Promise.resolve().then(async () => {
+    const executableSidecar = `${process.execPath}-rg`
+    if ('pkg' in process && existsSync(executableSidecar)) return executableSidecar
+    return (await import('@vscode/ripgrep')).rgPath
+  })
   return rgPathPromise
 }
 

+ 52 - 0
packages/fs/tool-fs-search/tests/rg-sidecar.spec.ts

@@ -0,0 +1,52 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+const { dependencyRgPath, existsSync } = vi.hoisted(() => ({
+  dependencyRgPath: '/node_modules/@vscode/ripgrep/bin/rg',
+  existsSync: vi.fn(),
+}))
+
+vi.mock('node:fs', async (importOriginal) => {
+  const actual = await importOriginal<typeof import('node:fs')>()
+  return { ...actual, existsSync }
+})
+
+vi.mock('@vscode/ripgrep', () => ({ rgPath: dependencyRgPath }))
+
+beforeEach(() => {
+  vi.resetModules()
+  existsSync.mockReset()
+  Reflect.deleteProperty(process, 'pkg')
+})
+
+afterEach(() => {
+  Reflect.deleteProperty(process, 'pkg')
+})
+
+describe('ripgrep resolution', () => {
+  it('uses the native sidecar beside the current executable', async () => {
+    Reflect.defineProperty(process, 'pkg', { configurable: true, value: {} })
+    existsSync.mockReturnValue(true)
+    const sidecar = `${process.execPath}-rg`
+    const { resolveRgPath } = await import('@deepseek-ai/dsh-tool-fs-search')
+
+    await expect(resolveRgPath()).resolves.toBe(sidecar)
+    expect(existsSync).toHaveBeenCalledWith(sidecar)
+  })
+
+  it('uses the dependency binary in an ordinary Node process', async () => {
+    existsSync.mockReturnValue(true)
+    const { resolveRgPath } = await import('@deepseek-ai/dsh-tool-fs-search')
+
+    await expect(resolveRgPath()).resolves.toBe(dependencyRgPath)
+    expect(existsSync).not.toHaveBeenCalled()
+  })
+
+  it('uses the dependency binary when a packaged runtime has no sidecar', async () => {
+    Reflect.defineProperty(process, 'pkg', { configurable: true, value: {} })
+    existsSync.mockReturnValue(false)
+    const { resolveRgPath } = await import('@deepseek-ai/dsh-tool-fs-search')
+
+    await expect(resolveRgPath()).resolves.toBe(dependencyRgPath)
+    expect(existsSync).toHaveBeenCalledWith(`${process.execPath}-rg`)
+  })
+})

+ 2 - 2
packages/sdk/server/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/sdk/server/README.md
-README.md: 5377b4fcf425cc5e10497e9d40fdddc075d52a10
-README.zh.md: dcca65a7175bb2774460bf265d98e41439df6a01
+README.md: 29ad5840b9d70c9c22ecd387730ba21ce89cbe07
+README.zh.md: f5afb255c83fa62fa4c2891cf725ffbf4c93e77d

+ 1 - 1
packages/sdk/server/README.md

@@ -22,7 +22,7 @@ The plugin answers `shutdown`, flushes the response, disposes the root context s
 
 ## Wire notes
 
-`initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. An optional positive `initialize.maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants; invalid values reject initialization, while omission sends no SDK cap and allows the selected adapter or provider route default to apply. `session/prompt` queues one identified user message and immediately returns `{ messageId }`. The server streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`; it does not assign an assistant message or `turn/end` to that prompt. Independent requests may enqueue more work on the same session. Persistence roots and persona come from `cordis.yml`.
+`initialize` is the runtime-readiness boundary: when the server is mounted by a Loader composition, it waits for the current plugin tree to settle before replying, so async sibling capabilities such as initial MCP tool discovery are visible to the first prompt. Hand-built contexts without Loader remain immediately usable. `initialize.serverInfo.name` is the wire-stable `deepseek-harness-sdk-runtime`. An optional positive `initialize.maxTokens` becomes the request output cap of each SDK-created agent and its in-process descendants; invalid values reject initialization, while omission sends no SDK cap and allows the selected adapter or provider route default to apply. `session/prompt` queues one identified user message and immediately returns `{ messageId }`. The server streams every durable fact as `session.event` and every whole-agent lifecycle transition as `session.status`; it does not assign an assistant message or `turn/end` to that prompt. Independent requests may enqueue more work on the same session. Persistence roots and persona come from `cordis.yml`.
 
 ## Model Experience
 

+ 1 - 1
packages/sdk/server/README.zh.md

@@ -22,7 +22,7 @@ Stdout 只承载 JSON-RPC 帧。部署不得组合 stdout logger;诊断应写
 
 ## 协议说明
 
-`initialize.serverInfo.name` 的协议稳定值为 `deepseek-harness-sdk-runtime`。可选的正整数 `initialize.maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限;非法值会使初始化失败,省略时则不发送 SDK 上限,并应用所选适配器或提供方路由的默认值。`session/prompt` 将一条带标识的用户消息排入队列,并立即返回 `{ messageId }`。服务器将每个持久事实作为 `session.event` 流式发出,并将整个 agent 生命周期的每次状态转换作为 `session.status` 发出;它不会把某条助手消息或 `turn/end` 归属于该提示词。同一会话上的独立请求可以继续排入更多工作。持久化根目录和 persona 由 `cordis.yml` 提供。
+`initialize` 是运行时就绪边界:服务器由 Loader 组合挂载时,会等待当前插件树完成所有加载任务后再响应,因此首次提示词能够看到 MCP 初始工具发现等异步同级能力。没有 Loader 的手工组装上下文仍可立即使用。`initialize.serverInfo.name` 的协议稳定值为 `deepseek-harness-sdk-runtime`。可选的正整数 `initialize.maxTokens` 会成为每个 SDK 创建的 agent 及其进程内后代的请求输出上限;非法值会使初始化失败,省略时则不发送 SDK 上限,并应用所选适配器或提供方路由的默认值。`session/prompt` 将一条带标识的用户消息排入队列,并立即返回 `{ messageId }`。服务器将每个持久事实作为 `session.event` 流式发出,并将整个 agent 生命周期的每次状态转换作为 `session.status` 发出;它不会把某条助手消息或 `turn/end` 归属于该提示词。同一会话上的独立请求可以继续排入更多工作。持久化根目录和 persona 由 `cordis.yml` 提供。
 
 ## 模型体验
 

+ 6 - 0
packages/sdk/server/src/index.ts

@@ -74,6 +74,12 @@ export function apply(ctx: Context, config: JsonRpcConfig): void {
   }
 
   transport.onRequest(async (method, params) => {
+    // `initialize` is the SDK's readiness boundary. This plugin can activate
+    // before async sibling Loader entries (for example an MCP client's initial
+    // tool discovery), so do not advertise a ready runtime until the complete
+    // current tree has settled. A hand-built context without Loader remains
+    // immediately usable.
+    if (method === 'initialize') await ctx.get('loader')?.await()
     const result = await server.handleRequest(method, params)
     if (method === 'shutdown') {
       // Run after the handler result is written; the task then flushes, disposes, and exits.

+ 59 - 1
packages/sdk/server/tests/plugin-apply.spec.ts

@@ -6,6 +6,7 @@ import { tmpdir } from 'node:os'
 import { PassThrough, Writable } from 'node:stream'
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import { Context } from '@deepseek-ai/cordis'
+import Loader from '@deepseek-ai/cordis-plugin-loader'
 import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
 import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl'
 import * as jsonrpc from '../src/index.ts'
@@ -57,12 +58,17 @@ async function settle(): Promise<void> {
 /** Mount the real plugin on a minimal harness with in-memory stdio and exit. */
 async function mountPlugin(
   storageDir: string,
-  options: { writeDelayMs?: number; failFlush?: boolean } = {},
+  options: {
+    writeDelayMs?: number
+    failFlush?: boolean
+    beforeServer?: (ctx: Context) => Promise<void> | void
+  } = {},
 ): Promise<ApplyHarness> {
   const ctx = new Context()
   await ctx.plugin(agentCore, { workspaceContext: false })
   await ctx.plugin(JsonlSessionPersistence, { root: storageDir })
   await new Promise(resolve => setTimeout(resolve, 50))
+  await options.beforeServer?.(ctx)
 
   const input = new PassThrough()
   const events: WireEvent[] = []
@@ -170,6 +176,58 @@ describe('dsh-sdk-jsonrpc-server plugin apply', () => {
     }
   })
 
+  it('does not answer initialize until async sibling Loader entries settle', async () => {
+    const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-apply-readiness-'))
+    vi.stubEnv('DEEPSEEK_API_KEY', 'test-key')
+    let markStarted!: () => void
+    let release!: () => void
+    const started = new Promise<void>((resolve) => { markStarted = resolve })
+    const ready = new Promise<void>((resolve) => { release = resolve })
+    let delayedEntry: Promise<string> | undefined
+    const harness = await mountPlugin(storageDir, {
+      beforeServer: async (ctx) => {
+        await ctx.plugin(Loader)
+        ctx.loader.builtins['delayed-readiness'] = {
+          async apply() {
+            markStarted()
+            await ready
+          },
+        }
+        delayedEntry = ctx.loader.create({ name: 'cordis:delayed-readiness' })
+        await started
+      },
+    })
+    try {
+      const initialize = {
+        jsonrpc: '2.0',
+        id: 'init-delayed',
+        method: 'initialize',
+        params: { cwd: storageDir, provider: 'deepseek-official', model: 'apply-model' },
+      }
+      const probe = { jsonrpc: '2.0', id: 'probe-during-delay', method: 'nope/unknown' }
+      harness.sendRaw(`${JSON.stringify(initialize)}\n${JSON.stringify(probe)}\n`)
+
+      // The transport processes independent requests concurrently. Receiving
+      // this later probe proves the preceding initialize handler has reached
+      // its Loader wait, without relying on a scheduler delay.
+      await harness.waitForFrame(frame => frame.id === 'probe-during-delay', 'probe while initialize waits')
+      expect(harness.frames().some(frame => frame.id === 'init-delayed')).toBe(false)
+
+      release()
+      await delayedEntry
+      const response = await harness.waitForFrame(frame => frame.id === 'init-delayed', 'initialize response after Loader settlement')
+      expect(response).toMatchObject({
+        id: 'init-delayed',
+        result: { serverInfo: { name: 'deepseek-harness-sdk-runtime' } },
+      })
+    } finally {
+      release()
+      await Promise.allSettled(delayedEntry === undefined ? [] : [delayedEntry])
+      await harness.dispose()
+      await rm(storageDir, { recursive: true, force: true })
+    }
+  })
+
   it('drives a session/prompt turn end-to-end and forwards session notifications as output frames', async () => {
     const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-apply-prompt-'))
     const llmServer = await mockCompletionServer()

+ 21 - 0
pnpm-lock.yaml

@@ -8514,6 +8514,9 @@ importers:
       '@deepseek-ai/dsh-agent-spine-demo':
         specifier: workspace:^
         version: link:../../packages/examples/agent-spine-demo
+      '@deepseek-ai/dsh-agent-tool-presentation':
+        specifier: workspace:^
+        version: link:../../packages/core/agent-tool-presentation
       '@deepseek-ai/dsh-anonymous-user-id':
         specifier: workspace:^
         version: link:../../packages/identity/anonymous-user-id
@@ -8535,6 +8538,9 @@ importers:
       '@deepseek-ai/dsh-code-runtime-worker-thread':
         specifier: workspace:^
         version: link:../../packages/code-runtime/code-runtime-worker-thread
+      '@deepseek-ai/dsh-command-compact':
+        specifier: workspace:^
+        version: link:../../packages/compaction/command-compact
       '@deepseek-ai/dsh-command-goal':
         specifier: workspace:^
         version: link:../../packages/goal/command-goal
@@ -8610,12 +8616,18 @@ importers:
       '@deepseek-ai/dsh-llm-retry':
         specifier: workspace:^
         version: link:../../packages/llm/llm-retry
+      '@deepseek-ai/dsh-mcp-client':
+        specifier: workspace:^
+        version: link:../../packages/mcp/mcp-client
       '@deepseek-ai/dsh-output-retention':
         specifier: workspace:^
         version: link:../../packages/util/output-retention
       '@deepseek-ai/dsh-permission-presets':
         specifier: workspace:^
         version: link:../../packages/interaction/permission-presets
+      '@deepseek-ai/dsh-persona':
+        specifier: workspace:^
+        version: link:../../packages/preset/persona
       '@deepseek-ai/dsh-plan-mode':
         specifier: workspace:^
         version: link:../../packages/plan/plan-mode
@@ -8691,6 +8703,9 @@ importers:
       '@deepseek-ai/dsh-skill-filesystem':
         specifier: workspace:^
         version: link:../../packages/skill/skill-filesystem
+      '@deepseek-ai/dsh-spill':
+        specifier: workspace:^
+        version: link:../../packages/spill/spill
       '@deepseek-ai/dsh-subagent':
         specifier: workspace:^
         version: link:../../packages/subagent/subagent
@@ -8745,12 +8760,18 @@ importers:
       '@deepseek-ai/dsh-tool-fs':
         specifier: workspace:^
         version: link:../../packages/fs/tool-fs
+      '@deepseek-ai/dsh-tool-fs-search':
+        specifier: workspace:^
+        version: link:../../packages/fs/tool-fs-search
       '@deepseek-ai/dsh-tool-goal':
         specifier: workspace:^
         version: link:../../packages/goal/tool-goal
       '@deepseek-ai/dsh-tool-jobs':
         specifier: workspace:^
         version: link:../../packages/jobs/tool-jobs
+      '@deepseek-ai/dsh-tool-ralph':
+        specifier: workspace:^
+        version: link:../../packages/workflow/tool-ralph
       '@deepseek-ai/dsh-tool-skill':
         specifier: workspace:^
         version: link:../../packages/skill/tool-skill

+ 2 - 2
python/development.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write python/development.md
-development.md: fe62a109f2643afe0b9be1ed51b86be0b9fa731f
-development.zh.md: d4ab9850d6c83dc17a740240f97cef89d61faaa0
+development.md: 617d030294dafa51aea513adb811bb5f377431c9
+development.zh.md: 2049ad12856c4107788aec168e3724965cebd5af

+ 10 - 2
python/development.md

@@ -61,10 +61,18 @@ The root `package.json` version is authoritative for both Python distributions.
 Build the pure SDK wheel once and one runtime wheel on each native platform:
 
 ```sh
-version="$(node -p "require('./package.json').version")"
+version="$(python - <<'PY'
+import runpy
+
+release = runpy.run_path("scripts/build-python-release.py")
+print(release["pep440_version"](release["repository_version"]()))
+PY
+)"
 python scripts/build-python-release.py --package sdk --output-dir dist-python
 python scripts/build-python-release.py --package runtime --platform macos-arm64 --runtime-exe dist-exe/dsh-jsonrpc-agent-pkg-macos-arm64 --output-dir dist-python
-pip install --find-links dist-python deepseek-harness-sdk=="$version"
+pip install \
+  "dist-python/deepseek_harness_sdk-$version-py3-none-any.whl" \
+  "dist-python/deepseek_harness_runtime_bin-$version-py3-none-macosx_14_0_arm64.whl"
 ```
 
 The runtime distribution is wheel-only. The release pipeline publishes three platform wheels with the pure SDK wheel: Linux x64, Linux arm64, and macOS 14 or newer on arm64. A `python-v<repository-version>` tag is accepted only when it matches the repository version; prerelease repository versions such as `0.0.1-rc.1` use their normalized PEP 440 spelling, such as `0.0.1rc1`, inside wheel filenames and metadata.

+ 10 - 2
python/development.zh.md

@@ -61,10 +61,18 @@ with DeepSeekHarness() as harness:
 纯 SDK wheel 包只需构建一次;每个原生平台分别构建一个运行时 wheel 包:
 
 ```sh
-version="$(node -p "require('./package.json').version")"
+version="$(python - <<'PY'
+import runpy
+
+release = runpy.run_path("scripts/build-python-release.py")
+print(release["pep440_version"](release["repository_version"]()))
+PY
+)"
 python scripts/build-python-release.py --package sdk --output-dir dist-python
 python scripts/build-python-release.py --package runtime --platform macos-arm64 --runtime-exe dist-exe/dsh-jsonrpc-agent-pkg-macos-arm64 --output-dir dist-python
-pip install --find-links dist-python deepseek-harness-sdk=="$version"
+pip install \
+  "dist-python/deepseek_harness_sdk-$version-py3-none-any.whl" \
+  "dist-python/deepseek_harness_runtime_bin-$version-py3-none-macosx_14_0_arm64.whl"
 ```
 
 运行时分发包仅提供 wheel 包。发布流水线会连同纯 SDK wheel 包一起发布三个平台 wheel 包:Linux x64、Linux arm64 和 macOS 14 或更高版本的 arm64。只有与仓库版本匹配时,才接受 `python-v<repository-version>` 标签;`0.0.1-rc.1` 之类的仓库预发布版本在 wheel 包文件名和元数据中使用规范化的 PEP 440 写法,例如 `0.0.1rc1`。

+ 2 - 2
python/sdk-runtime/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write python/sdk-runtime/README.md
-README.md: 592ce091f3b6c4bc151dfdee9d313b7c970c95de
-README.zh.md: 0a29281a39c885bc3c40e017bdc9475e0065605f
+README.md: 597d69a803a7cd1204fd48456f8e1ba18d9786e5
+README.zh.md: 30dc6b2a34709ac76d16133be34f3d0c87f93b8a

+ 5 - 3
python/sdk-runtime/README.md

@@ -8,19 +8,21 @@ Runtime carrier package for the Python SDK (dist `deepseek-harness-runtime-bin`,
 
 Two carriers coexist under `src/deepseek_harness_runtime/runtime/`, both injected by the repo's `scripts/build-exe-for-python-sdk.ts` build and both gitignored:
 
-- **exe (production)** — a single-file Node executable `dsh-jsonrpc-agent-pkg-<platform>-<arch>` (platform: `linux`/`macos`; arch: `x64`/`arm64`). macOS builds also ship the native `-spawn-helper` sibling that `node-pty` uses there. No Node installation is needed on the target machine. This is the only carrier that ships in wheel distributions; this package does not publish sdists.
+- **exe (production)** — a single-file Node executable `dsh-jsonrpc-agent-pkg-<platform>-<arch>` (platform: `linux`/`macos`; arch: `x64`/`arm64`) with a target-native ripgrep `-rg` sidecar. macOS builds also ship the native `-spawn-helper` sibling that `node-pty` uses there. No Node installation is needed on the target machine. This is the only carrier that ships in wheel distributions; this package does not publish sdists.
 - **node (dev-only)** — the full deploy closure under `runtime/node/` (`package.json` + `node_modules/`), executed as `node runtime/node/node_modules/@deepseek-ai/dsh-sdk-jsonrpc-demo/lib/packaged-bin.js` on a system Node >= 22.19. It is the current checkout's source build, meant for repo-local development and verification only; it is never selected automatically and is excluded from distributions.
 
 Both carriers hold the same content, defined once: the [package.json](https://github.com/deepseek-ai/deepseek-harness/blob/master/python/sdk-runtime/package.json) at this package's root is the deploy root of the single-exe pipeline — a pure dependency manifest (no code of its own) whose dependency closure IS both the plugin set compiled into the exe and the tree materialized into `runtime/node/`. Adding a plugin to the distribution means adding one dependency line there and rebuilding.
 
+The bundled plugin set includes `@deepseek-ai/dsh-mcp-client`, so an external Cordis config can connect to stdio or Streamable HTTP MCP servers and expose their tools to the model. The wheel does not bundle MCP server programs or credentials: a stdio config supplies its executable and arguments, while a Streamable HTTP config supplies its URL and headers. The bridge supports MCP tools; MCP Resources and Prompts remain unsupported.
+
 A missing exe raises `FileNotFoundError` naming both acquisition routes: build via `scripts/build-exe-for-python-sdk.ts` in a deepseek-harness checkout, or install the matching platform runtime wheel produced by the `build-exe-for-python-sdk` CI workflow. A missing dev-only node carrier names its sole route, the build script. The workflow retains wheels rather than standalone executable archives. Acquisition strategy is deliberately separate from the lookup interface, so an on-demand download can replace it later without touching callers.
 
-Each wheel contains exactly one runtime executable. The macOS wheel also contains its matching native spawn helper; a missing sidecar makes that installation incomplete and is a hard startup error, even for a selected Cordis composition that does not use PTY tools. Linux wheels contain no spawn helper because `node-pty` uses the staged `pty.node` addon directly. The fixed tags are `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, and `py3-none-macosx_14_0_arm64`; the macOS tag conservatively matches the bundled Node 24 executable's macOS 13.5 deployment target. This package's `platforms.json` owns the fixed tag and executable-name pairs used by both the repository release builder and the isolated build hook. The build hook rejects `py3-none-any`, absent or multiple runtime files, non-executable files, and unsupported platform tags. The repository root `package.json` supplies the shared version for this package and the SDK, and a `python-v<repository-version>` release tag must match it.
+Each wheel contains exactly one runtime executable and its matching ripgrep `-rg` sidecar. The macOS wheel also contains its matching native spawn helper; any missing sidecar makes that installation incomplete and is a hard startup error, even for a selected Cordis composition that does not use filesystem-search or PTY tools. Linux wheels contain no spawn helper because `node-pty` uses the staged `pty.node` addon directly. The fixed tags are `py3-none-manylinux_2_28_x86_64`, `py3-none-manylinux_2_28_aarch64`, and `py3-none-macosx_14_0_arm64`; the macOS tag conservatively matches the bundled Node 24 executable's macOS 13.5 deployment target. This package's `platforms.json` owns the fixed tag and executable-name pairs used by both the repository release builder and the isolated build hook. The build hook rejects `py3-none-any`, absent or multiple runtime executables, missing or extra sidecars, non-executable files, and unsupported platform tags. The repository root `package.json` supplies the shared version for this package and the SDK, and a `python-v<repository-version>` release tag must match it.
 
 ## Resolution API
 
 - `resolve_bundled_launch_args(mode=None) -> tuple[str, ...]` — the argv tuple that launches the bundled runtime: `(exe_path,)` in exe mode, `(node_path, bin_js_path)` in node mode. Mode selection: explicit argument > `DSH_RUNTIME_MODE` env var (`exe` | `node`) > automatic. Automatic resolution finds the production exe ONLY — the dev-only node carrier must be opted into explicitly so a production deployment can never silently ride on a source build.
-- `bundled_runtime_path() -> Path` — the platform exe path (exe carrier only; on macOS it validates that the required sibling `-spawn-helper` is also installed). The node carrier has no single-path equivalent and launches via the argv tuple above.
+- `bundled_runtime_path() -> Path` — the platform exe path (exe carrier only); it validates the required sibling `-rg` sidecar on every platform and the `-spawn-helper` sidecar on macOS. The node carrier has no single-path equivalent and launches via the argv tuple above.
 - `bundled_default_config_path() -> Path` — the checked-in default config (see below).
 - `bundled_package_dir() -> Path` — the installed package data root.
 

+ 5 - 3
python/sdk-runtime/README.zh.md

@@ -8,19 +8,21 @@ Python SDK 的运行时载体包(分发名 `deepseek-harness-runtime-bin`,
 
 两种载体并存于 `src/deepseek_harness_runtime/runtime/` 之下,均由仓库的 `scripts/build-exe-for-python-sdk.ts` 构建注入,且均被 git 忽略:
 
-- **exe(生产)**——单文件 Node 可执行程序 `dsh-jsonrpc-agent-pkg-<platform>-<arch>`(platform:`linux`/`macos`;arch:`x64`/`arm64`)。macOS 构建还会随附 `node-pty` 在该平台使用的原生 `-spawn-helper` 伴随文件。目标机器无需安装 Node。这是唯一随 wheel 包分发的载体;本包不发布 sdist。
+- **exe(生产)**——单文件 Node 可执行程序 `dsh-jsonrpc-agent-pkg-<platform>-<arch>`(platform:`linux`/`macos`;arch:`x64`/`arm64`),以及匹配目标平台的 ripgrep `-rg` 伴随文件。macOS 构建还会随附 `node-pty` 在该平台使用的原生 `-spawn-helper` 伴随文件。目标机器无需安装 Node。这是唯一随 wheel 包分发的载体;本包不发布 sdist。
 - **node(仅限开发)**——`runtime/node/` 下的完整部署闭包(`package.json` + `node_modules/`),在系统 Node >= 22.19 上以 `node runtime/node/node_modules/@deepseek-ai/dsh-sdk-jsonrpc-demo/lib/packaged-bin.js` 执行。它是当前检出的源码构建,仅用于仓库本地的开发与验证;不会被自动选中,也不进入分发物。
 
 两种载体承载相同的内容,且只定义一次:本包根目录的 [package.json](https://github.com/deepseek-ai/deepseek-harness/blob/master/python/sdk-runtime/package.json) 是 single-exe 流水线的部署根目录——一份零代码的纯依赖 manifest,其依赖闭包既是编译进 exe 的插件集,也是物化到 `runtime/node/` 的文件树。往分发物里加插件,就是在那里加一行依赖再重新构建。
 
+内置插件集合包含 `@deepseek-ai/dsh-mcp-client`,因此外部 Cordis 配置可以连接 stdio 或 Streamable HTTP MCP server,并向模型提供这些 server 的工具。wheel 包不包含 MCP server 程序或凭据:stdio 配置需要提供可执行程序及其参数,Streamable HTTP 配置需要提供 URL 和请求头。该桥接仅支持 MCP 工具,尚不支持 MCP Resources 与 Prompts。
+
 exe 缺失时抛出 `FileNotFoundError`,并写明两种获取途径:在 deepseek-harness 检出中经 `scripts/build-exe-for-python-sdk.ts` 构建,或安装 `build-exe-for-python-sdk` CI 工作流生成的对应平台运行时 wheel 包。仅限开发的 node 载体缺失时只提示构建脚本这一条途径。该工作流只保留 wheel 包,不保留独立 exe 归档。获取策略与查找接口刻意分离,之后可以换成按需下载而不改动任何调用方。
 
-每个 wheel 包只包含一个运行时可执行文件。macOS wheel 包还包含与其匹配的原生 spawn helper;缺少伴随文件意味着该安装不完整,并会在启动时硬失败,即使所选 Cordis 组合不使用 PTY 工具也是如此。Linux wheel 包不包含 spawn helper,因为 `node-pty` 直接使用暂存的 `pty.node` 原生插件。固定标签为 `py3-none-manylinux_2_28_x86_64`、`py3-none-manylinux_2_28_aarch64` 与 `py3-none-macosx_14_0_arm64`;macOS 标签保守匹配内置 Node 24 可执行文件的 macOS 13.5 部署目标。本包的 `platforms.json` 统一定义仓库发行构建器与隔离构建钩子使用的固定标签和可执行文件名。构建钩子会拒绝 `py3-none-any`、不存在运行时文件、存在多个运行时文件、文件不可执行以及不支持的平台标签。仓库根目录的 `package.json` 为本包和 SDK 提供共同版本,`python-v<repository-version>` 发布标签必须与其匹配。
+每个 wheel 包只包含一个运行时可执行文件及其匹配的 ripgrep `-rg` 伴随文件。macOS wheel 包还包含与其匹配的原生 spawn helper;缺少任一伴随文件意味着该安装不完整,并会在启动时硬失败,即使所选 Cordis 组合不使用文件系统搜索或 PTY 工具也是如此。Linux wheel 包不包含 spawn helper,因为 `node-pty` 直接使用暂存的 `pty.node` 原生插件。固定标签为 `py3-none-manylinux_2_28_x86_64`、`py3-none-manylinux_2_28_aarch64` 与 `py3-none-macosx_14_0_arm64`;macOS 标签保守匹配内置 Node 24 可执行文件的 macOS 13.5 部署目标。本包的 `platforms.json` 统一定义仓库发行构建器与隔离构建钩子使用的固定标签和可执行文件名。构建钩子会拒绝 `py3-none-any`、不存在或存在多个运行时可执行文件、缺失或多余的伴随文件、文件不可执行以及不支持的平台标签。仓库根目录的 `package.json` 为本包和 SDK 提供共同版本,`python-v<repository-version>` 发布标签必须与其匹配。
 
 ## 解析 API
 
 - `resolve_bundled_launch_args(mode=None) -> tuple[str, ...]`——启动内置运行时的 argv 元组:exe 模式下为 `(exe_path,)`,node 模式下为 `(node_path, bin_js_path)`。模式选择:显式参数 > `DSH_RUNTIME_MODE` 环境变量(`exe` | `node`)> 自动。自动解析只找生产 exe——仅限开发的 node 载体必须显式选用,从而生产部署绝不会悄悄跑在源码构建上。
-- `bundled_runtime_path() -> Path`——平台 exe 路径(仅 exe 载体,并在 macOS 上校验必要的 `-spawn-helper` 伴随文件也已安装)。node 载体没有单一路径的等价物,经由上面的 argv 元组启动。
+- `bundled_runtime_path() -> Path`——平台 exe 路径(仅 exe 载体);它会在所有平台校验必要的 `-rg` 伴随文件,并在 macOS 上额外校验 `-spawn-helper` 伴随文件。node 载体没有单一路径的等价物,经由上面的 argv 元组启动。
 - `bundled_default_config_path() -> Path`——检入的默认配置(见下文)。
 - `bundled_package_dir() -> Path`——已安装包的数据根目录。
 

+ 1 - 1
python/sdk-runtime/hatch_build.py

@@ -67,7 +67,7 @@ class RuntimeBuildHook(BuildHookInterface):
         expected_executable = matches[0][1]
         runtime_dir = Path(self.root) / "src" / "deepseek_harness_runtime" / "runtime"
         runtime_files = sorted(runtime_dir.glob("dsh-jsonrpc-agent-pkg-*") if runtime_dir.is_dir() else [])
-        expected_files = [expected_executable]
+        expected_files = [expected_executable, f"{expected_executable}-rg"]
         if "-macos-" in expected_executable:
             expected_files.append(f"{expected_executable}-spawn-helper")
         found_files = [path.name for path in runtime_files]

+ 7 - 0
python/sdk-runtime/package.json

@@ -14,6 +14,7 @@
     "@deepseek-ai/dsh-agent": "workspace:^",
     "@deepseek-ai/dsh-agent-loop": "workspace:^",
     "@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
+    "@deepseek-ai/dsh-agent-tool-presentation": "workspace:^",
     "@deepseek-ai/dsh-app-boot": "workspace:^",
     "@deepseek-ai/dsh-attachment": "workspace:^",
     "@deepseek-ai/dsh-shell": "workspace:^",
@@ -22,6 +23,7 @@
     "@deepseek-ai/dsh-brand": "workspace:^",
     "@deepseek-ai/dsh-code-runtime": "workspace:^",
     "@deepseek-ai/dsh-code-runtime-worker-thread": "workspace:^",
+    "@deepseek-ai/dsh-command-compact": "workspace:^",
     "@deepseek-ai/dsh-command-goal": "workspace:^",
     "@deepseek-ai/dsh-commands": "workspace:^",
     "@deepseek-ai/dsh-compaction": "workspace:^",
@@ -46,9 +48,11 @@
     "@deepseek-ai/dsh-llm-deepseek": "workspace:^",
     "@deepseek-ai/dsh-llm-pi-ai": "workspace:^",
     "@deepseek-ai/dsh-llm-retry": "workspace:^",
+    "@deepseek-ai/dsh-mcp-client": "workspace:^",
     "@deepseek-ai/dsh-home-paths": "workspace:^",
     "@deepseek-ai/dsh-permission-presets": "workspace:^",
     "@deepseek-ai/dsh-plan-mode": "workspace:^",
+    "@deepseek-ai/dsh-persona": "workspace:^",
     "@deepseek-ai/dsh-pwsh-local": "workspace:^",
     "@deepseek-ai/dsh-terminal": "workspace:^",
     "@deepseek-ai/dsh-terminal-bash": "workspace:^",
@@ -72,6 +76,7 @@
     "@deepseek-ai/dsh-settings": "workspace:^",
     "@deepseek-ai/dsh-skill": "workspace:^",
     "@deepseek-ai/dsh-skill-filesystem": "workspace:^",
+    "@deepseek-ai/dsh-spill": "workspace:^",
     "@deepseek-ai/dsh-subagent": "workspace:^",
     "@deepseek-ai/dsh-subagent-acp": "workspace:^",
     "@deepseek-ai/dsh-subagent-fork-in-process": "workspace:^",
@@ -90,7 +95,9 @@
     "@deepseek-ai/dsh-tool-bash-persistent": "workspace:^",
     "@deepseek-ai/dsh-tool-cordis": "workspace:^",
     "@deepseek-ai/dsh-tool-fs": "workspace:^",
+    "@deepseek-ai/dsh-tool-fs-search": "workspace:^",
     "@deepseek-ai/dsh-tool-goal": "workspace:^",
+    "@deepseek-ai/dsh-tool-ralph": "workspace:^",
     "@deepseek-ai/dsh-tool-skill": "workspace:^",
     "@deepseek-ai/dsh-tool-str-replace-editor": "workspace:^",
     "@deepseek-ai/dsh-tool-subagent": "workspace:^",

+ 13 - 6
python/sdk-runtime/src/deepseek_harness_runtime/__init__.py

@@ -5,8 +5,8 @@ Two runtime carriers coexist under ``runtime/``, both injected by the repo's
 
 - **exe (production)**: single-file Node executables named
   ``dsh-jsonrpc-agent-pkg-<platform>-<arch>`` (platform in {linux, macos}, arch in
-  {x64, arm64}); macOS also uses a sibling ``-spawn-helper``. The target machine
-  needs no Node installation.
+  {x64, arm64}) with a sibling ``-rg`` executable; macOS also uses a sibling
+  ``-spawn-helper``. The target machine needs no Node installation.
 - **node (dev-only)**: the full deploy closure under ``runtime/node/``
   (``package.json`` + ``node_modules/``), executed as ``node
   runtime/node/node_modules/@deepseek-ai/dsh-sdk-jsonrpc-demo/lib/packaged-bin.js`` on a
@@ -71,10 +71,11 @@ def bundled_runtime_path() -> Path:
     """Absolute path of the bundled single-file runtime executable for the current platform.
 
     Raises FileNotFoundError when the platform is unsupported, the executable
-    has not been placed into this package, or the required macOS spawn helper is
-    missing; the message names the acquisition routes (acquisition strategy is
-    deliberately separate from this lookup interface, so an on-demand download
-    can replace it without touching callers).
+    has not been placed into this package, the required ripgrep sidecar is
+    missing, or the required macOS spawn helper is missing; the message names
+    the acquisition routes (acquisition strategy is deliberately separate from
+    this lookup interface, so an on-demand download can replace it without
+    touching callers).
     """
     tag = _current_platform_tag()
     path = bundled_package_dir() / "runtime" / f"dsh-jsonrpc-agent-pkg-{tag}"
@@ -83,6 +84,12 @@ def bundled_runtime_path() -> Path:
             f"deepseek-harness-runtime-bin is missing the runtime executable at {path}. "
             + _EXE_ACQUISITION_HINT
         )
+    ripgrep = Path(f"{path}-rg")
+    if not ripgrep.is_file():
+        raise FileNotFoundError(
+            f"deepseek-harness-runtime-bin is missing the ripgrep sidecar at {ripgrep}. "
+            + _EXE_ACQUISITION_HINT
+        )
     if tag.startswith("macos-"):
         helper = Path(f"{path}-spawn-helper")
         if not helper.is_file():

+ 4 - 0
python/sdk/tests/test_release_version.py

@@ -92,6 +92,10 @@ def test_stage_runtime_copies_platform_payload(
     executable.write_bytes(b"runtime")
     executable.chmod(0o755)
     expected = {executable.name: b"runtime"}
+    ripgrep = Path(f"{executable}-rg")
+    ripgrep.write_bytes(b"ripgrep")
+    ripgrep.chmod(0o755)
+    expected[ripgrep.name] = b"ripgrep"
     if with_helper:
         spawn_helper = Path(f"{executable}-spawn-helper")
         spawn_helper.write_bytes(b"helper")

+ 17 - 1
python/sdk/tests/test_runtime_resolution.py

@@ -51,7 +51,10 @@ def test_runtime_requires_spawn_helper_only_on_macos(
     runtime_dir.mkdir()
     linux = runtime_dir / "dsh-jsonrpc-agent-pkg-linux-x64"
     linux.touch()
-    (runtime_dir / "dsh-jsonrpc-agent-pkg-macos-arm64").touch()
+    Path(f"{linux}-rg").touch()
+    macos = runtime_dir / "dsh-jsonrpc-agent-pkg-macos-arm64"
+    macos.touch()
+    Path(f"{macos}-rg").touch()
     monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
 
     monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "macos-arm64")
@@ -59,3 +62,16 @@ def test_runtime_requires_spawn_helper_only_on_macos(
         runtime.bundled_runtime_path()
     monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
     assert runtime.bundled_runtime_path() == linux
+
+
+def test_runtime_requires_ripgrep_sidecar(
+    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+    runtime_dir = tmp_path / "runtime"
+    runtime_dir.mkdir()
+    (runtime_dir / "dsh-jsonrpc-agent-pkg-linux-x64").touch()
+    monkeypatch.setattr(runtime, "bundled_package_dir", lambda: tmp_path)
+    monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
+
+    with pytest.raises(FileNotFoundError, match="ripgrep sidecar"):
+        runtime.bundled_runtime_path()

+ 41 - 0
python/sdk/tests/test_smoke_model.py

@@ -30,3 +30,44 @@ def test_child_prompt_precedes_runtime_context(prompt_name: str, expected: str)
         for chunk in chunks
         for choice in chunk.get("choices", [])
     )
+
+
+def test_mcp_smoke_requests_the_discovered_tool() -> None:
+    chunks = SMOKE["completion_chunks"]({
+        "messages": [{"role": "user", "content": SMOKE["MCP_PROMPT"]}],
+        "tools": [{"type": "function", "function": {"name": "mcp__fixture__add"}}],
+    })
+
+    calls = [
+        call
+        for chunk in chunks
+        for choice in chunk.get("choices", [])
+        for call in choice.get("delta", {}).get("tool_calls", [])
+    ]
+    assert calls[0]["function"] == {
+        "name": "mcp__fixture__add",
+        "arguments": '{"a": 19, "b": 23}',
+    }
+
+
+def test_mcp_smoke_accepts_the_external_server_result() -> None:
+    chunks = SMOKE["completion_chunks"]({
+        "messages": [
+            {"role": "user", "content": SMOKE["MCP_PROMPT"]},
+            {
+                "role": "assistant",
+                "tool_calls": [{
+                    "id": "mcp-add",
+                    "type": "function",
+                    "function": {"name": "mcp__fixture__add", "arguments": '{}'},
+                }],
+            },
+            {"role": "tool", "tool_call_id": "mcp-add", "content": "42"},
+        ],
+    })
+
+    assert any(
+        choice.get("delta", {}).get("content") == SMOKE["MCP_TEXT"]
+        for chunk in chunks
+        for choice in chunk.get("choices", [])
+    )

+ 28 - 3
scripts/build-exe-for-python-sdk.ts

@@ -378,7 +378,7 @@ class SingleExeBuild {
   /**
    * Package one target; SEA mode accepts one target per invocation.
    * @param target - the pkg target triple to build.
-   * @returns the executable path and, on macOS, its helper path.
+   * @returns the executable and ripgrep sidecar paths, plus the macOS spawn helper path when required.
    */
   async pack(target: Target): Promise<string[]> {
     const product = join(this.outDir, `${OUTPUT_BASENAME}-${target.platform}-${target.arch}`)
@@ -397,7 +397,8 @@ class SingleExeBuild {
     if (!this.cli.dryRun && !existsSync(product)) {
       throw new Error(`build-exe-for-python-sdk: product ${product} is missing after the pkg run; inspect ${this.outDir}.`)
     }
-    if (target.platform !== 'macos') return [product]
+    const ripgrep = await this.copyRipgrepSidecar(target, product)
+    if (target.platform !== 'macos') return [product, ripgrep]
     const spawnHelper = `${product}-spawn-helper`
     const source = join(this.staging, 'node_modules', 'node-pty', 'prebuilds', `darwin-${target.arch}`, 'spawn-helper')
     if (this.cli.dryRun) {
@@ -406,7 +407,31 @@ class SingleExeBuild {
       await copyFile(source, spawnHelper)
       await chmod(spawnHelper, 0o755)
     }
-    return [product, spawnHelper]
+    return [product, ripgrep, spawnHelper]
+  }
+
+  /** Copy the target ripgrep binary beside the executable so Node can spawn it outside pkg's virtual filesystem. */
+  private async copyRipgrepSidecar(target: Target, product: string): Promise<string> {
+    const platform = target.platform === 'macos' ? 'darwin' : target.platform
+    const source = join(
+      this.staging,
+      'node_modules',
+      '@vscode',
+      `ripgrep-${platform}-${target.arch}`,
+      'bin',
+      'rg',
+    )
+    const destination = `${product}-rg`
+    if (this.cli.dryRun) {
+      console.log(`build-exe-for-python-sdk: [dry-run] cp ${source} ${destination}`)
+      return destination
+    }
+    if (!existsSync(source)) {
+      throw new Error(`build-exe-for-python-sdk: target ripgrep binary is missing at ${source}.`)
+    }
+    await copyFile(source, destination)
+    await chmod(destination, 0o755)
+    return destination
   }
 
   /**

+ 2 - 1
scripts/build-python-release.py

@@ -48,7 +48,8 @@ PLATFORMS = load_platforms()
 
 
 def runtime_suffixes(executable_name: str) -> tuple[str, ...]:
-    return ("", "-spawn-helper") if "-macos-" in executable_name else ("",)
+    suffixes = ("", "-rg")
+    return (*suffixes, "-spawn-helper") if "-macos-" in executable_name else suffixes
 
 
 def main() -> None:

+ 12 - 2
scripts/ci-workflow.spec.ts

@@ -268,7 +268,10 @@ describe('Python release workflows', () => {
       },
     })
     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 pythonCompatSteps = JSON.stringify(pythonCompat.steps)
+    expect(pythonCompatSteps).toContain('dist/deepseek_harness_sdk-$VERSION-py3-none-any.whl')
+    expect(pythonCompatSteps).toContain('dist/deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl')
+    expect(pythonCompatSteps).not.toContain('--find-links')
     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') {
@@ -341,7 +344,14 @@ describe('Python release workflows', () => {
     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')
+    const workflowJson = JSON.stringify(workflow)
+    expect(workflowJson).toContain('macosx_14_0_arm64')
+    expect(workflowJson).toContain('dist-python/$SDK_WHEEL')
+    expect(workflowJson).toContain('dist-python/$RUNTIME_WHEEL')
+    expect(workflowJson).toContain('/work/dist-python/$SDK_WHEEL')
+    expect(workflowJson).toContain('/work/dist-python/$RUNTIME_WHEEL')
+    expect(workflowJson).not.toContain('--find-links dist-python')
+    expect(workflowJson).not.toContain('--find-links /work/dist-python')
     expect(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')

+ 54 - 0
scripts/cordis-yaml.ts

@@ -0,0 +1,54 @@
+/**
+ * Cordis YAML parsing and Loader-entry classification shared by repository checks.
+ * @module scripts/cordis-yaml
+ */
+
+import * as yaml from 'js-yaml'
+
+/** A Loader `!!js` expression preserved as data instead of executed. */
+export interface JsExpr {
+  __jsExpr: string
+}
+
+const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
+  kind: 'scalar',
+  resolve: data => typeof data === 'string',
+  construct: (data: unknown): JsExpr => {
+    if (typeof data !== 'string') throw new TypeError('!!js requires a scalar string')
+    return { __jsExpr: data }
+  },
+})
+const schema = yaml.JSON_SCHEMA.extend(jsExprType)
+
+/**
+ * Parse a Cordis config while preserving Loader `!!js` expressions as data.
+ * @param source - Cordis YAML source text.
+ * @returns the parsed YAML value.
+ */
+export function loadCordisYaml(source: string): unknown {
+  return yaml.load(source, { schema })
+}
+
+/**
+ * Test whether a value is a preserved Loader `!!js` expression.
+ * @param value - parsed YAML value.
+ * @returns whether the value contains one preserved expression.
+ */
+export function isJsExpr(value: unknown): value is JsExpr {
+  return typeof value === 'object'
+    && value !== null
+    && typeof (value as Record<string, unknown>).__jsExpr === 'string'
+}
+
+/**
+ * Test whether a Loader entry owns nested entries in its `config` array.
+ * @param value - parsed Loader entry.
+ * @returns whether the entry is an explicit or package-named Cordis group.
+ */
+export function isCordisGroupEntry(value: unknown): value is Record<string, unknown> & { config: unknown[] } {
+  return typeof value === 'object'
+    && value !== null
+    && Array.isArray((value as Record<string, unknown>).config)
+    && ((value as Record<string, unknown>).group === true
+      || (value as Record<string, unknown>).name === '@deepseek-ai/cordis-plugin-group')
+}

+ 272 - 2
scripts/smoke-python-runtime.py

@@ -9,6 +9,7 @@ import json
 import os
 import queue
 import subprocess
+import sys
 import tempfile
 import threading
 import time
@@ -28,6 +29,11 @@ WORKFLOW_WORKER_TEXT = "workflow worker smoke ok"
 MINIMAL_PROMPT = "Exercise the packaged minimal agent's persistent Bash and string-replacement editor."
 MINIMAL_TEXT = "minimal agent smoke ok"
 MINIMAL_EDITOR_PATH_PREFIX = "Editor path: "
+FS_SEARCH_PROMPT = "Exercise the packaged filesystem search tools."
+FS_SEARCH_TEXT = "filesystem search smoke ok"
+FS_SEARCH_MARKER = "PACKAGED_FS_SEARCH_OK"
+MCP_PROMPT = "Exercise the packaged MCP client with one external stdio server."
+MCP_TEXT = "MCP client smoke ok"
 MINIMAL_CORDIS = (
     Path(__file__).resolve().parent.parent / "examples" / "jsonrpc-agent" / "minimal.cordis.yml"
 )
@@ -116,6 +122,144 @@ CUSTOM_CORDIS = """\
 - id: cordis-tool
   name: '@deepseek-ai/dsh-tool-cordis'
 """
+FS_SEARCH_CORDIS = """\
+- id: sdk-jsonrpc-server
+  name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
+- id: agent-core
+  name: '@deepseek-ai/dsh-agent-spine-demo'
+  config:
+    workspaceContext: false
+    skills:
+      enabled: false
+    toolBash: false
+    toolJobs: false
+- id: sessions
+  name: '@deepseek-ai/dsh-session-persistence-jsonl'
+  config:
+    root: !!js process.env.DSH_SESSION_ROOT
+    compression: 'none'
+- id: subprocess
+  name: '@deepseek-ai/dsh-subprocess-local'
+- id: fs-search
+  name: '@deepseek-ai/dsh-tool-fs-search'
+  config:
+    sampleOverCapGlobResults: false
+"""
+MCP_SERVER_SCRIPT = """\
+import json
+import os
+import sys
+import time
+
+
+log_path = os.environ.get("MCP_SMOKE_LOG")
+
+
+def send(message):
+    sys.stdout.write(json.dumps(message, separators=(",", ":")) + "\\n")
+    sys.stdout.flush()
+
+
+for line in sys.stdin:
+    request = json.loads(line)
+    if log_path is not None:
+        with open(log_path, "a", encoding="utf-8") as log:
+            log.write(str(request.get("method")) + "\\n")
+    request_id = request.get("id")
+    if request_id is None:
+        continue
+    method = request.get("method")
+    if method == "initialize":
+        send({
+            "jsonrpc": "2.0",
+            "id": request_id,
+            "result": {
+                "protocolVersion": request["params"]["protocolVersion"],
+                "capabilities": {"tools": {"listChanged": False}},
+                "serverInfo": {"name": "python-wheel-fixture", "version": "1.0.0"},
+            },
+        })
+    elif method == "tools/list":
+        # Keep discovery pending longer than the old smoke's 100 ms grace
+        # period. An SDK runtime that answers initialize too early will make
+        # its first model request without this tool and fail deterministically.
+        time.sleep(0.25)
+        send({
+            "jsonrpc": "2.0",
+            "id": request_id,
+            "result": {
+                "tools": [{
+                    "name": "add",
+                    "description": "Add two numbers.",
+                    "inputSchema": {
+                        "type": "object",
+                        "properties": {"a": {"type": "number"}, "b": {"type": "number"}},
+                        "required": ["a", "b"],
+                        "additionalProperties": False,
+                    },
+                }],
+            },
+        })
+    elif method == "tools/call":
+        params = request["params"]
+        if params.get("name") != "add" or params.get("arguments") != {"a": 19, "b": 23}:
+            send({
+                "jsonrpc": "2.0",
+                "id": request_id,
+                "error": {"code": -32602, "message": "unexpected tool call"},
+            })
+            continue
+        send({
+            "jsonrpc": "2.0",
+            "id": request_id,
+            "result": {"content": [{"type": "text", "text": "42"}]},
+        })
+    else:
+        send({
+            "jsonrpc": "2.0",
+            "id": request_id,
+            "error": {"code": -32601, "message": f"unsupported method: {method}"},
+        })
+"""
+
+
+def mcp_cordis(server_script: Path) -> str:
+    """Build an external config that mounts the packaged MCP client."""
+    return json.dumps([
+        {
+            "id": "sdk-jsonrpc-server",
+            "name": "@deepseek-ai/dsh-sdk-jsonrpc-server",
+        },
+        {
+            "id": "agent-core",
+            "name": "@deepseek-ai/dsh-agent-spine-demo",
+            "config": {
+                "workspaceContext": False,
+                "skills": {"enabled": False},
+                "toolBash": False,
+            },
+        },
+        {
+            "id": "sessions",
+            "name": "@deepseek-ai/dsh-session-persistence-jsonl",
+            "config": {"root": "./sessions", "compression": "none"},
+        },
+        {
+            "id": "mcp-fixture",
+            "name": "@deepseek-ai/dsh-mcp-client",
+            "config": {
+                "serverName": "fixture",
+                "transport": "stdio",
+                "command": sys.executable,
+                "args": [str(server_script)],
+                "env": {"MCP_SMOKE_LOG": str(server_script.with_suffix(".log"))},
+                "failOnStartupError": True,
+                "reconnect": {"enabled": False},
+            },
+        },
+    ], indent=2)
+
+
 class MockModelHandler(BaseHTTPRequestHandler):
     """Return deterministic text, worker, and orchestration completions."""
 
@@ -150,6 +294,12 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
     if latest.get("role") == "tool":
         call_id, tool_name = latest_tool_call(messages)
         tool_text = message_text(latest.get("content"))
+        mcp = mcp_tool_followup(call_id, tool_name, tool_text)
+        if mcp is not None:
+            return mcp
+        fs_search = fs_search_tool_followup(call_id, tool_name, tool_text)
+        if fs_search is not None:
+            return fs_search
         minimal = minimal_tool_followup(body, call_id, tool_name, tool_text)
         if minimal is not None:
             return minimal
@@ -191,6 +341,8 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
         SNAPSHOT_PROMPT,
         CODE_PROMPT,
         WORKFLOW_PROMPT,
+        FS_SEARCH_PROMPT,
+        MCP_PROMPT,
     }
     prompt = next(
         (candidate for candidate in user_prompts if candidate in scenario_prompts),
@@ -232,9 +384,60 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
                 },
             },
         )
+    if prompt == FS_SEARCH_PROMPT:
+        assert_advertised_tool(body, "grep")
+        assert_advertised_tool(body, "glob")
+        return tool_call_chunks(
+            "fs-search-grep",
+            "grep",
+            {"pattern": FS_SEARCH_MARKER, "path": "."},
+        )
+    if prompt == MCP_PROMPT:
+        assert_advertised_tool(body, "mcp__fixture__add")
+        return tool_call_chunks(
+            "mcp-add",
+            "mcp__fixture__add",
+            {"a": 19, "b": 23},
+        )
     return text_chunks(EXPECTED_TEXT)
 
 
+def mcp_tool_followup(
+    call_id: str,
+    tool_name: str,
+    tool_text: str,
+) -> list[dict[str, object]] | None:
+    """Verify one tool call through the packaged MCP client."""
+    if call_id != "mcp-add":
+        return None
+    if tool_name != "mcp__fixture__add" or "42" not in tool_text:
+        raise AssertionError(f"packaged MCP call returned an unexpected result: {tool_name}: {tool_text}")
+    return text_chunks(MCP_TEXT)
+
+
+def fs_search_tool_followup(
+    call_id: str,
+    tool_name: str,
+    tool_text: str,
+) -> list[dict[str, object]] | None:
+    """Exercise both ripgrep-backed tools through the packaged executable."""
+    if not call_id.startswith("fs-search-"):
+        return None
+    if call_id == "fs-search-grep" and tool_name == "grep":
+        if "needle.txt" not in tool_text or FS_SEARCH_MARKER not in tool_text:
+            raise AssertionError(f"packaged grep returned no marker: {tool_text}")
+        return tool_call_chunks(
+            "fs-search-glob",
+            "glob",
+            {"pattern": "**/*.txt"},
+        )
+    if call_id == "fs-search-glob" and tool_name == "glob":
+        if "needle.txt" not in tool_text:
+            raise AssertionError(f"packaged glob returned no fixture path: {tool_text}")
+        return text_chunks(FS_SEARCH_TEXT)
+    raise AssertionError(f"unexpected filesystem-search follow-up: {call_id} {tool_name}: {tool_text}")
+
+
 def minimal_tool_followup(
     body: dict[str, object],
     call_id: str,
@@ -476,13 +679,13 @@ def main() -> None:
     parser = argparse.ArgumentParser(description=__doc__)
     parser.add_argument(
         "--scenario",
-        choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-snapshot", "direct"),
+        choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-fs-search", "sdk-mcp", "sdk-snapshot", "direct"),
         default="all",
     )
     parser.add_argument("--exe", type=Path)
     parser.add_argument("--update-snapshots", action="store_true")
     args = parser.parse_args()
-    if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-snapshot", "direct"} and args.exe is None:
+    if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-fs-search", "sdk-snapshot", "direct"} and args.exe is None:
         parser.error("--exe is required for custom, minimal, snapshot, and direct scenarios")
     if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-snapshot"}:
         parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-snapshot, or all")
@@ -498,6 +701,11 @@ def main() -> None:
         if args.scenario in {"all", "sdk-minimal"}:
             assert args.exe is not None
             smoke_sdk_minimal(model.url, args.exe.resolve(), args.update_snapshots)
+        if args.scenario in {"all", "sdk-fs-search"}:
+            assert args.exe is not None
+            smoke_sdk_fs_search(model.url, args.exe.resolve())
+        if args.scenario in {"all", "sdk-mcp"}:
+            smoke_sdk_mcp(model.url, None if args.exe is None else args.exe.resolve())
         if args.scenario in {"all", "sdk-snapshot"}:
             assert args.exe is not None
             smoke_sdk_snapshot(model.url, args.exe.resolve(), args.update_snapshots)
@@ -594,6 +802,68 @@ def smoke_sdk_minimal(base_url: str, executable: Path, update_snapshots: bool) -
         )
 
 
+def smoke_sdk_fs_search(base_url: str, executable: Path) -> None:
+    """Exercise real grep and glob spawns through the packaged executable."""
+    from deepseek_harness import DeepSeekHarness
+
+    with tempfile.TemporaryDirectory(prefix="dsh-sdk-fs-search-") as temporary:
+        root = Path(temporary).resolve()
+        (root / "needle.txt").write_text(f"{FS_SEARCH_MARKER}\n")
+        sessions = root / "sessions"
+        cordis = root / "cordis.yml"
+        cordis.write_text(FS_SEARCH_CORDIS)
+        with DeepSeekHarness(
+            provider="deepseek-official",
+            model="smoke-model",
+            cwd=str(root),
+            session_root=str(sessions),
+            cordis=str(cordis),
+            runtime_bin=str(executable),
+            api_key="sk-keyless-smoke",
+            base_url=base_url,
+            request_timeout_seconds=60,
+        ) as harness:
+            result = harness.run(FS_SEARCH_PROMPT, session_id="fs-search-smoke")
+
+        assert result.final_response == FS_SEARCH_TEXT, result.final_response
+        assert_session_log(sessions, root, FS_SEARCH_TEXT, FS_SEARCH_MARKER, "needle.txt")
+
+
+def smoke_sdk_mcp(base_url: str, executable: Path | None) -> None:
+    """Discover and call an external stdio MCP tool through the packaged client."""
+    from deepseek_harness import DeepSeekHarness
+
+    with tempfile.TemporaryDirectory(prefix="dsh-sdk-mcp-") as temporary:
+        root = Path(temporary).resolve()
+        sessions = root / "sessions"
+        server_script = root / "mcp_server.py"
+        server_script.write_text(MCP_SERVER_SCRIPT)
+        cordis = root / "cordis.yml"
+        cordis.write_text(mcp_cordis(server_script))
+        discovery_log = server_script.with_suffix(".log")
+        with DeepSeekHarness(
+            provider="deepseek-official",
+            model="smoke-model",
+            cwd=str(root),
+            session_root=str(sessions),
+            cordis=str(cordis),
+            runtime_bin=None if executable is None else str(executable),
+            api_key="sk-keyless-smoke",
+            base_url=base_url,
+            request_timeout_seconds=60,
+        ) as harness:
+            result = harness.run(MCP_PROMPT, session_id="mcp-smoke")
+
+        assert result.final_response == MCP_TEXT, result.final_response
+        assert discovery_log.read_text().splitlines() == [
+            "initialize",
+            "notifications/initialized",
+            "tools/list",
+            "tools/call",
+        ]
+        assert_session_log(sessions, root, MCP_TEXT, "mcp__fixture__add", "42")
+
+
 def smoke_sdk_snapshot(base_url: str, executable: Path, update_snapshots: bool) -> None:
     """Drive and compare the advanced SDK/executable behavioral snapshot."""
     from deepseek_harness import DeepSeekHarness

+ 7 - 25
scripts/verify-cordis-config.ts

@@ -12,13 +12,10 @@
 
 import { globSync, readFileSync } from 'node:fs'
 import { dirname, relative, resolve } from 'node:path'
-import * as yaml from 'js-yaml'
+import { Script } from 'node:vm'
 import ts from 'typescript'
 import { cordisConfigFiles } from './cordis-config-files.ts'
-
-interface JsExpr {
-  __jsExpr: string
-}
+import { isCordisGroupEntry, isJsExpr, loadCordisYaml } from './cordis-yaml.ts'
 
 export interface PackageManifest {
   name?: string
@@ -58,16 +55,6 @@ const CHOOSER_BACKEND_PACKAGES = [
   '@deepseek-ai/dsh-client-ui-directory-picker-browse',
   '@deepseek-ai/dsh-client-ui-directory-picker-native',
 ]
-const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
-  kind: 'scalar',
-  resolve: data => typeof data === 'string',
-  construct: (data: unknown): JsExpr => {
-    if (typeof data !== 'string') throw new TypeError('!!js requires a scalar string')
-    return { __jsExpr: data }
-  },
-})
-const schema = yaml.JSON_SCHEMA.extend(jsExprType)
-
 const errors: string[] = []
 const pluginReferences: PluginReference[] = []
 
@@ -75,7 +62,7 @@ if (import.meta.main) {
   const files = cordisConfigFiles(root)
 
   for (const file of files) {
-    const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
+    const document = loadCordisYaml(readFileSync(resolve(root, file), 'utf8'))
     if (!isUnknownArray(document)) {
       errors.push(`${file}: root must be a Loader entry array`)
       continue
@@ -174,7 +161,7 @@ function validatePresetPlaneSeparation(): string[] {
 
 /** Every entry of one config file, or an empty list when it is not an entry array. */
 function loadEntries(file: string): unknown[] {
-  const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
+  const document = loadCordisYaml(readFileSync(resolve(root, file), 'utf8'))
   return isUnknownArray(document) ? document : []
 }
 
@@ -206,7 +193,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
   }
   recordPlugin(value, file)
   validateMetadata(value, file, path)
-  if ((value.group === true || value.name === '@deepseek-ai/cordis-plugin-group') && isUnknownArray(value.config)) {
+  if (isCordisGroupEntry(value)) {
     for (let index = 0; index < value.config.length; index++) {
       validateEntry(value.config[index], file, `${path}.config[${index}]`)
     }
@@ -496,9 +483,8 @@ export function metadataExpressionErrors(entry: Record<string, unknown>, path: s
  */
 function disabledExpressionProblem(expression: string): string | undefined {
   try {
-    // Compilation only — the constructor never executes the body.
-    // oxlint-disable-next-line typescript/no-implied-eval
-    new Function(`return (${expression})`)
+    // Compilation only — constructing a Script does not execute its source.
+    new Script(`(${expression})`)
     return undefined
   } catch (error) {
     const detail = error instanceof Error ? error.message : String(error)
@@ -519,10 +505,6 @@ function collectExpressionPaths(value: unknown, path: string, output: string[]):
   for (const [key, child] of Object.entries(value)) collectExpressionPaths(child, `${path}.${key}`, output)
 }
 
-function isJsExpr(value: unknown): value is JsExpr {
-  return isRecord(value) && typeof value.__jsExpr === 'string'
-}
-
 function isRecord(value: unknown): value is Record<string, unknown> {
   return value !== null && typeof value === 'object'
 }

+ 165 - 0
scripts/verify-runtime-closure.spec.ts

@@ -0,0 +1,165 @@
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { dirname, join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import { verifyRuntimeClosure } from './verify-runtime-closure.ts'
+
+const roots: string[] = []
+
+function fixture(files: Record<string, string | Record<string, unknown>>): string {
+  const root = mkdtempSync(join(tmpdir(), 'dsh-runtime-closure-'))
+  roots.push(root)
+  for (const [relative, value] of Object.entries(files)) {
+    const path = join(root, relative)
+    mkdirSync(dirname(path), { recursive: true })
+    writeFileSync(path, typeof value === 'string' ? value : `${JSON.stringify(value, null, 2)}\n`)
+  }
+  return root
+}
+
+const platforms = {
+  'linux-x64': { tag: 'manylinux_2_28_x86_64', executable: 'runtime-linux-x64' },
+  'linux-arm64': { tag: 'manylinux_2_28_aarch64', executable: 'runtime-linux-arm64' },
+  'macos-arm64': { tag: 'macosx_14_0_arm64', executable: 'runtime-macos-arm64' },
+}
+
+function workspace(root: string, name: string, manifest: Record<string, unknown>): void {
+  const packageName = name.replace('@scope/', '')
+  const path = join(root, 'packages/core', packageName, 'package.json')
+  mkdirSync(dirname(path), { recursive: true })
+  writeFileSync(path, `${JSON.stringify({ name, ...manifest }, null, 2)}\n`)
+}
+
+afterEach(() => {
+  for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('verifyRuntimeClosure', () => {
+  it('requires only plugins active for a Linux or macOS target', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/shared': 'workspace:^' } },
+      'python/sdk-runtime/platforms.json': platforms,
+      'apps/cli/config/agent-presets/standard/agent.cordis.yml': `
+- id: tools
+  name: cordis:group
+  group: true
+  config:
+    - id: shared
+      name: '@scope/shared'
+    - id: linux
+      name: '@scope/linux'
+      disabled: !!js process.platform !== 'linux'
+    - id: macos
+      name: '@scope/macos'
+      disabled: !!js process.platform !== 'darwin'
+`,
+    })
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.presetCount).toBe(1)
+    expect(result.failures).toEqual([
+      'standard preset -> @scope/linux (linux-arm64, linux-x64)',
+      'standard preset -> @scope/macos (macos-arm64)',
+    ])
+  })
+
+  it('treats an unsupported disabled expression as active on every target', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: {} },
+      'python/sdk-runtime/platforms.json': platforms,
+      'apps/cli/config/agent-presets/standard/agent.cordis.yml': `
+- id: conditional
+  name: '@scope/conditional'
+  disabled: !!js process.env.DSH_DISABLE_CONDITIONAL === '1'
+`,
+    })
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.failures).toEqual([
+      'standard preset -> @scope/conditional (linux-arm64, linux-x64, macos-arm64)',
+    ])
+  })
+
+  it('does not interpret an ordinary plugin array config as nested Loader entries', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/plugin': 'workspace:^' } },
+      'python/sdk-runtime/platforms.json': platforms,
+      'apps/cli/config/agent-presets/standard/agent.cordis.yml': `
+- id: plugin
+  name: '@scope/plugin'
+  config:
+    - name: '@scope/config-value'
+`,
+    })
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.failures).toEqual([])
+  })
+
+  it('requires preset plugins to be linked from the workspace', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/plugin': '1.2.3' } },
+      'python/sdk-runtime/platforms.json': platforms,
+      'apps/cli/config/agent-presets/standard/agent.cordis.yml': `
+- id: plugin
+  name: '@scope/plugin'
+`,
+    })
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.failures).toEqual([
+      'standard preset -> @scope/plugin [runtime dependency is "1.2.3"; expected workspace:] (linux-arm64, linux-x64, macos-arm64)',
+    ])
+  })
+
+  it('fails when no shipped preset is discovered', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: {} },
+      'python/sdk-runtime/platforms.json': platforms,
+    })
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.presetCount).toBe(0)
+    expect(result.failures).toEqual([
+      'no agent presets matched apps/cli/config/agent-presets/*/agent.cordis.yml',
+    ])
+  })
+
+  it('fails when the runtime platform manifest has no targets', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: {} },
+      'python/sdk-runtime/platforms.json': {},
+      'apps/cli/config/agent-presets/standard/agent.cordis.yml': '[]\n',
+    })
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.failures).toEqual([
+      'python/sdk-runtime/platforms.json defines no runtime targets',
+    ])
+  })
+
+  it('retains the required workspace-peer closure check', async () => {
+    const root = fixture({
+      'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/root': 'workspace:^' } },
+      'python/sdk-runtime/platforms.json': platforms,
+      'apps/cli/config/agent-presets/minimal/agent.cordis.yml': '[]\n',
+    })
+    workspace(root, '@scope/root', {
+      peerDependencies: { '@scope/required': 'workspace:^', '@scope/optional': 'workspace:^' },
+      peerDependenciesMeta: { '@scope/optional': { optional: true } },
+    })
+    workspace(root, '@scope/required', {})
+    workspace(root, '@scope/optional', {})
+
+    const result = await verifyRuntimeClosure(root)
+
+    expect(result.workspacePackageCount).toBe(1)
+    expect(result.failures).toEqual(['runtime -> @scope/root -> @scope/required'])
+  })
+})

+ 178 - 49
scripts/verify-runtime-closure.ts

@@ -1,12 +1,14 @@
 /**
- * Verify that the executable deploy manifest supplies every required workspace
- * peer in its dependency graph. With auto peer installation disabled, a missing
- * root peer can otherwise fail only when Cordis loads the packaged plugin.
+ * Verify that the executable deploy manifest supplies every plugin referenced
+ * by a shipped agent preset and every required workspace peer in its dependency
+ * graph. With auto peer installation disabled, either omission can otherwise
+ * fail only when Cordis loads the packaged plugin.
  */
 import { globSync } from 'node:fs'
 import { readFile } from 'node:fs/promises'
-import { resolve } from 'node:path'
+import { basename, dirname, resolve } from 'node:path'
 import { parseArgs } from 'node:util'
+import { isCordisGroupEntry, loadCordisYaml } from './cordis-yaml.ts'
 
 interface PackageManifest {
   name?: string
@@ -21,58 +23,181 @@ interface WorkspacePackage {
   manifest: PackageManifest
 }
 
-const root = resolve(import.meta.dirname, '..')
-const { values } = parseArgs({
-  args: process.argv.slice(2),
-  options: { manifest: { type: 'string' } },
-})
-const runtimeManifestPath = resolve(root, values.manifest ?? 'python/sdk-runtime/package.json')
-const runtimeManifest = await loadManifest(runtimeManifestPath)
-const runtimeName = runtimeManifest.name ?? 'python/sdk-runtime'
-const workspace = await loadWorkspacePackages()
-const runtimeDependencies = runtimeManifest.dependencies ?? {}
-const parents = new Map<string, string | undefined>()
-const queue: string[] = []
-
-for (const dependency of Object.keys(runtimeDependencies).sort()) {
-  if (!workspace.has(dependency)) continue
-  parents.set(dependency, undefined)
-  queue.push(dependency)
-}
-
-const failures: string[] = []
-for (let index = 0; index < queue.length; index += 1) {
-  const packageName = queue[index]
-  if (packageName === undefined) continue
-  const current = workspace.get(packageName)
-  if (current === undefined) continue
-  const peers = current.manifest.peerDependencies ?? {}
-  const peerMeta = current.manifest.peerDependenciesMeta ?? {}
-  for (const peer of Object.keys(peers).sort()) {
-    if (!workspace.has(peer) || peerMeta[peer]?.optional === true) continue
-    if (runtimeDependencies[peer]?.startsWith('workspace:') === true) continue
-    failures.push(`${formatChain(runtimeName, packageName, parents)} -> ${peer}`)
+interface RuntimePlatform {
+  tag: string
+  executable: string
+}
+
+type RuntimePlatformManifest = Record<string, RuntimePlatform>
+
+const AGENT_PRESET_GLOB = 'apps/cli/config/agent-presets/*/agent.cordis.yml'
+
+export interface RuntimeClosureResult {
+  failures: string[]
+  presetCount: number
+  workspacePackageCount: number
+}
+
+/**
+ * Check that the runtime manifest contains every shipped-preset plugin and workspace peer.
+ * @param root repository root containing the runtime manifest and shipped presets.
+ * @param manifestPath runtime manifest path relative to {@link root}.
+ * @returns the discovered preset count, reachable workspace package count, and violations.
+ */
+export async function verifyRuntimeClosure(
+  root: string,
+  manifestPath = 'python/sdk-runtime/package.json',
+): Promise<RuntimeClosureResult> {
+  const runtimeManifest = await loadManifest(resolve(root, manifestPath))
+  const runtimeName = runtimeManifest.name ?? manifestPath
+  const workspace = await loadWorkspacePackages(root)
+  const runtimeDependencies = runtimeManifest.dependencies ?? {}
+  const platforms = await loadJson<RuntimePlatformManifest>(resolve(root, 'python/sdk-runtime/platforms.json'))
+  const presetPaths = globSync(AGENT_PRESET_GLOB, { cwd: root }).sort()
+  const targets = Object.keys(platforms).sort()
+  const parents = new Map<string, string | undefined>()
+  const queue: string[] = []
+
+  for (const dependency of Object.keys(runtimeDependencies).sort()) {
+    if (!workspace.has(dependency)) continue
+    parents.set(dependency, undefined)
+    queue.push(dependency)
   }
-  const dependencies = {
-    ...current.manifest.dependencies,
-    ...current.manifest.optionalDependencies,
+
+  const failures: string[] = []
+  if (presetPaths.length === 0) failures.push(`no agent presets matched ${AGENT_PRESET_GLOB}`)
+  if (targets.length === 0) failures.push('python/sdk-runtime/platforms.json defines no runtime targets')
+  failures.push(...await missingPresetPlugins(root, runtimeDependencies, presetPaths, targets))
+  for (let index = 0; index < queue.length; index += 1) {
+    const packageName = queue[index]
+    if (packageName === undefined) continue
+    const current = workspace.get(packageName)
+    if (current === undefined) continue
+    const peers = current.manifest.peerDependencies ?? {}
+    const peerMeta = current.manifest.peerDependenciesMeta ?? {}
+    for (const peer of Object.keys(peers).sort()) {
+      if (!workspace.has(peer) || peerMeta[peer]?.optional === true) continue
+      if (runtimeDependencies[peer]?.startsWith('workspace:') === true) continue
+      failures.push(`${formatChain(runtimeName, packageName, parents)} -> ${peer}`)
+    }
+    const dependencies = {
+      ...current.manifest.dependencies,
+      ...current.manifest.optionalDependencies,
+    }
+    for (const dependency of Object.keys(dependencies).sort()) {
+      if (!workspace.has(dependency) || parents.has(dependency)) continue
+      parents.set(dependency, packageName)
+      queue.push(dependency)
+    }
   }
-  for (const dependency of Object.keys(dependencies).sort()) {
-    if (!workspace.has(dependency) || parents.has(dependency)) continue
-    parents.set(dependency, packageName)
-    queue.push(dependency)
+
+  return {
+    failures,
+    presetCount: presetPaths.length,
+    workspacePackageCount: queue.length,
+  }
+}
+
+if (import.meta.main) {
+  const root = resolve(import.meta.dirname, '..')
+  const { values } = parseArgs({
+    args: process.argv.slice(2),
+    options: { manifest: { type: 'string' } },
+  })
+  const result = await verifyRuntimeClosure(root, values.manifest)
+  if (result.failures.length > 0) {
+    console.error('verify-runtime-closure: preset plugins or required workspace peers are missing from python/sdk-runtime dependencies:')
+    for (const failure of result.failures) console.error(`  ${failure}`)
+    process.exitCode = 1
+  } else {
+    console.log(
+      `verify-runtime-closure: ${result.presetCount} agent presets and ${result.workspacePackageCount} workspace packages form a closed runtime dependency graph.`,
+    )
+  }
+}
+
+async function missingPresetPlugins(
+  root: string,
+  runtimeDependencies: Readonly<Record<string, string>>,
+  presetPaths: readonly string[],
+  targets: readonly string[],
+): Promise<string[]> {
+  const missing = new Map<string, Set<string>>()
+  const failures: string[] = []
+  for (const presetPath of presetPaths) {
+    const document = loadCordisYaml(await readFile(resolve(root, presetPath), 'utf8'))
+    if (!Array.isArray(document)) {
+      failures.push(`${presetPath}: preset root must be a Loader entry array`)
+      continue
+    }
+    for (const target of targets) {
+      const processPlatform = processPlatformForTarget(target)
+      for (const plugin of activeBarePluginPackages(document, processPlatform)) {
+        const version = runtimeDependencies[plugin]
+        if (version?.startsWith('workspace:') === true) continue
+        const preset = basename(dirname(presetPath))
+        const declaration = version === undefined
+          ? ''
+          : ` [runtime dependency is ${JSON.stringify(version)}; expected workspace:]`
+        const key = `${preset} preset -> ${plugin}${declaration}`
+        const targets = missing.get(key) ?? new Set<string>()
+        targets.add(target)
+        missing.set(key, targets)
+      }
+    }
   }
+  failures.push(...[...missing.entries()].map(([chain, targets]) =>
+    `${chain} (${[...targets].sort().join(', ')})`))
+  return failures
 }
 
-if (failures.length > 0) {
-  console.error('verify-runtime-closure: required workspace peers are missing from python/sdk-runtime dependencies:')
-  for (const failure of failures) console.error(`  ${failure}`)
-  process.exit(1)
+function activeBarePluginPackages(entries: unknown[], processPlatform: string): Set<string> {
+  const packages = new Set<string>()
+  const visit = (value: unknown, parentDisabled: boolean): void => {
+    if (!isRecord(value)) return
+    const disabled = parentDisabled || disabledOnPlatform(value.disabled, processPlatform)
+    if (disabled) return
+    if (typeof value.name === 'string') {
+      const packageName = barePackageName(value.name)
+      if (packageName !== undefined) packages.add(packageName)
+    }
+    if (isCordisGroupEntry(value)) {
+      for (const child of value.config) visit(child, disabled)
+    }
+  }
+  for (const entry of entries) visit(entry, false)
+  return packages
+}
+
+function disabledOnPlatform(value: unknown, processPlatform: string): boolean {
+  if (typeof value === 'boolean') return value
+  if (!isRecord(value) || typeof value.__jsExpr !== 'string') return false
+  const match = /^process\.platform\s*(===|!==)\s*(['"])(win32|linux|darwin)\2$/.exec(value.__jsExpr.trim())
+  if (match === null) return false
+  const [, operator, , expected] = match
+  return operator === '===' ? processPlatform === expected : processPlatform !== expected
 }
 
-console.log(`verify-runtime-closure: ${queue.length} workspace packages form a closed runtime dependency graph.`)
+function processPlatformForTarget(target: string): string {
+  if (target.startsWith('linux-')) return 'linux'
+  if (target.startsWith('macos-')) return 'darwin'
+  throw new Error(`verify-runtime-closure: unsupported runtime target ${JSON.stringify(target)}`)
+}
 
-async function loadWorkspacePackages(): Promise<Map<string, WorkspacePackage>> {
+function barePackageName(specifier: string): string | undefined {
+  if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.includes(':')) return undefined
+  const parts = specifier.split('/')
+  if (specifier.startsWith('@')) {
+    return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : undefined
+  }
+  return parts[0] || undefined
+}
+
+function isRecord(value: unknown): value is Record<string, unknown> {
+  return typeof value === 'object' && value !== null && !Array.isArray(value)
+}
+
+async function loadWorkspacePackages(root: string): Promise<Map<string, WorkspacePackage>> {
   const paths = globSync(['packages/*/*/package.json', 'vendor/*/package.json'], { cwd: root })
     .sort()
     .map(relative => resolve(root, relative))
@@ -85,7 +210,11 @@ async function loadWorkspacePackages(): Promise<Map<string, WorkspacePackage>> {
 }
 
 async function loadManifest(path: string): Promise<PackageManifest> {
-  return JSON.parse(await readFile(path, 'utf8')) as PackageManifest
+  return loadJson<PackageManifest>(path)
+}
+
+async function loadJson<T>(path: string): Promise<T> {
+  return JSON.parse(await readFile(path, 'utf8')) as T
 }
 
 function formatChain(

Някои файлове не бяха показани, защото твърде много файлове са промени