Merge observed master checkpoint to resolve Python smoke pairing conflict

This commit is contained in:
Tianyi Cui committed 2026-10-05 09:05:54 +08:00
commit 3ca9334868
2054 files changed
+34974 -82416

No files matched your search

+144 -6
View File
@@ -105,6 +105,11 @@ RESTART_SECOND_PROMPT = "Complete the second isolated Python SDK process turn."
RESTART_SECOND_TEXT = "PROCESS_TWO_OK"
RESTART_FIRST_SESSION_ID = "process-one"
RESTART_SECOND_SESSION_ID = "process-two"
RECOVERY_PROMPT = "Run three todo updates; the scheduler will reject the second call."
RECOVERY_CONTINUE_PROMPT = "Continue after the scheduler failure without repeating any tools."
RECOVERY_TEXT = "SCHEDULER_RECOVERY_OK"
RECOVERY_SESSION_ID = "scheduler-recovery"
RECOVERY_CALL_IDS = ("scheduler-complete", "scheduler-fail", "scheduler-unstarted")
SNAPSHOT_WORKFLOW_SCRIPT = (
"phase('Delegate')\n"
f"const reply = await agent('{SNAPSHOT_WORKFLOW_CHILD_PROMPT}', {{ label: 'workflow-child' }})\n"
@@ -136,6 +141,10 @@ RESTART_SNAPSHOT_DIRECTORY = (
RESTART_SNAPSHOT_FILENAMES = (
"result.json", "requests.json", "session.1.v3.jsonl", "session.2.v3.jsonl",
)
RECOVERY_SNAPSHOT_DIRECTORY = (
Path(__file__).resolve().parent / "snapshots" / "python-sdk-single-exe" / "scheduler-recovery"
)
RECOVERY_SNAPSHOT_FILENAMES = ("result.json", "requests.json", "session.v4.jsonl")
MCP_SERVER_SCRIPT = """\
import json
import os
@@ -316,6 +325,32 @@ def completion_chunks(body: dict[str, object]) -> list[dict[str, object]]:
if not isinstance(latest, dict):
raise AssertionError(f"model request has an invalid latest message: {body}")
recovery_prompt = next((
block.get("text")
for block in latest.get("content", [])
if isinstance(block, dict) and block.get("type") == "text"
and block.get("text") in {RECOVERY_PROMPT, RECOVERY_CONTINUE_PROMPT}
), None)
if recovery_prompt == RECOVERY_PROMPT:
assert_advertised_tool(body, "todo_write")
chunks = [message_start()]
for index, call_id in enumerate(RECOVERY_CALL_IDS):
call = tool_call_chunks(call_id, "todo_write", {"todos": []})
chunks.extend({**chunk, "index": index} for chunk in call[1:-2])
chunks.extend(call[-2:])
return chunks
if recovery_prompt == RECOVERY_CONTINUE_PROMPT:
results = [
block for message in messages
for block in message.get("content", [])
if isinstance(block, dict) and block.get("type") == "tool_result"
]
if [result.get("tool_use_id") for result in results] != list(RECOVERY_CALL_IDS):
raise AssertionError(f"recovered history lost tool-result pairing: {results}")
if [result.get("is_error", False) for result in results] != [False, True, True]:
raise AssertionError(f"recovered history changed completed or pending outcomes: {results}")
return text_chunks(RECOVERY_TEXT)
tool_results = [
block for block in latest.get("content", [])
if isinstance(block, dict) and block.get("type") == "tool_result"
@@ -743,7 +778,7 @@ def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--scenario",
choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-dynamic-tools", "sdk-fs-search", "sdk-spawn-node", "sdk-mcp", "sdk-snapshot", "sdk-restart", "sdk-profile-plugin", "sdk-office", "sdk-authoring", "sdk-live", "runner", "direct"),
choices=("all", "sdk-default", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-dynamic-tools", "sdk-fs-search", "sdk-spawn-node", "sdk-mcp", "sdk-snapshot", "sdk-recovery", "sdk-restart", "sdk-profile-plugin", "sdk-office", "sdk-authoring", "sdk-live", "runner", "direct"),
default="all",
)
parser.add_argument("--exe", type=Path)
@@ -762,10 +797,10 @@ def main() -> None:
parser.error("--scenario sdk-profile-plugin requires --installed-wheel")
if args.installed_wheel:
args.exe = assert_installed_wheel_environment()
if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-dynamic-tools", "sdk-fs-search", "sdk-spawn-node", "sdk-snapshot", "sdk-restart", "sdk-office", "sdk-authoring", "runner", "direct"} and args.exe is None:
parser.error("--exe is required for custom, minimal, dynamic-tools, fs-search, spawn-node, snapshot, restart, office, runner, and direct scenarios")
if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-minimal-in-history", "sdk-dynamic-tools", "sdk-snapshot", "sdk-restart", "sdk-authoring"}:
parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-minimal-in-history, sdk-dynamic-tools, sdk-snapshot, sdk-restart, sdk-authoring, or all")
if args.scenario in {"all", "sdk-custom", "sdk-minimal", "sdk-minimal-in-history", "sdk-dynamic-tools", "sdk-fs-search", "sdk-spawn-node", "sdk-snapshot", "sdk-recovery", "sdk-restart", "sdk-office", "sdk-authoring", "runner", "direct"} and args.exe is None:
parser.error("--exe is required for custom, minimal, dynamic-tools, fs-search, spawn-node, snapshot, recovery, restart, office, runner, and direct scenarios")
if args.update_snapshots and args.scenario not in {"all", "sdk-minimal", "sdk-minimal-in-history", "sdk-dynamic-tools", "sdk-snapshot", "sdk-recovery", "sdk-restart", "sdk-authoring"}:
parser.error("--update-snapshots requires --scenario sdk-minimal, sdk-minimal-in-history, sdk-dynamic-tools, sdk-snapshot, sdk-recovery, sdk-restart, sdk-authoring, or all")
if args.exe is not None and not args.exe.is_file():
parser.error(f"runtime executable does not exist: {args.exe}")
@@ -817,6 +852,9 @@ def main() -> None:
if args.scenario in {"all", "sdk-snapshot"}:
assert args.exe is not None
smoke_sdk_snapshot(model.url, args.exe.resolve(), args.update_snapshots)
if args.scenario in {"all", "sdk-snapshot", "sdk-recovery"}:
assert args.exe is not None
smoke_sdk_scheduler_recovery(model.url, args.exe.resolve(), args.update_snapshots)
if args.scenario in {"all", "sdk-restart"}:
assert args.exe is not None
smoke_sdk_restart_snapshot(model.url, args.exe.resolve(), args.update_snapshots)
@@ -1640,6 +1678,94 @@ def smoke_sdk_restart_snapshot(base_url: str, executable: Path, update_snapshots
)
def smoke_sdk_scheduler_recovery(base_url: str, executable: Path, update_snapshots: bool) -> None:
"""Keep a failed tool turn usable through the real Messages serializer and SDK."""
from deepseek_harness import DeepSeekHarness
first_request = len(MockModelHandler.requests)
with tempfile.TemporaryDirectory(prefix="dsh-sdk-scheduler-recovery-") as temporary:
root = Path(temporary).resolve()
dsh_home = root / "home"
sessions = dsh_home / "sessions"
base_patch = write_advanced_profile_patch(root, "base.patch.yml", sessions)
patch = write_profile_patch(root, "recovery.patch.yml", sessions, [
{"id": "tool-todo", "disabled": False},
{"id": "session-title-llm", "disabled": True},
{"id": "session-log-deepseek", "config": {"enabled": False}},
{"insert": [{
"id": "scheduler-failure-fixture",
"name": (
Path(__file__).resolve().parent.parent
/ "snapshots/sdk/tool-scheduler-recovery/scheduler-failure.mjs"
).as_uri(),
}]},
])
def connect() -> DeepSeekHarness:
return DeepSeekHarness(
provider="deepseek-official", model="smoke-model", cwd=str(root),
dsh_bin=str(executable), dsh_home=str(dsh_home),
patches=(str(base_patch), str(patch)),
env={"DSH_PERMISSION_MODE": "danger-full-access", "DSH_TELEMETRY_DISABLED": "1"},
api_key="sk-keyless-smoke", base_url=base_url, request_timeout_seconds=60,
)
with connect() as harness:
failed = harness.run(RECOVERY_PROMPT, session_id=RECOVERY_SESSION_ID)
continued = harness.run(RECOVERY_CONTINUE_PROMPT, session_id=RECOVERY_SESSION_ID)
results = [failed, continued]
if [result.finish_reason for result in results] != ["error", "completed"]:
outcomes = [
event["data"]["reason"] for result in results
for event in result.events if event.get("type") == "turn/end"
]
raise AssertionError(f"scheduler recovery did not preserve turn outcomes: {outcomes}")
if continued.final_response != RECOVERY_TEXT:
raise AssertionError("scheduler recovery lost the continued response")
requests = MockModelHandler.requests[first_request:]
if len(requests) != 2:
raise AssertionError(f"scheduler recovery expected two serialized model requests: {requests}")
logs = read_session_logs(sessions)
records = logs[RECOVERY_SESSION_ID]
calls = [record["data"]["callId"] for record in records if record.get("type") == "tool/call"]
if calls != list(RECOVERY_CALL_IDS[:2]):
raise AssertionError(f"scheduler recovery dispatched the unstarted call: {calls}")
tool_results = [
record["data"]["message"]
for record in records if record.get("type") == "tool/result"
]
if [(message.get("role"), message.get("toolCallId"), message.get("isError", False)) for message in tool_results] != [
("tool", RECOVERY_CALL_IDS[0], False), ("tool", RECOVERY_CALL_IDS[1], True), ("tool", RECOVERY_CALL_IDS[2], True),
]:
raise AssertionError(f"scheduler recovery duplicated or lost tool outcomes: {tool_results}")
if sum(record.get("type") == "todo/write" for record in records) != 1:
raise AssertionError("scheduler recovery executed an unstarted todo update")
replacements = [(str(root), "{{cwd}}"), (RECOVERY_SESSION_ID, "{{parent}}")]
result_value = [{
"session_id": result.session_id,
"final_response": result.final_response,
"finish_reason": result.finish_reason,
"eventTypes": [event.get("type") for event in result.events],
"notificationMethods": [notification.method for notification in result.notifications],
} for result in results]
request_value = [{
"model": request.get("model"),
"messages": restart_request_messages(request),
} for request in requests]
normalized_records = project_session_snapshot([
normalize_snapshot_value(record, replacements) for record in records
])
content = render_jsonl(normalized_records)
files = {
"result.json": json.dumps(normalize_snapshot_value(result_value, replacements), indent=2) + "\n",
"requests.json": json.dumps(normalize_snapshot_value(request_value, replacements), indent=2) + "\n",
snapshot_session_filename(0, session_header_version(content, "scheduler recovery")): content,
}
compare_snapshot_files(
files, update_snapshots, RECOVERY_SNAPSHOT_DIRECTORY, RECOVERY_SNAPSHOT_FILENAMES,
)
def smoke_direct(base_url: str, executable: Path) -> None:
with tempfile.TemporaryDirectory(prefix="dsh-direct-") as temporary:
root = Path(temporary).resolve()
@@ -2432,7 +2558,19 @@ def normalize_snapshot_value(
if isinstance(dt, list):
member["dt"] = [0] * len(dt)
if isinstance(normalized.get("id"), str) and normalized.get("role") in ("assistant", "system", "user", "tool", "developer"):
if not normalized["id"].startswith("{{message:"):
# Replay fixtures retain deterministic repair ids with the call id and original event sequence.
source = normalized.get("source")
call_id = normalized.get("toolCallId") if normalized.get("role") == "tool" else (
source.get("callId") if isinstance(source, dict) and source.get("kind") == "tool" else None
)
repair_prefix = (
f"interrupted-tool-result-{call_id}-" if isinstance(call_id, str) else None
)
repair_id = (
repair_prefix is not None and normalized["id"].startswith(repair_prefix)
and normalized["id"][len(repair_prefix):].isdigit()
)
if not normalized["id"].startswith("{{message:") and not repair_id:
normalized["id"] = "{{messageId}}"
if normalized.get("type") in ("feedback/message-put", "feedback/message-delete"):
data = normalized.get("data")