Merge current subagent naming stack into Team session UI

This commit is contained in:
Dudu-0223 committed 2026-10-25 15:45:40 +08:00
commit 80f70969d9
5594 files changed
+141677 -692708

No files matched your search

+2 -2
View File
@@ -9,8 +9,8 @@
en: 8e99640e4936332d
zh: 729d5de1e0ad2869
/python-contributor-workflows/validate-the-sdk:
en: 37884fe76e167a0e
zh: 4b6e50768c4bb945
en: ad4d61dccf2756a4
zh: 9365affc91287738
/python-contributor-workflows/run-against-node-source:
en: 940c941d189a8248
zh: 321f5183383d4a12
+1 -1
View File
@@ -38,7 +38,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
--scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64
```
SDK scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. `authoring/model-visible.json` pins the Linux/macOS Office authoring catalog and dependency result; `authoring/win-x64/model-visible.json` pins the Windows catalog, including the ACL diagnostic skill. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. `sdk-minimal-in-history` reuses the persistent-shell and editor scenario with a section that changes after the first successful shell call. `minimal-in-history/prompt-history.json` pins both prompt versions, the unchanged leading prompt in later requests, appended SDK system-message events, and `request/context.systemPromptUpdate`; tool schemas stay fixed and the editor file is checked independently. `sdk-dynamic-tools` pins native tool additions and removals in `dynamic-tools/tool-history.json`, including historical header references and agreement between Python SDK results, notifications, and persisted events. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
SDK scenarios compare committed expected output under `scripts/snapshots/python-sdk-single-exe/`. `minimal/model-visible.json` pins the Linux/macOS `sdk-minimal` profile's assembled system prompts, advertised tool schemas, and model-visible messages; `minimal/win-x64/model-visible.json` pins its PowerShell counterpart. `authoring/model-visible.json` pins the Linux/macOS Office authoring catalog and dependency result; `authoring/win-x64/model-visible.json` pins the Windows catalog, including the ACL diagnostic skill. A plugin that contributes an unintended system section or user message therefore fails the job, and every message the profile emits is compared. `advanced/` pins one complex process's SDK result and parent/child session logs across every target. `restart/` launches two complete SDK runtime processes against one persistence root and snapshots their isolated model histories, high-level results, and separate durable logs across every target. Its resumed turn initializes a different model and pins the persisted selection, request route, and SDK event sequence. `sdk-minimal-in-history` reuses the persistent-shell and editor scenario with a section that changes after the first successful shell call. `minimal-in-history/prompt-history.json` pins both prompt versions, the unchanged leading prompt in later requests, appended SDK system-message events, and `request/context.systemPromptUpdate`; tool schemas stay fixed and the editor file is checked independently. `sdk-dynamic-tools` pins native tool additions and removals in `dynamic-tools/tool-history.json`, including historical header references and agreement between Python SDK results, notifications, and persisted events. Rerun the owning scenario with `--update-snapshots` and review that diff before committing it.
`scheduler-recovery/` records a failed tool turn and a subsequent completed turn. It preserves the original `UNKNOWN` turn error, the first tool's completed result, and `TOOL_OUTCOME_UNKNOWN` / `TOOL_NOT_STARTED` results for the remaining calls. `sdk-snapshot` and `all` include this case; `--scenario sdk-recovery` runs it alone.
+1 -1
View File
@@ -38,7 +38,7 @@ uv run --project python/sdk python scripts/smoke-python-runtime.py \
--scenario sdk-minimal --exe dist-exe/deepseek-harness-sdk-runtime-macos-arm64
```
SDK 场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。`authoring/model-visible.json` 固定 Linux/macOS 的 Office 创作技能目录和依赖结果;`authoring/win-x64/model-visible.json` 固定 Windows 的技能目录,其中包含 ACL 诊断技能。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。`sdk-minimal-in-history` 复用持久 shell 与编辑器场景,并在首次 shell 调用成功后更改一个分段。`minimal-in-history/prompt-history.json` 固定两个提示词版本、后续请求中不变的首条提示词、追加的 SDK system-message 事件及 `request/context.systemPromptUpdate`;工具 schema 保持不变,并独立检查编辑器创建的文件。`sdk-dynamic-tools` 在 `dynamic-tools/tool-history.json` 中固定原生工具的新增与移除,包括历史 header 引用,以及 Python SDK 结果、通知与持久事件的一致性。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
SDK 场景会比对 `scripts/snapshots/python-sdk-single-exe/` 下已提交的期望输出。`minimal/model-visible.json` 固定 Linux/macOS `sdk-minimal` profile 所组装的系统提示词、对外公布的工具 schema 与模型可见消息;`minimal/win-x64/model-visible.json` 固定对应的 PowerShell 版本。`authoring/model-visible.json` 固定 Linux/macOS 的 Office 创作技能目录和依赖结果;`authoring/win-x64/model-visible.json` 固定 Windows 的技能目录,其中包含 ACL 诊断技能。因此,插件一旦贡献出计划外的系统分段或 user 消息,该任务即失败,且该 profile 发出的每条消息都会参与比对。`advanced/` 跨所有目标固定一个复杂进程的 SDK 结果及父/子会话日志。`restart/` 针对同一持久化根目录启动两个完整 SDK 运行时进程,并跨所有目标固定其彼此隔离的模型历史、高层结果与独立持久日志。恢复后的轮次初始化另一个模型,并固定持久模型选择、请求路由与 SDK 事件顺序。`sdk-minimal-in-history` 复用持久 shell 与编辑器场景,并在首次 shell 调用成功后更改一个分段。`minimal-in-history/prompt-history.json` 固定两个提示词版本、后续请求中不变的首条提示词、追加的 SDK system-message 事件及 `request/context.systemPromptUpdate`;工具 schema 保持不变,并独立检查编辑器创建的文件。`sdk-dynamic-tools` 在 `dynamic-tools/tool-history.json` 中固定原生工具的新增与移除,包括历史 header 引用,以及 Python SDK 结果、通知与持久事件的一致性。重新运行对应场景时加上 `--update-snapshots`,并在提交前审阅该差异。
`scheduler-recovery/` 记录一个失败的工具轮次及后续完成的轮次。它保留原始的 `UNKNOWN` 轮次错误、第一个工具已完成的结果,以及其余调用的 `TOOL_OUTCOME_UNKNOWN` / `TOOL_NOT_STARTED` 结果。`sdk-snapshot` 和 `all` 包含此场景;`--scenario sdk-recovery` 单独运行它。
+2 -1
View File
@@ -14,7 +14,6 @@
"@deepseek-ai/dsh": "workspace:*",
"@deepseek-ai/dsh-acp": "workspace:*",
"@deepseek-ai/dsh-agent": "workspace:*",
"@deepseek-ai/dsh-agent-controller": "workspace:*",
"@deepseek-ai/dsh-agent-default-model": "workspace:*",
"@deepseek-ai/dsh-agent-instructions": "workspace:*",
"@deepseek-ai/dsh-agent-loop": "workspace:*",
@@ -111,6 +110,7 @@
"@deepseek-ai/dsh-storage": "workspace:*",
"@deepseek-ai/dsh-storage-domain": "workspace:*",
"@deepseek-ai/dsh-subagent": "workspace:*",
"@deepseek-ai/dsh-subagent-names": "workspace:*",
"@deepseek-ai/dsh-subagent-acp": "workspace:*",
"@deepseek-ai/dsh-subagent-fork-in-process": "workspace:*",
"@deepseek-ai/dsh-subagent-spawn-in-process": "workspace:*",
@@ -128,6 +128,7 @@
"@deepseek-ai/dsh-tool-bash-persistent": "workspace:*",
"@deepseek-ai/dsh-tool-call-timeout-policy": "workspace:*",
"@deepseek-ai/dsh-tool-cordis": "workspace:*",
"@deepseek-ai/dsh-tool-executor-ptc": "workspace:*",
"@deepseek-ai/dsh-tool-fs": "workspace:*",
"@deepseek-ai/dsh-tool-fs-search": "workspace:*",
"@deepseek-ai/dsh-tool-goal": "workspace:*",
+5 -2
View File
@@ -9,8 +9,11 @@
en: ddf4dabea526cef1
zh: 6e31cab10d2f3ed8
/deepseek-harness-python-sdk/start-a-runtime:
en: 5fceaffcc35039c6
zh: 1d87e3defe7e875b
en: 1ae09bebf0f9e19b
zh: 55a0d557a2cae4d8
/deepseek-harness-python-sdk/client-approvals:
en: fc69be1beb8abff1
zh: 91b56781931a6f68
/deepseek-harness-python-sdk/customize-plugins:
en: 69a6a6745c6e89c1
zh: 85e82a4cc31e16e5
+21
View File
@@ -58,6 +58,27 @@ with DeepSeekHarness(dsh_home="/absolute/path/to/saved-home") as harness:
result = session.run("Continue the task.")
```
<a id="client-approvals"></a>
## Client approvals
This low-level example receives one approval and rejects it. Use a dedicated request-handling thread or loop for a full run: `next_request()` blocks, and the request handler must run concurrently with `session/wait` or a high-level `run()`. Set `approval_requests=True` on `HarnessClient.initialize()` or `DeepSeekHarnessConfig` only when that handler is ready. Omission preserves the runtime's answerer chain. Decisions are one-shot; invalid responses and lost connections fail closed, and cancellation discards late replies. `ApprovalRequestResult` validates response values when constructing decisions.
```py
from deepseek_harness import ApprovalRequestParams, HarnessClient, HarnessConfig
with HarnessClient(HarnessConfig(dsh_home="/absolute/path/to/dsh-home")) as client:
client.initialize(cwd="/absolute/path/to/workspace", provider="deepseek-official",
model="deepseek-v4-flash", approval_requests=True)
client.session_prompt("main", [{"type": "text", "text": "Run the operation requiring approval."}])
request = client.next_request()
if request.method == "approval/request":
question = ApprovalRequestParams.model_validate(request.payload)
print(question.toolName, question.reason)
client.respond(request.id, {"outcome": "rejected"})
else:
client.respond_error(request.id, code=-32601, message="Unsupported client request")
```
## Customize plugins
Persistent customization belongs to a `dsh` profile. Initialize the shipped SDK profile and install an external bundle with the runtime wheel's `dsh` command:
+21
View File
@@ -58,6 +58,27 @@ with DeepSeekHarness(dsh_home="/absolute/path/to/saved-home") as harness:
result = session.run("Continue the task.")
```
<a id="client-approvals"></a>
## 客户端审批
此底层示例接收一次审批并拒绝。完整运行应使用专门处理请求的线程或循环:`next_request()` 会阻塞,请求处理器必须与 `session/wait` 或高层 `run()` 并行运行。仅在处理器就绪时,在 `HarnessClient.initialize()` 或 `DeepSeekHarnessConfig` 设置 `approval_requests=True`;省略时保留运行时应答链。决定只作用于单次操作;无效响应和断连会拒绝放行,取消后丢弃迟到响应。构造决定时,可用 `ApprovalRequestResult` 校验响应值。
```py
from deepseek_harness import ApprovalRequestParams, HarnessClient, HarnessConfig
with HarnessClient(HarnessConfig(dsh_home="/absolute/path/to/dsh-home")) as client:
client.initialize(cwd="/absolute/path/to/workspace", provider="deepseek-official",
model="deepseek-v4-flash", approval_requests=True)
client.session_prompt("main", [{"type": "text", "text": "Run the operation requiring approval."}])
request = client.next_request()
if request.method == "approval/request":
question = ApprovalRequestParams.model_validate(request.payload)
print(question.toolName, question.reason)
client.respond(request.id, {"outcome": "rejected"})
else:
client.respond_error(request.id, code=-32601, message="Unsupported client request")
```
## 自定义插件
持久自定义属于 `dsh` profile。使用运行时 wheel 包提供的 `dsh` 命令初始化随附的 SDK profile,并安装外部 bundle:
+6 -1
View File
@@ -1,7 +1,10 @@
from .api import DeepSeekHarness, DeepSeekHarnessConfig, RunResult, Session
from .client import HarnessClient, HarnessConfig, UserQuestionHandler
from .errors import SdkProtocolError
from .models import IncomingRequest, InitializeResponse, JsonObject, Notification, ServerInfo, UserQuestionAnswer, UserQuestionAnswerItem, UserQuestionRequest
from .models import (
ApprovalRequestParams, ApprovalRequestResult, IncomingRequest, InitializeResponse,
JsonObject, Notification, ServerInfo, UserQuestionAnswer, UserQuestionAnswerItem, UserQuestionRequest,
)
__all__ = [
"DeepSeekHarness",
@@ -15,6 +18,8 @@ __all__ = [
"UserQuestionAnswer",
"UserQuestionAnswerItem",
"SdkProtocolError",
"ApprovalRequestParams",
"ApprovalRequestResult",
"IncomingRequest",
"InitializeResponse",
"JsonObject",
+2
View File
@@ -36,6 +36,7 @@ class DeepSeekHarnessConfig:
base_url: str | None = None
api_key: str | None = None
on_user_question: UserQuestionHandler | None = None
approval_requests: bool = False
@dataclass(slots=True)
@@ -112,6 +113,7 @@ class DeepSeekHarness:
model=self.config.model,
reasoning_effort=self.config.reasoning_effort,
max_tokens=self.config.max_tokens,
approval_requests=self.config.approval_requests,
)
self._initialized = True
@@ -156,12 +156,21 @@ class HarnessClient:
model: str,
reasoning_effort: str | None = None,
max_tokens: int | None = None,
approval_requests: bool = False,
) -> InitializeResponse:
"""Initialize the runtime; approval_requests enables approval/request reception.
When enabled, drain next_request() concurrently with blocking waits and
return one-operation decisions through respond(). Omission preserves the
runtime's composed approval answerers.
"""
payload: JsonObject = {
"cwd": str(Path(cwd).resolve()),
"provider": provider,
"model": model,
}
if approval_requests:
payload["approvalRequests"] = True
if reasoning_effort is not None:
payload["reasoningEffort"] = reasoning_effort
if max_tokens is not None:
+15 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TypeAlias
from typing import Literal, TypeAlias
from pydantic import BaseModel, Field, JsonValue as PydanticJsonValue
@@ -32,6 +32,20 @@ class InitializeResponse(BaseModel):
serverInfo: ServerInfo | None = None
class ApprovalRequestParams(BaseModel):
"""Server-to-client approval/request payload after explicit handshake opt-in."""
sessionId: str
toolName: str
callId: str | None = None
reason: str | None = None
class ApprovalRequestResult(BaseModel):
"""One-operation decision; allowed-once is the only grant."""
outcome: Literal["allowed-once", "rejected", "cancelled"]
class UserQuestionRequest(BaseModel):
"""Runtime question batch; questions retain their options, detail, and presentation intent."""
+55
View File
@@ -0,0 +1,55 @@
"""Approval request decoding and explicit wire capability negotiation."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
from pydantic import ValidationError
from deepseek_harness import ApprovalRequestParams, ApprovalRequestResult, DeepSeekHarnessConfig, HarnessClient
@pytest.mark.parametrize("enabled", [False, True])
def test_approval_handshake_and_response(tmp_path: Path, enabled: bool) -> None:
peer = tmp_path / "approval_peer.py"
peer.write_text('''import json
import sys
for line in sys.stdin:
msg = json.loads(line)
if msg.get("method") == "initialize":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"serverInfo": {"name": "test"}}}), flush=True)
print(json.dumps({"jsonrpc": "2.0", "method": "capability", "params": msg["params"]}), flush=True)
if msg["params"].get("approvalRequests"):
print(json.dumps({"jsonrpc": "2.0", "id": "ask", "method": "approval/request", "params": {"sessionId": "main", "toolName": "bash", "reason": "write"}}), flush=True)
elif msg.get("id") == "ask":
print(json.dumps({"jsonrpc": "2.0", "method": "decision", "params": msg["result"]}), flush=True)
elif msg.get("method") == "shutdown":
print(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {}}), flush=True)
break
''')
with HarnessClient(_launch_args=(sys.executable, str(peer))) as client:
client.initialize(cwd=str(tmp_path), provider="mock", model="mock", approval_requests=enabled)
payload = client.next_notification().payload
assert payload.get("approvalRequests", False) is enabled
if not enabled:
assert "approvalRequests" not in payload
return
request = client.next_request()
assert request.method == "approval/request"
question = ApprovalRequestParams.model_validate(request.payload)
assert question.toolName == "bash"
assert question.callId is None
client.respond(request.id, ApprovalRequestResult(outcome="rejected").model_dump())
assert client.next_notification().payload == {"outcome": "rejected"}
def test_approval_result_rejects_unrecognized_grants() -> None:
with pytest.raises(ValidationError):
ApprovalRequestResult(outcome="allow-always")
def test_approval_opt_in_preserves_existing_config_positional_arguments() -> None:
config = DeepSeekHarnessConfig("mock", "model", None, None, "/workspace")
assert config.cwd == "/workspace"
assert config.approval_requests is False
+52
View File
@@ -0,0 +1,52 @@
"""Python decisions control a guarded file operation through the built sdk profile."""
from __future__ import annotations
import json
import shutil
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import pytest
from pydantic import BaseModel
from deepseek_harness import ApprovalRequestParams, HarnessClient, HarnessConfig
class EmptyResponse(BaseModel):
pass
@pytest.mark.parametrize("outcome", ["allowed-once", "rejected"])
def test_python_approval_controls_runtime_operation(tmp_path: Path, outcome: str) -> None:
repo = Path(__file__).resolve().parents[3]
node = shutil.which("node")
binary = repo / "apps/cli/lib/bin.js"
if node is None or not binary.exists():
pytest.skip("requires Node and built dsh CLI")
fixture = repo / "packages/sdk/server/tests/fixtures/approval.mjs"
marker = tmp_path / "approved.txt"
patch = tmp_path / "approval.patch.yml"
patch.write_text("- insert:\n - id: approval-fixture\n name: " + json.dumps(str(fixture))
+ "\n config:\n stopAfterApproval: true\n path: " + json.dumps(str(marker)) + "\n")
# The client closes inside the executor lifetime, unblocking a reader even
# when a regression prevents the expected request from arriving.
with ThreadPoolExecutor(max_workers=1) as worker:
with HarnessClient(
HarnessConfig(cwd=str(repo), request_timeout_seconds=20, shutdown_timeout_seconds=20,
env={"DSH_HOME": str(tmp_path / "home"), "DSH_PERMISSION_MODE": "workspace-write",
"DSH_TELEMETRY_DISABLED": "1", "DEEPSEEK_API_KEY": "keyless-no-model-call"}),
_launch_args=(node, str(binary), "--profile", "sdk", "--patch", str(patch)),
) as client:
client.initialize(cwd=str(tmp_path), provider="deepseek-official", model="deepseek-v4-flash",
approval_requests=True)
client.session_prompt("main", [{"type": "text", "text": "Request the marker write."}])
request = worker.submit(client.next_request).result(timeout=20)
assert request.method == "approval/request"
question = ApprovalRequestParams.model_validate(request.payload)
assert question.sessionId == "main"
assert question.toolName == "fixture-write"
client.respond(request.id, {"outcome": outcome})
client.request("session/wait", {"sessionId": "main"}, response_model=EmptyResponse)
assert marker.exists() is (outcome == "allowed-once")
if marker.exists():
assert marker.read_text() == "approved\n"
+2
View File
@@ -165,6 +165,7 @@ for line in sys.stdin:
)
with DeepSeekHarness(
approval_requests=True,
model="deepseek-v4-flash",
reasoning_effort="max",
max_tokens=4096,
@@ -211,6 +212,7 @@ for line in sys.stdin:
assert dumped_env["DSH_SESSION_ROOT"] is None
assert dumped_env["DSH_CORDIS_CONFIG"] is None
assert json.loads(init_dump.read_text()) == {
"approvalRequests": True,
"cwd": str(tmp_path),
"provider": "deepseek-official",
"model": "deepseek-v4-flash",