fix(sdk-client): address ds-review-bot findings
- api: resolve a relative workspace cwd to absolute before the handshake — the child spawns relative to the parent cwd, but the wire cwd is resolved again inside the child, so a relative value double-resolved (worker -> worker/worker). - api: make the documented handshake retry real — HarnessClient.close() is permanent, so a failed initialize now reaps the runtime and swaps in a fresh client; DeepSeekHarness.close() is terminal and stops the respawns. - api: validate session.event envelopes, assistant/message content, and session.finished reasons at the wire boundary — a malformed runtime surfaces as SdkProtocolError instead of type-invalid TurnResult data or a TypeError out of finalResponse. - client: a throwing subscribe() filter fails and detaches only its own subscription (normalized to Error); sibling fan-out and the transport read loop are undisturbed. - client: NotificationSubscription.close() drops its queued notifications, matching its documented contract; runtime-death fail() still leaves already-delivered items drainable. - client: subscribe() after close()/runtime death returns a born-failed subscription so next() rejects instead of parking forever. - client/transport: bounded requests abandon via AbortSignal — the transport drops the pending entry at timeout, so repeated bounded calls against a hung method retain no per-call state. One test per finding; per-file coverage stays 100% on both packages.
This commit is contained in:
@@ -60,6 +60,29 @@ describe('JsonRpcLineTransport', () => {
|
||||
b.close()
|
||||
})
|
||||
|
||||
it('rejects immediately on a pre-aborted signal without registering pending state', async () => {
|
||||
const { b } = transportPair()
|
||||
b.start()
|
||||
const controller = new AbortController()
|
||||
controller.abort(new Error('already gone'))
|
||||
await expect(b.request('never-sent', {}, controller.signal)).rejects.toThrow('already gone')
|
||||
expect((b as unknown as { pending: Map<string, unknown> }).pending.size).toBe(0)
|
||||
b.close()
|
||||
})
|
||||
|
||||
it('abandons a pending request on abort, stringifying a non-Error reason', async () => {
|
||||
const { b } = transportPair()
|
||||
b.start()
|
||||
const controller = new AbortController()
|
||||
const pending = b.request('never-answered', {}, controller.signal)
|
||||
controller.abort('plain-string-reason')
|
||||
await expect(pending).rejects.toThrow('JSON-RPC request aborted: plain-string-reason')
|
||||
// The abandonment removed the pending entry — nothing is retained for a
|
||||
// response that may never come.
|
||||
expect((b as unknown as { pending: Map<string, unknown> }).pending.size).toBe(0)
|
||||
b.close()
|
||||
})
|
||||
|
||||
it('preserves structured error data from an error response frame', async () => {
|
||||
const { aToB, bToA, b } = transportPair()
|
||||
b.start()
|
||||
|
||||
Reference in New Issue
Block a user