diff --git a/integration-tests/sdk-typescript/permission-control.test.ts b/integration-tests/sdk-typescript/permission-control.test.ts index 31c7768a..adeb6792 100644 --- a/integration-tests/sdk-typescript/permission-control.test.ts +++ b/integration-tests/sdk-typescript/permission-control.test.ts @@ -555,6 +555,15 @@ describe('Permission Control (E2E)', () => { ...SHARED_TEST_OPTIONS, cwd: testDir, permissionMode: 'default', + timeout: { + /** + * We use a short control request timeout and + * wait till the time exceeded to test if + * an immediate close() will raise an query close + * error and no other uncaught timeout error + */ + controlRequest: 5000, + }, }, }); @@ -563,7 +572,9 @@ describe('Permission Control (E2E)', () => { await expect(q.setPermissionMode('yolo')).rejects.toThrow( 'Query is closed', ); - }); + + await new Promise((resolve) => setTimeout(resolve, 8000)); + }, 10_000); }); describe('canUseTool and setPermissionMode integration', () => { diff --git a/packages/sdk-typescript/README.md b/packages/sdk-typescript/README.md index f5b8fc8e..38f5a375 100644 --- a/packages/sdk-typescript/README.md +++ b/packages/sdk-typescript/README.md @@ -13,7 +13,7 @@ npm install @qwen-code/sdk-typescript ## Requirements - Node.js >= 20.0.0 -- [Qwen Code](https://github.com/QwenLM/qwen-code) installed and accessible in PATH +- [Qwen Code](https://github.com/QwenLM/qwen-code) >= 0.4.0 (stable) installed and accessible in PATH > **Note for nvm users**: If you use nvm to manage Node.js versions, the SDK may not be able to auto-detect the Qwen Code executable. You should explicitly set the `pathToQwenExecutable` option to the full path of the `qwen` binary. diff --git a/packages/sdk-typescript/src/query/Query.ts b/packages/sdk-typescript/src/query/Query.ts index 81edc48f..88a0e7f1 100644 --- a/packages/sdk-typescript/src/query/Query.ts +++ b/packages/sdk-typescript/src/query/Query.ts @@ -620,6 +620,10 @@ export class Query implements AsyncIterable { subtype: string, data: Record = {}, ): Promise | null> { + if (this.closed) { + return Promise.reject(new Error('Query is closed')); + } + const requestId = randomUUID(); const request: CLIControlRequest = { @@ -688,12 +692,13 @@ export class Query implements AsyncIterable { for (const pending of this.pendingControlRequests.values()) { pending.abortController.abort(); clearTimeout(pending.timeout); + pending.reject(new Error('Query is closed')); } this.pendingControlRequests.clear(); // Clean up pending MCP responses for (const pending of this.pendingMcpResponses.values()) { - pending.reject(new Error('Query closed')); + pending.reject(new Error('Query is closed')); } this.pendingMcpResponses.clear(); @@ -719,7 +724,7 @@ export class Query implements AsyncIterable { } } this.sdkMcpTransports.clear(); - logger.info('Query closed'); + logger.info('Query is closed'); } private async *readSdkMessages(): AsyncGenerator { @@ -821,28 +826,16 @@ export class Query implements AsyncIterable { } async interrupt(): Promise { - if (this.closed) { - throw new Error('Query is closed'); - } - await this.sendControlRequest(ControlRequestType.INTERRUPT); } async setPermissionMode(mode: string): Promise { - if (this.closed) { - throw new Error('Query is closed'); - } - await this.sendControlRequest(ControlRequestType.SET_PERMISSION_MODE, { mode, }); } async setModel(model: string): Promise { - if (this.closed) { - throw new Error('Query is closed'); - } - await this.sendControlRequest(ControlRequestType.SET_MODEL, { model }); } @@ -853,10 +846,6 @@ export class Query implements AsyncIterable { * @throws Error if query is closed */ async supportedCommands(): Promise | null> { - if (this.closed) { - throw new Error('Query is closed'); - } - return this.sendControlRequest(ControlRequestType.SUPPORTED_COMMANDS); } @@ -867,10 +856,6 @@ export class Query implements AsyncIterable { * @throws Error if query is closed */ async mcpServerStatus(): Promise | null> { - if (this.closed) { - throw new Error('Query is closed'); - } - return this.sendControlRequest(ControlRequestType.MCP_SERVER_STATUS); }