Files
deepseek-harness/packages/ui/jsonrpc
Tianyi Cui 35473c2701 Merge branch 'codex/simp-unify-agent-session-id' into codex/simp-ui-identity-residue
# Conflicts:
#	AGENTS.md
#	docs/config-catalog.md
#	docs/event-producer-consumer.md
#	examples/acp-agent/tests/snapshots/advanced-toolchain/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/cancel/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/code-mode-turn/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/config-options/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/error-finish/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/escalation-approved/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/acp-agent/tests/snapshots/escalation-rejected/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-edit/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-policy-reject/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-read-window/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-read/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-terminal-card/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-write-overwrite/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/fs-write/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/handshake/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-posttool-block/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-posttool-context/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-block/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-stop-continue/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-posttool-block/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-posttool-context/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-pretool-block/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-block/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-stop-continue/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/multi-turn/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/repeat-tool-guard/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/subagent-fork/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/subagent-mixed/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/subagent-multi/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/subagent-spawn/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/todo-plan/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/tool-call-turn/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/workflow-run/stdout.golden.jsonl
#	examples/acp-agent/tests/snapshots/workspace-edit/stdout.golden.jsonl
#	packages/ui/acp/README.md
#	packages/ui/acp/src/index.ts
#	packages/ui/jsonrpc/README.md
#	packages/ui/jsonrpc/src/server.ts
2026-07-18 12:25:33 +08:00
..

@deepseek-ai/dsh-jsonrpc

The SDK server plugin (jsonrpc): mounting it serves a stdio JSON-RPC server that lets an out-of-process SDK client (e.g. the Python deepseek_harness package) drive DeepSeek Harness agents without touching Cordis. The client speaks newline-delimited JSON-RPC on the process stdin/stdout (HarnessSdkServer: initializesession/promptshutdown, with session.event / session.finished / subagent.* notifications over JsonRpcLineTransport). The SDK-client analogue of the acp bridge, split the same way: this package is the protocol plugin, while jsonrpc-demo is the app bin that boots a cordis.yml around it — which process serves this protocol is a config decision, not a hardcoded bin. This plugin is the serving face of the single-executable distribution plan.

Wiring

inject: ['agents']. The server gets or creates one agent per sessionId from the initialize.provider/initialize.model pair. It classifies subagent completions through live parent ownership or durable parent lineage and retains parent-scoped provider/id counts after child disposal. A registered owner for the provider route wins; an unowned deepseek route mounts dsh-llm-deepseek using $DEEPSEEK_API_KEY and $DEEPSEEK_BASE_URL, while any other unowned provider fails initialization. Persistence, tools, and other adapters come from the surrounding cordis.yml.

Config

No cordis.yml-settable keys. The JsonRpcConfig fields (input, output, exit) are runtime-only test seams so a spec can drive the server over in-memory streams without a subprocess or a killed test process; production always serves the process stdio and exits via process.exit.

stdout is the protocol

The process stdout this plugin runs in carries only JSON-RPC frames. The tree that loads it must load NO stdout logger (a console logger corrupts the frames) — the guarantee is config-only, same as the ACP bridge. Diagnostics go to stderr.

Shutdown and exit semantics

The plugin owns the PROTOCOL-level exit: a shutdown request is answered first (the response frame flushes), then the plugin disposes its own fiber — running the effect disposer: an idempotent server.shutdown() (every SDK-created agent disposed to quiescence, event subscriptions detached) plus transport.close() — and exits the process with code 0. Own-fiber disposal is deliberate: the request's server.shutdown() already flushed all SDK-owned session state, and the process exit that follows is the teardown of the rest of the tree. Process-level exits (stdin EOF → 0, SIGTERM → 0, SIGINT → 130) belong to the app bin, which disposes the whole root context. Fiber disposal WITHOUT a shutdown request (HMR-style unload) just stops serving — it never exits the process.

Wire notes

initialize.serverInfo.name is the wire-stable deepseek-harness-sdk-runtime (SDK clients key on it, independent of this package's name). A session accepts at most one in-flight session/prompt; an overlapping prompt for the same sessionId fails immediately through the standard handler-error response, while other sessions remain independent and the same session can be reused after the active prompt settles. Persistence roots and the deployment persona come from cordis.yml; the wire exposes only parameters the server applies.

Model Experience

SDK user message

What the model sees: For each accepted session/prompt, the conversation model receives the caller-supplied contentBlocks verbatim as one user message in that SDK session. This package adds no system-prompt prose or tool schema; those come from the plugins in the surrounding cordis.yml.

Token effect: Data-dependent user-message tokens enter retained session history and are resent on later turns until another package compacts them. The JSON-RPC frames, session notifications, and server bookkeeping add zero model-context tokens.

Known Limitations and Deferred Work

  • The wire has no per-session close or prompt-cancel method — SDK-created agents remain live until process shutdown, and one accepted prompt runs to agent idle before that session accepts another.
  • stdout purity is deployment-enforced — a surrounding config can still load a stdout logger and corrupt the JSON-RPC channel; this plugin does not inspect or veto sibling loggers.
  • Automatic adapter mounting is DeepSeek-specificinitialize can reuse any pre-registered model adapter, but its only fallback mounts dsh-llm-deepseek.