DSHarness Systems Dissection Pinned baseline 47f943859b · 36 verified / 0 in progress / 36 chapters
中文
Reliability and Product Surfaces·Chapter 29

API, SDK, JSON-RPC, and ACP

How browsers, automation, and external clients drive the same Agent runtime

VerifiedUpstream 47f943859bScope: Analyze Host remotes, Typert, WebSocket, stdio SDK, ACP mappings, event subscriptions, and protocol-boundary validation.

Conclusion: this is not “one API,” but four protocols with different ownership models

DeepSeek Harness exposes a browser Host API, generated Typert Remote APIs, a private SDK JSON-RPC protocol, and a standard ACP adapter. They share some Agent and Session capabilities, but they do not share the same stability, authentication, cancellation, reconnection, or lifecycle guarantees. The most dangerous reading is to treat them as interchangeable remote APIs merely because they all use JSON.

The shortest useful distinction is this: the browser API serves a UI shipped with the Host; Remote projects compile-time descriptors onto that same browser carrier; the SDK owns a local subprocess on behalf of its caller; and ACP serves automation clients that understand the standard protocol. When the wrong boundary is chosen, the first thing to become ambiguous is usually not the type system, but who owns the process, who may cancel work, and who restores state after a disconnect.

1. Four protocol surfaces: separate them by consumer and lifecycle first

SurfaceTypical consumerPhysical carrierSession ownerRole
Browser ApiProxyWeb client shipped with the HostHTTP POST uplink + two WebSocket downlinksHost processFull product UI contract
Typert RemoteClient plugins with compile-time selected contributionsReuses /api HTTP and the Host event streamHost Service / ContextStrict generated BFF surface
SDK JSON-RPCTypeScript and Python automation callersSubprocess stdio, JSONLSDK instanceMinimal private runtime wire
ACPStandard ACP clients or out-of-process child backendsStandard JSON-RPC over stdioACP connectionMinimal automation adapter
Inference

“They can all send a prompt” is not compatibility. Compatibility also includes handshake ordering, session recovery, error classification, cancellation propagation, credential boundaries, and state reconstruction after a disconnect.

2. Browser API grammar: a four-quadrant message model, not a loose set of fetch calls

ApiProxy separates messages from carriers into four forms: client-request, server-response, server-request, and client-response. The initiator creates an rpcId, and the responder echoes it unchanged. A server request may represent an approval or question that requires an answer, or a pure push; whether a reply is required is statically determined by the method. Client answers return through POST /api/respond, while late or duplicate responses receive only a carrier receipt.API-RPC-MODEL

Parsing follows a two-stage discipline. The first stage recognizes only the four envelope forms, rpcId, and the error union, leaving payload/value as unknown. The second stage parses the method-specific business schema. This lets the transport report a malformed envelope with correlation while preserving a precise business contract instead of diluting everything into one broad JSON schema.API-RPC-SCHEMAS

Fact

A business failure is not an exception; it is the error branch of RpcResult<T>. A transport exception may collapse to internal. HTTP status, RPC envelope, and business result therefore represent three distinct semantic layers.

3. Browser API version posture: ship together instead of negotiating the protocol

HostApi explicitly states that client and Host are shipped together, so there is currently no protocolVersion. host.describe.version is defined as the Host application version, not a wire version. Protocol version negotiation is deferred until independently shipped clients exist.API-HOST-VERSION-CONTRACT

Drift

The implementation does not currently return the real application version: a source TODO still returns the fixed value 0.0.1. It therefore cannot negotiate the protocol and, for now, cannot reliably identify the Host build either.API-HOST-VERSION-PLACEHOLDER

4. Browser carriers: HTTP uplink and WebSocket downlink have asymmetric error layers

The generic Remote/Connection RPC handler accepts only POST with application/json, and requires the URL endpoint to match the envelope method. Invalid envelopes and mismatched methods return typed bad-request results. An unexpected business-handler exception becomes a direct HTTP 500, which the browser carrier then treats as a transport failure. This scope excludes the event WebSocket upgrades and session-export route on the same /api surface.API-HTTP-HANDLERAPI-HTTP-CLIENT

Every /api request first passes Host authority, Fetch Metadata, and Origin fences. These defend against DNS rebinding and cross-site browser requests, but the source explicitly says they are not an authentication layer. Configuring a trusted host only permits requests bearing that authority through this fence; it proves neither network reachability nor requester identity.API-TRUST-FENCE

The trusted-host LAN surface does not give every method equal authority either. Privileged methods for settings, credentials, native dialogs, agent-preset management, and model discovery with draft credentials are separately pinned to loopback authority through an empty trusted-host set and still pass the shared Fetch-Metadata and Origin-authority checks; HTTP and both WebSocket upgrades pass the base trust fence. This is not a full scheme/host/port same-origin test: Origin may be absent, and when present only its authority is compared.API-PRIVILEGED-LOOPBACK

The HTTP bridge buffers the whole request body in memory, with a default 160 MiB limit. Browser disconnect is translated through response close into a Fetch AbortSignal, and the response stream observes socket drain for backpressure. The signal can notify downstream work, but it cannot forcibly stop in-process business logic that ignores cancellation.API-HTTP-BRIDGE

5. The two WebSockets are downlink-only streams; malformed frames and disconnects are different failures

The Host opens one downlink-only WebSocket for mux events and another for Host events. Any business message sent by the client closes the socket with a policy violation. If a source throws, the Host attempts to send stream/error first; on close it aborts the source and waits for all pumps to converge.API-WS-HOST

On the browser side, binary frames, invalid JSON, invalid envelopes, and invalid business frames are logged and dropped rather than terminating the generation. Schema corruption therefore does not automatically reconnect; only socket termination ends the stream.API-WS-CLIENT

Tradeoff

Dropping one bad frame prevents a local extension mismatch from taking down the connection, but if that frame carried a critical increment, repair depends on later reconnect/resync. The current generation has no per-frame replay.

6. Browser reconnection replaces the entire generation, not one broken socket

Each generation starts mux, Host, and host.describe concurrently. Normal readiness requires both streams to open and describe to succeed; if a proxy never emits open, a three-second guard allows degraded connection readiness. Provided describe/handshake converges, either stream ending aborts the whole generation and rebuilds both streams. Backoff grows exponentially from 500ms to a 10s cap, with delay jittered between half the cap and the cap. A successful handshake resets the attempt counter. A business sink exception is logged without killing the pump.API-CONNECTION-LOOP

Fact

The carrier has no resume token or per-frame offset. onConnected is the upper layer’s opportunity to reload lists and snapshots. The connection layer restores reachability, not business state.

7. Typert Remote: compile-time descriptors become live Service calls at runtime

The carrier-independent Remote request shape contains only namespace, method, exact named arguments, and a signal injected only for methods that declare cancellation. The Gateway also declares a stable set of boundary failure categories.REMOTE-GATEWAY-TYPES

For every invocation, the Host reloads the current strict descriptor, validates the field set, resolves lookup or Context identity, binds the current Service, and validates the result before returning it. If a strict endpoint appeared and was later withdrawn, the Gateway fails closed instead of falling back to weaker SRC inference. This prevents a hot-unloaded method name from silently acquiring a broader contract.REMOTE-HOST-DISPATCH

Cancellation is not universal: methods without a cancellation descriptor receive no signal. If a method that declares cancellation throws after its signal has aborted, the wire maps the outcome to cancelled. Existing RpcError values from lookup policy are preserved, while most other Gateway or business exceptions collapse to internal; rich in-process error categories therefore do not all cross the wire.REMOTE-HOST-ERRORS

8. Remote mounting, withdrawal, and events are explicit capabilities

A client contribution accepts only a strict codec. Before publishing methods, it checks for duplicate descriptors, namespaces, and Service conflicts; a partial installation failure rolls back in reverse order. Each mount token owns an AbortController. Withdrawal marks the mount inactive, aborts in-flight calls, and then removes the methods.REMOTE-CLIENT-MOUNT

The caller converts positional arguments into descriptor-named fields and combines the caller signal with mount lifetime. The returned value is parsed again on the client. At invocation, before entering the carrier try, a missing active Connection still throws directly; after a Connection exists, RPC rejection, abort, and malformed responses fold into the error branch of RemoteResult.REMOTE-CLIENT-INVOKE

Events are not forwarded automatically either. The current surface is an 11-item allowlist that preserves the original Cordis event name and argument list without projection, renaming, or redaction.REMOTE-EVENT-ALLOWLIST Before enqueueing, the Host proves only that the arguments are JSON-safe and then wraps them as host/remote-event.REMOTE-EVENT-FORWARD

Agent and Session identity lookup is part of the BFF lifecycle policy. A live Agent is reused directly; concurrent cold resumes of an ordinary Session are coalesced by ID. Before cold resume, subagent ownership is fenced at entry, after durable inspection, and immediately before calling resume. If a publication race makes resume fail, the catch path rechecks the published identity and maps the relevant collision to agent-busy, preventing the general API from taking over a child owned by another router.REMOTE-AGENT-RESOLVE

9. SDK JSONL: JSON-RPC 2.0 outbound, lenient classification inbound

The SDK transport writes "jsonrpc":"2.0" on outbound requests, responses, and notifications, with one compact frame per line. Inbound, it classifies frames only by the presence of id and method; it does not validate the jsonrpc field or a complete JSON-RPC envelope. StringDecoder prevents a chunk boundary from splitting multibyte UTF-8. Invalid JSON lines, non-objects, and unknown response IDs are ignored. A missing handler returns -32601; a handler rejection returns -32603.SDK-JSONL-TRANSPORT

The application vocabulary has only three client requests: initialize, session/prompt, and shutdown; and four server notifications: session.event, session.status, subagent.started, and subagent.finished. A prompt response is only a durable queue receipt containing messageId. The full SessionEvent crosses the wire, making the Session event vocabulary part of the compatibility surface too.SDK-WIRE-METHODS

initialize ───────────────→ serverInfo
session/prompt ───────────→ messageId  (queue receipt only)
       ├─ session.event ──→ durable facts
       ├─ session.status ─→ whole-agent running / idle
       └─ subagent.* ─────→ in-process child lineage / completion
shutdown ─────────────────→ {}

10. SDK compatibility and security posture: no version negotiation, wire auth, or prompt cancellation

The published limitations state directly that serverInfo.version is fixed at 0.0.1, clients do not compare it with an expected version, prompt cancellation and session close do not exist, and server-to-client requests have no current producer. The transport retains that last capability only for a future approval flow.SDK-WIRE-LIMITS

Fact

The SDK security boundary is caller ownership of a local subprocess and its stdio, not an online identity protocol. The wire has no authenticate method. Model credentials enter through the child environment or runtime configuration, so environment construction is part of the credential policy.

Inference

At the pre-release stage, equal package versions alone do not prove wire compatibility. The safest posture is to ship and upgrade SDK and runtime together, and treat serverInfo as diagnostic information rather than a negotiated result.

11. SDK server: global event sources, with session-tree filtering delegated to clients

At construction, the server subscribes to every Session event and Agent status in the Context, not only Sessions created by the SDK. Child-created events are likewise reported globally. A subagent-finished notification is emitted only when the service snapshot says local is true; locality cannot be inferred from a matching ID or parent lineage.SDK-SERVER-EVENTS

initialize parses cwd, stores provider/model/maxTokens, and automatically mounts the DeepSeek fallback only when no owner exists. The class comment says reinitialization is unsupported, but the implementation has no handshake-state field; handleRequest also does not prevent a pre-initialize prompt.SDK-SERVER-INIT

A Session is created lazily on its first prompt, and concurrent creation for the same ID is coalesced through a promise map. Every prompt verifies that the retained handle still equals the live registry instance. Shutdown closes admission, waits for creation to converge, removes subscriptions, and uses allSettled for every Agent and the fallback adapter, reporting multiple failures together.SDK-SERVER-SESSIONS

After the protocol shutdown response is written, the next event-loop turn flushes the transport, disposes the root fiber, and calls exit(0). The successful plugin-only unmount path shuts down the server, then closes the transport without exiting the process; however, the disposer has no finally, so a server-teardown rejection skips transport close.SDK-SERVER-SHUTDOWN

12. TypeScript SDK: run spans receipt-to-idle, not a one-to-one turn result

The low-level HarnessClient spawns the runtime lazily. Initialize requires only that the returned name and version are strings; it does not validate expected values. A request timeout only abandons the local pending-map entry, while server work continues until the runtime is closed. JSON-RPC errors preserve code/data, and transport failures add the exit code plus at most 400 lines of stderr tail.SDK-TS-CLIENT

High-level run() subscribes to the Session tree before sending the prompt. After receiving messageId, it drops notifications before the matching durable inbox receipt and then collects until the root Agent next becomes idle. finalResponse is the last committed assistant text in that interval. Work admitted by another producer before idle may also contribute, so it is not a response uniquely attributable to that prompt.SDK-TS-RUN

Close is a locally authoritative termination sequence: bounded best-effort shutdown, then stdin EOF → POSIX SIGTERM → SIGKILL, resolving only after process exit is observed. Windows proceeds directly to the forced-termination tier.SDK-TS-DISPOSE

13. The Python SDK speaks the same wire, but is not a mechanical translation of the TypeScript API

Python HarnessClient is synchronous and sends the same three requests. It additionally exposes next_request(), respond(), and respond_error() for future server requests; the current server does not send such a request. Python copies the parent environment and then merges explicit overrides by default, unlike TypeScript, where an explicit env replaces the whole environment. Its initialize-response model also permits serverInfo, name, and version to be absent.SDK-PY-CLIENTSDK-PY-MODELS

The reader thread routes responses by ID into waiter queues, fans notifications out to subscriptions, and places frames with id+method into a server-request queue. Timeout removes the waiter and adds exit/stderr diagnostics, but likewise sends no cancellation to the runtime. A filter exception terminates only its own subscription.SDK-PY-READER

High-level Python Session.run() also uses a receipt-to-idle interval, but additionally extracts the final turn/end.reason.kind as finish_reason. A missing string kind raises SdkProtocolError. This is still an interval summary, not a prompt-specific verdict.SDK-PY-RUN

DifferenceTypeScriptPythonImpact
Explicit envReplaces the complete child envCopies parent, then mergesCredential inheritance differs
serverInfo validationRequires name/version stringsBoth fields may be absentDifferent acceptance of a malformed runtime
RunResultNo finish reasonIncludes finish_reason and session_rootHigh-level APIs are not isomorphic
Server requestNo consumer APIGeneric queue/respond APIA dead capability today, with different extension posture
Close/reuseDefault six-second configurable EOF grace before termination escalation; low-level client is permanently closedTerminates immediately after the shutdown response if still alive; the same instance can start again, but close sentinels left in the global-notification and server-request queues are not clearedPython may TERM while runtime root/persistence disposal is still running; the first low-level queue read after restart may also observe an old sentinel

The TypeScript public type fixes env replacement and a four-field RunResult.SDK-TS-RESULT Python configuration adds session-root, base-URL, and API-key conveniences, and returns additional fields.SDK-PY-RESULT

14. SDK drift and enforcement gap: documented intent is not a wire state machine

Drift

SdkProtocolError JSDoc still uses “prompt response missing accepted:true” as an example, but the current wire and implementation require messageId. This is a stale local comment, not dual-format compatibility.SDK-DOC-STALE-ACCEPTED

Enforcement gap

Reinitialization being “unsupported” exists only in the class comment. The source rejects neither a second initialize nor a prompt before initialize. Official SDK wrappers handshake in order, so the normal path hides the gap; any raw JSON-RPC client can reach it.

Impact

A second initialize rewrites cwd/provider/model/maxTokens for later new Sessions, while existing Sessions retain the old configuration, producing mixed generations within one connection. The fix should be an explicit connection state machine, not a client convention to avoid the sequence.

15. ACP: a standard protocol version with a deliberately narrow automation baseline

The adapter is a public 0.1.0-rc.5 package and pins @agentclientprotocol/sdk 0.25.1.ACP-PACKAGE At runtime it imports PROTOCOL_VERSION, ndJsonStream, and AgentSideConnection directly from that SDK, then constructs the connection over the standard stream. Framing, method machinery, and the protocol constant therefore come from the SDK rather than a custom wire.ACP-SDK-IMPORTSACP-SDK-CONNECTION

Regardless of which version the client requests, initialize returns the PROTOCOL_VERSION supported by this build, advertises only the capabilities needed for a text/resource-link baseline, returns an empty authMethods, and implements authenticate as a no-op. session/new creates only fresh Sessions, requires an absolute cwd, and rejects nonempty additional directories and MCP servers.ACP-HANDSHAKE

Fact

ACP “standards compatibility” means a standard envelope plus baseline method compatibility; it does not imply every optional ACP capability. Clients must inspect capabilities and the returned protocol version instead of assuming resume, filesystem, terminal, or MCP support from the protocol name.

16. ACP output and errors: committed answers only, trading token latency for a clean result

The bridge does not forward raw deltas, reasoning, tool traces, plans, or titles. It emits only committed assistant/message text as agent_message_chunk, and turns images into explicit attachment placeholders. A transport/write rejection from sessionUpdate() is caught and warned; because a JSON-RPC notification has no response, a client-handler failure cannot feed back over that notification. For tool-call approvals it owns, the bridge sends a one-shot allow/reject permission request; cancellation or an unknown choice does not grant permission.ACP-OUTPUT-PERMISSION

Each Session permits only one in-flight prompt. The bridge creates a slot keyed by the user messageId and captures its turn on agent/inbox/claimed. turn/end for that captured turn is correlated exactly, while ordinary endings still wait for whole-agent idle. A separate agent/error fallback rejects the current slot only when the turn is not yet captured or differs, with no message-level causality: a prior autonomous-turn failure, or a competing-turn failure after the prompt turn but before idle, may be misattributed to the waiting ACP prompt. Output likewise includes every committed assistant message for that Session through idle, not only the prompt turn. session/cancel is a no-op for an unknown ID; for a known ID it cancels only the corresponding Agent and immediately settles that prompt as cancelled.ACP-PROMPT-CANCEL

The content codec concatenates text unchanged, renders a resource link as a bracketed reference containing its name and URI, and rejects richer blocks. Mapping turn endings to ACP stop reasons is lossy. The codec itself maps max-token to max_tokens, but at whole-agent-idle settlement the prompt wrapper separately lowers it to end_turn because it does not claim a prompt-specific turn outcome.ACP-CODECACP-PROMPT-CANCEL

17. ACP lifecycle: one connection may own multiple Sessions, but there is no per-Session close

Connection close and plugin disposal share one memoized quiesce operation. It synchronously closes admission, clears the map, cancels Agents, and settles every prompt. It then drains only continuable descendants beneath those top-level Agents and disposes handles in parallel. Descendant-drain failure only warns and continues; only handle-disposal failures are aggregated and thrown. Forests owned by other frontends in the same Context are not swept accidentally.ACP-QUIESCE

Tradeoff

This provides connection-owned isolation and deterministic ownership/teardown ordering on the successful path. Top-level handle disposal continues even after descendant-drain failure, but complete descendant release is then unproven. There is no session/load, resume, fork, or per-Session close. A long-lived client must treat the connection itself as the resource container instead of treating each Session as an independently reclaimable remote object.

18. ACP is also a child backend: every run is a fresh-process transaction

The out-of-process provider explicitly advertises no output-schema, depth-limit, tool-filter, or persona support, and does not inherit the parent conversation. Child cwd must come from explicit configuration or the delegating parent Session. Missing workspace fails loudly instead of falling back to server-process cwd.ACP-CHILD-CAPABILITIES

Every run starts a fresh process. The parent creates a separate lifecycle ID so identical ACP Session IDs in different children cannot collide. A handle is published only after spawn, initialize, and session/new all succeed; an error or cancellation before that point first reclaims the private process. A shared subprocess policy removes credential-shaped and DSH_* ambient names, after which the local spawn layer merges explicit child env.ACP-CHILD-STARTACP-CHILD-ENV-POLICYACP-CHILD-ENV-MERGE

Caller abort may immediately return the text collected so far with aborted, while sending best-effort ACP cancel; this does not prove the child has stopped. dispose() is the authoritative quiescence path: it ensures best-effort cancellation has been requested if not already, then performs stdin EOF → termination escalation → whole-tree exit proof.ACP-CHILD-CANCEL

19. Version, authentication, cancellation, reconnect, and recovery matrix

DimensionBrowser API / RemoteSDK JSON-RPCACP adapter
VersionShipped together; no protocolVersion; describe version remains a placeholderserverInfo 0.0.1; no negotiation or expected-value comparisonStandard SDK PROTOCOL_VERSION; capability subset
AuthenticationHost/Origin trust fence, not identity authenticationNo wire auth; relies on local subprocess ownershipEmpty authMethods; trusted stdio
CancellationHTTP disconnect/caller signal; injected only for declared methods; cooperativeTimeout only abandons locally; closing the runtime is required to terminatesession/cancel routes to the target Agent and settles the prompt
ReconnectAfter readiness converges, rebuild both streams as one generation + upper-layer snapshot resync; hung describe can wedge convergenceTransport death after handshake is terminal; no automatic reconnectNo connection resume; disconnect releases owned Sessions
Session recoveryBFF may cold-resume an ordinary durable SessionReuses by sessionId inside one runtime; no wire load/resumeFresh only
Process teardownOwned by the HostSDK shutdown + EOF/TERM/KILLServer connection quiesce; child backend separately reaps the whole tree
Result semanticsTyped RPC result + event streamsmessageId receipt; high-level receipt-to-idle intervalCommitted chunks + stopReason, without claiming a prompt-causal turn

20. Comparison with the public Codex App Server contract

DimensionDeepSeek Harness (source reviewed here)Codex (public documentation only)
PurposeSeparate browser surface, narrow SDK, and ACP adapterApp Server for rich clients; SDK for automation/CI
JSON-RPC framingSDK writes a 2.0 header outbound but classifies inbound leniently; ACP uses standard SDK framing; browser API has a custom envelopeBidirectional JSON-RPC 2.0, but omits the jsonrpc field on the wire; stdio is JSONL, with experimental WebSocket and Unix-socket transports
HandshakeSDK server lacks an enforced state machine; ACP returns the standard versionExactly one initialize per connection followed by initialized; requests before handshake and repeated initialize are rejected
SessionsSDK implicit get/create; ACP fresh only; Browser BFF may cold-resumePublic thread start/resume/fork/read/list and turn start/steer/interrupt
Cancellation and resultSDK has no wire cancel; ACP has session/cancel; Browser injects signals by descriptorAfter turn/interrupt, convergence occurs through turn/completed with final status
Remote authenticationThe adapters covered here expose no public network identity authenticationRemote WebSocket may require a capability token or signed bearer authentication before initialize
Compatibility strategyBrowser ships atomically; SDK pre-release has no negotiation; ACP exposes a capability subsetTypeScript/JSON Schema can be generated for the exact Codex version being run; experimental methods and fields require capability opt-in
BackpressureBrowser HTTP/WS and SDK handle it separately; SDK has no overload codeA full WebSocket ingress queue returns -32001, with documented exponential backoff + jitter
Server requestsBrowser approvals can be answered; ACP permissions exist; SDK capability is currently deadPublic approval and other server-initiated requests receive a client decision
Public fact

The Codex information above comes from the official Codex App Server documentation (retrieved 2026-08-13). This comparison covers public protocol capabilities only and makes no inference about private implementation.

Assessment

Codex App Server resembles a rich-client protocol with handshake state, version-matched schemas, long-lived threads/turns, and explicit approvals. The DeepSeek SDK resembles a minimal subprocess automation pipe, while its ACP adapter trades product breadth for standards compatibility. This is not merely a difference in feature count; it reflects different expected client lifetimes and degrees of independent release.

21. Choosing safely: select by required guarantees, not familiar syntax

NeedPreferYou must still supply
Full Web UI shipped with the productBrowser ApiProxy + RemoteReal identity authentication, cross-version deployment discipline, snapshot resync after reconnect
Local scripts, CI, batch automationTypeScript/Python SDKSame-version runtime pinning, process close, and a policy for whether timeout terminates the whole runtime
Generic agent-client integrationACPDepend only on advertised capabilities; accept fresh-only, committed-only, connection-owned lifetime
Out-of-process child AgentACP child backendExplicit cwd, explicit credentials, permission policy, and unconditional disposal
Independently shipped rich clientNo equivalent unified DeepSeek protocol todayVersion negotiation, authentication, resume, schema generation, overload/replay contract
Core tradeoff

A minimal protocol reduces implementation and token-surface cost, but pushes state ownership, upgrades, and recovery onto the client. A richer protocol supports an independent client lifecycle, but must carry capability negotiation, authentication, backpressure, replay, and a larger compatibility test matrix.

22. Verification scope and reproducible result

This chapter uses commit 47f943859bef60e4160492346772ded9b24f765a as its fixed baseline. Focused Vitest coverage includes SDK transport/server/client, ACP bridge/codec/permission/multi-session/disposal, the ACP child backend, Typert Gateway, Remote identity resolution, and browser trust/HTTP bridge/WebSocket/reconnect. The SDK transport suite pins framing and error semantics.TEST-SDK-PROTOCOL The ACP bridge suite pins version, auth, fresh-session, and input boundaries.TEST-ACP

The connection suite pins two-stream generations and reconnect behavior.TEST-CONNECTION The Remote client suite pins strict mounting, withdrawal cancellation, scope, and error folding.TEST-REMOTE

pnpm exec vitest run \
  packages/sdk/protocol/tests/transport.spec.ts \
  packages/sdk/server/tests/{server,plugin-apply,plugin-shape}.spec.ts \
  packages/sdk/client/tests/{sdk-client,dispose}.spec.ts \
  packages/acp/acp/tests/{approval,bridge,codec,dispose,edges,multi-session,turns}.spec.ts \
  packages/subagent/subagent-acp/tests/subagent-acp.spec.ts \
  packages/api/gateway/tests/{gateway.client,gateway.host}.spec.ts \
  packages/api/remotes/tests/agent-lookup.spec.ts \
  packages/client/connection/tests/{api-request-trust.host,http-bridge.host,websocket-downlink.host,connection.client}.spec.ts \
  --reporter=verbose

Result: all 21 test files passed; 313/313 tests passed.
Duration: 21.01s (reported by Vitest).

My Learning Notes

Autosaved only in this browser. Nothing is uploaded or committed. Export Markdown whenever you want to keep a copy.