Merge remote-tracking branch 'origin/master' into cross-family-fs-sandbox

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md
#	.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md
#	docs/capability-seams.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	docs/persistence-catalog.md
#	docs/rfc/INDEX.md
#	examples/acp-agent/README.md
#	examples/acp-agent/fs.cordis.snapshot.yml
#	examples/acp-agent/fs.cordis.yml
#	examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/session.jsonl
#	examples/acp-agent/tests/snapshots/workspace-context/system-prompt.expected.md
#	examples/acp-agent/tests/snapshots/workspace-context/tool-schemas.expected.json
#	examples/acp-agent/tests/snapshots/workspace-edit/system-prompt.expected.md
#	examples/acp-agent/tests/snapshots/workspace-edit/tool-schemas.expected.json
#	packages/bash/bash/src/index.ts
#	packages/bash/tool-bash/package.json
#	packages/bash/tool-bash/src/index.ts
#	packages/bash/tool-bash/tests/tools.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/fs/README.md
#	packages/fs/tool-fs/src/edit.ts
#	packages/fs/tool-fs/src/write.ts
#	packages/sandbox/README.md
#	pnpm-lock.yaml
This commit is contained in:
kingwl
2026-07-20 11:44:37 +08:00
1293 changed files with 74019 additions and 16297 deletions
+2 -2
View File
@@ -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
README.md: 30bca971f1bfc6f694302c8f7eb8ce80843ed9b2
README.zh.md: d2eea59d148b6de1bdf36b2f2e9c96fc1c933be7
README.md: d2b6a1cfe9897026d567b2def301799069c350fb
README.zh.md: 2ffccef922d52923f7c6e373a01ed8b19d9a55c1
+1 -1
View File
@@ -2,7 +2,7 @@
English | [中文](README.zh.md)
Python packages for driving DeepSeek Harness as a subprocess: a client SDK that spawns the `dsh-jsonrpc-agent` binary and talks newline-delimited JSON-RPC over stdio. The runtime carrier is the single-file executable produced by this repo; design, build, and acceptance details live in [docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md](../docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
Python packages for driving DeepSeek Harness as a subprocess: a client SDK that spawns the `dsh-jsonrpc-agent` binary and talks newline-delimited JSON-RPC over stdio. The runtime carrier is the single-file executable produced by this repo; design, build, and acceptance details live in [.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md](../.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
## Packages
+1 -1
View File
@@ -2,7 +2,7 @@
[English](README.md) | 中文
以子进程方式驱动 DeepSeek Harness 的 Python 包:客户端 SDK spawn `dsh-jsonrpc-agent` 二进制,并通过 stdio 上按行分隔的 JSON-RPC 与之通信。运行时载体是本仓库产出的单文件可执行文件;设计、构建与验收细节见 [docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md](../docs/rfc/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md)。
以子进程方式驱动 DeepSeek Harness 的 Python 包:客户端 SDK spawn `dsh-jsonrpc-agent` 二进制,并通过 stdio 上按行分隔的 JSON-RPC 与之通信。运行时载体是本仓库产出的单文件可执行文件;设计、构建与验收细节见 [.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md](../.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md)。
## 包
+2 -2
View File
@@ -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
README.md: 5525e8a7b88df3f686bb1fa3a08c3556acc2e655
README.zh.md: 30723d6d78b84e16261898a88ed22c3c58fc83bf
README.md: cdf38d4474a0e0148a4804e14c12971c76b38e27
README.zh.md: 99d57c6f900371a46b94c5ea80b2a1665fd5e8d1
+1 -1
View File
@@ -26,4 +26,4 @@ Each wheel contains exactly one executable. The fixed tags are `py3-none-manylin
## Zero-config design
The runtime binary always demands an explicit config (`$DSH_CORDIS_CONFIG`, or a config path as an argv positional argument) and exits loudly without one — that hard semantic is part of the runtime's design and this package does not soften it. The bin (`dsh-jsonrpc-agent`) boots only the plugins the config lists; the serving surface (the stdio JSON-RPC server) is itself one of its entries (`@deepseek-ai/dsh-jsonrpc`), and without it the booted agent has no channel to the outside. This package checks in `runtime/cordis.yml` with the JSON-RPC serving entry, agent core, a preloaded DeepSeek adapter, JSONL persistence, and local bash. The adapter reads `DEEPSEEK_API_KEY` and `DEEPSEEK_BASE_URL`, while persistence and bash use `DSH_SESSION_ROOT` and `DSH_CWD` with manual-run fallbacks. When the caller uses no explicit config channel, the `deepseek_harness` client injects that file's path via `DSH_CORDIS_CONFIG` (injection conditions: [sdk README](../sdk/README.md)). Zero-config is thus an explicit, visible parameter pass in the wrapper, not a hidden fallback in the runtime.
The runtime binary always demands an explicit config (`$DSH_CORDIS_CONFIG`, or a config path as an argv positional argument) and exits loudly without one — that hard semantic is part of the runtime's design and this package does not soften it. The bin (`dsh-jsonrpc-agent`) boots only the plugins the config lists; the serving surface (the stdio JSON-RPC server) is itself one of its entries (`@deepseek-ai/dsh-jsonrpc`), and without it the booted agent has no channel to the outside. This package checks in `runtime/cordis.yml` with the JSON-RPC serving entry, agent core, a preloaded DeepSeek adapter, JSONL persistence, local bash, and a local filesystem provider for bounded workspace-instruction loading. The adapter reads `DEEPSEEK_API_KEY` and `DEEPSEEK_BASE_URL`, while persistence, bash, and the filesystem provider use `DSH_SESSION_ROOT` and `DSH_CWD` with manual-run fallbacks. When the caller uses no explicit config channel, the `deepseek_harness` client injects that file's path via `DSH_CORDIS_CONFIG` (injection conditions: [sdk README](../sdk/README.md)). Zero-config is thus an explicit, visible parameter pass in the wrapper, not a hidden fallback in the runtime.
+1 -1
View File
@@ -26,4 +26,4 @@ exe 缺失时抛出 `FileNotFoundError`,并写明两种获取途径:在 deep
## 零配置设计
运行时二进制始终要求显式配置(`$DSH_CORDIS_CONFIG`,或作为 argv 位置参数的配置路径),缺了就报错退出——这一硬语义是运行时设计的一部分,本包不软化它。`bin``dsh-jsonrpc-agent`)只启动配置里列出的插件;对外服务接口(stdio JSON-RPC 服务器)也是其中一个条目(`@deepseek-ai/dsh-jsonrpc`),缺了它,启动出的 agent 就没有对外通道。本包检入的 `runtime/cordis.yml` 包含 JSON-RPC 服务条目、`agent-core`、预载的 DeepSeek 适配器、JSONL 持久化本地 bash。DeepSeek 适配器读取 `DEEPSEEK_API_KEY``DEEPSEEK_BASE_URL`,持久化bash 则使用 `DSH_SESSION_ROOT``DSH_CWD`,并为手动运行提供回退值。调用方未使用任何显式配置通道时,`deepseek_harness` 客户端把该文件路径注入 `DSH_CORDIS_CONFIG`(注入条件见 [sdk README](../sdk/README.md))。因此,零配置是包装层中一次显式、可见的参数传递,而不是运行时中的隐藏回退。
运行时二进制始终要求显式配置(`$DSH_CORDIS_CONFIG`,或作为 argv 位置参数的配置路径),缺了就报错退出——这一硬语义是运行时设计的一部分,本包不软化它。`bin``dsh-jsonrpc-agent`)只启动配置里列出的插件;对外服务接口(stdio JSON-RPC 服务器)也是其中一个条目(`@deepseek-ai/dsh-jsonrpc`),缺了它,启动出的 agent 就没有对外通道。本包检入的 `runtime/cordis.yml` 包含 JSON-RPC 服务条目、`agent-core`、预载的 DeepSeek 适配器、JSONL 持久化本地 bash,以及用于有界加载工作区指令的本地文件系统 provider。DeepSeek 适配器读取 `DEEPSEEK_API_KEY``DEEPSEEK_BASE_URL`,持久化bash 和文件系统 provider 则使用 `DSH_SESSION_ROOT``DSH_CWD`,并为手动运行提供回退值。调用方未使用任何显式配置通道时,`deepseek_harness` 客户端把该文件路径注入 `DSH_CORDIS_CONFIG`(注入条件见 [sdk README](../sdk/README.md))。因此,零配置是包装层中一次显式、可见的参数传递,而不是运行时中的隐藏回退。
+4
View File
@@ -23,6 +23,7 @@
"@deepseek-ai/dsh-fs": "workspace:^",
"@deepseek-ai/dsh-fs-local": "workspace:^",
"@deepseek-ai/dsh-fs-policy": "workspace:^",
"@deepseek-ai/dsh-home": "workspace:^",
"@deepseek-ai/dsh-hook-protocol": "workspace:^",
"@deepseek-ai/dsh-hooks-claude": "workspace:^",
"@deepseek-ai/dsh-hooks-codex": "workspace:^",
@@ -30,9 +31,11 @@
"@deepseek-ai/dsh-jsonrpc": "workspace:^",
"@deepseek-ai/dsh-jsonrpc-demo": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-token-meter": "workspace:^",
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
"@deepseek-ai/dsh-llm-pi-ai": "workspace:^",
"@deepseek-ai/dsh-permission": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
@@ -71,6 +74,7 @@
"@deepseek-ai/dsh-web-search-deepseek": "workspace:^",
"@deepseek-ai/dsh-web-search-exa": "workspace:^",
"@deepseek-ai/dsh-web-search-perplexity": "workspace:^",
"@deepseek-ai/dsh-workspace-context": "workspace:^",
"@deepseek-ai/dsh-workflow": "workspace:^",
"@deepseek-ai/dsh-workflow-workerthread": "workspace:^",
"cordis": "workspace:^"
@@ -9,6 +9,9 @@
# Agent spine; the SDK server creates agents per sessionId.
- id: agent-core
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
workspaceContext:
maxBytes: 65536
# Stock DeepSeek adapters. Loading requires an API key; initialize and shutdown
# may use a dummy key because they do not call the model.
@@ -17,9 +20,6 @@
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
models:
- deepseek-v4-flash
- deepseek-v4-pro
# JSONL persistence; $DSH_SESSION_ROOT wins over ./.sessions in the process cwd.
- id: sessions
@@ -32,3 +32,10 @@
name: '@deepseek-ai/dsh-bash-local'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
# Local filesystem provider for workspace instruction loading. This does not
# expose model-facing file tools by itself.
- id: fs-local
name: '@deepseek-ai/dsh-fs-local'
config:
cwd: !!js process.env.DSH_CWD ?? process.cwd()
+2 -2
View File
@@ -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
README.md: 441b335b9e850c221fbd6a657c7de539ceca070d
README.zh.md: 65134c0e856b933c793510c2ed528e97f940f47d
README.md: 5fd1bc7cd89152a28d3da17100fd62eed4f8cb14
README.zh.md: 247a2ca5ea5c1c3afc19335a6bbcba356c823211
+4 -1
View File
@@ -25,12 +25,15 @@ By default, the SDK launches the bundled single-file `dsh-jsonrpc-agent` executa
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
provider="deepseek",
model="deepseek-v4-flash",
cordis="examples/dsbench-coding-agent/cordis.yml",
cordis="examples/jsonrpc-agent/cordis.yml",
) as harness:
result = harness.run("Make the requested code change.")
```
`provider` selects a provider route registered by the chosen Cordis composition; `model` is the model id resolved by that adapter. The bundled default composition registers `deepseek`. A custom composition can mount `llm-pi-ai`, configure provider-specific credentials/endpoints there, and select any provider/model present in pi-ai's installed catalog.
`TurnResult.final_response` is the text content from the last
`assistant/message` event in the turn. Use `TurnResult.events` for the complete
event stream, including intermediate assistant messages and tool activity.
+4 -1
View File
@@ -21,12 +21,15 @@ with DeepSeekHarness() as harness:
from deepseek_harness import DeepSeekHarness
with DeepSeekHarness(
provider="deepseek",
model="deepseek-v4-flash",
cordis="examples/dsbench-coding-agent/cordis.yml",
cordis="examples/jsonrpc-agent/cordis.yml",
) as harness:
result = harness.run("Make the requested code change.")
```
`provider` 用于选择当前 Cordis 组合已注册的提供方路由;`model` 是该适配器解析的模型 ID。内置默认组合注册 `deepseek`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方的凭据与端点,再选择 pi-ai 已安装目录中的任意提供方/模型组合。
`TurnResult.final_response` 是本轮次最后一个 `assistant/message` 事件的文本内容。完整的事件流(包括中间的助手消息与工具活动)用 `TurnResult.events` 获取。
同样的行为也可以通过 `DSH_CORDIS_CONFIG` 为运行时子进程选定。注入逻辑位于 `HarnessClient.start()`,因此底层客户端的默认启动也具有此行为:当启动解析到内置运行时,且 `cordis` 与非空的 `DSH_CORDIS_CONFIG` 均未设置时(运行时把空值视为缺省,注入检查与之一致),使用内置的默认配置;显式给出 `runtime_bin``bridge_bin``launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](../sdk-runtime/README.md)。
+2
View File
@@ -18,6 +18,7 @@ class DeepSeekHarnessConfig:
intentionally override or inject variables for a subprocess.
"""
provider: str = "deepseek"
model: str = "deepseek-v4-flash"
cwd: str | None = None
runtime_cwd: str | None = None
@@ -97,6 +98,7 @@ class DeepSeekHarness:
self._client.start()
self._client.initialize(
cwd=self._cwd,
provider=self.config.provider,
model=self.config.model,
)
self._initialized = True
@@ -115,10 +115,12 @@ class HarnessClient:
self,
*,
cwd: str,
provider: str,
model: str,
) -> InitializeResponse:
payload: JsonObject = {
"cwd": str(Path(cwd).resolve()),
"provider": provider,
"model": model,
}
try:
+4 -2
View File
@@ -22,6 +22,8 @@ _CORDIS_YML = """\
name: '@deepseek-ai/dsh-jsonrpc'
- id: agent-core
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
workspaceContext: false
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
@@ -66,7 +68,7 @@ def test_bundled_runtime_boots_a_cordis_config(tmp_path: Path, mode: str) -> Non
(tmp_path / "cordis.yml").write_text(_CORDIS_YML)
with _client(tmp_path, launch_args) as client:
init = client.initialize(cwd=str(tmp_path), model="deepseek-v4-pro")
init = client.initialize(provider="deepseek", cwd=str(tmp_path), model="deepseek-v4-pro")
assert init.serverInfo is not None
assert init.serverInfo.name == "deepseek-harness-sdk-runtime"
@@ -83,7 +85,7 @@ def test_bundled_runtime_surfaces_unbundled_plugin_failure(tmp_path: Path, mode:
client.start()
try:
with pytest.raises((TransportClosedError, TimeoutError)) as excinfo:
client.initialize(cwd=str(tmp_path), model="deepseek-v4-pro")
client.initialize(provider="deepseek", cwd=str(tmp_path), model="deepseek-v4-pro")
finally:
client.close()
+13 -13
View File
@@ -330,7 +330,7 @@ for line in sys.stdin:
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
init = client.initialize(cwd="/workspace", model="dsagent")
init = client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
assert init.serverInfo.name == "fake-dsh"
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
@@ -382,7 +382,7 @@ for line in sys.stdin:
raise RuntimeError("bad notification filter")
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
with (
client.subscribe_notifications(broken_filter) as broken,
client.subscribe_notifications(lambda notification: notification.method == "tick") as healthy,
@@ -419,7 +419,7 @@ for line in sys.stdin:
)
with HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script)))) as client:
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
with pytest.raises(ValueError):
client.session_prompt("main", [{"type": "text", "text": "fix it"}])
@@ -448,7 +448,7 @@ for line in sys.stdin:
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
request = client.next_request()
assert request.id == "bridge-req-1"
@@ -482,7 +482,7 @@ for line in sys.stdin:
with HarnessClient(
HarnessConfig(launch_args_override=(sys.executable, str(script)))
) as client:
init = client.initialize(cwd="/workspace", model="dsagent")
init = client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
assert init.serverInfo.name == "fake-dsh"
@@ -504,7 +504,7 @@ time.sleep(60)
) as client:
start = time.monotonic()
try:
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
except TimeoutError:
assert time.monotonic() - start < 2
else:
@@ -540,7 +540,7 @@ for line in sys.stdin:
client.start()
proc = client._proc
assert proc is not None
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
start = time.monotonic()
client.close()
assert time.monotonic() - start < 2
@@ -571,7 +571,7 @@ for line in sys.stdin:
assert proc is not None
with pytest.raises(Exception, match="bad initialize"):
client.initialize(cwd=".", model="dsagent")
client.initialize(provider="deepseek", cwd=".", model="dsagent")
assert proc.wait(timeout=1) is not None
assert client._proc is None
@@ -611,7 +611,7 @@ for line in sys.stdin:
client = HarnessClient(HarnessConfig(launch_args_override=(sys.executable, str(script))))
client.start()
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
client.close()
client.close()
@@ -634,7 +634,7 @@ sys.exit(42)
)
) as client:
with pytest.raises(Exception, match="fatal bridge exploded"):
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
def test_client_serializes_concurrent_writes(tmp_path: Path) -> None:
@@ -665,7 +665,7 @@ with open(os.environ["SEEN"], "w") as seen:
env={"SEEN": str(output)},
)
) as client:
client.initialize(cwd="/workspace", model="dsagent")
client.initialize(provider="deepseek", cwd="/workspace", model="dsagent")
threads = [
threading.Thread(target=client.notify, args=(f"notice-{index}", {"index": index}))
for index in range(50)
@@ -736,7 +736,7 @@ def test_client_default_launch_uses_bundled_runtime_and_injects_default_config(
monkeypatch.setenv("DSH_CORDIS_CONFIG", ambient_config)
with HarnessClient(HarnessConfig(env={"ENV_DUMP": str(env_dump)})) as client:
init = client.initialize(cwd="/workspace", model="deepseek-v4-pro")
init = client.initialize(provider="deepseek", cwd="/workspace", model="deepseek-v4-pro")
assert init.serverInfo.name == "bundled-runtime"
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == str(default_config)
@@ -752,7 +752,7 @@ def test_client_respects_explicit_config_over_bundled_default(
with HarnessClient(
HarnessConfig(env={"ENV_DUMP": str(env_dump), "DSH_CORDIS_CONFIG": "./explicit.yml"})
) as client:
client.initialize(cwd="/workspace", model="deepseek-v4-pro")
client.initialize(provider="deepseek", cwd="/workspace", model="deepseek-v4-pro")
assert json.loads(env_dump.read_text())["DSH_CORDIS_CONFIG"] == "./explicit.yml"