diff --git a/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.i18n.yaml b/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.i18n.yaml
index dbe848dca4..ea7d632808 100644
--- a/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.i18n.yaml
+++ b/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.i18n.yaml
@@ -18,5 +18,5 @@
en: 9861066ae00e7bde
zh: 0dec00f4e3c692e0
/agent-note-sdk-native-user-questions/verification:
- en: 18c40c34b936f966
- zh: 81bd38dff90a9adb
+ en: c16fb364fe8ec13b
+ zh: fa23c162348ba682
diff --git a/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.md b/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.md
index b5522a6e7c..c4aa4ce875 100644
--- a/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.md
+++ b/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.md
@@ -26,4 +26,4 @@ Requiring a Web GUI or mounting its controller couples headless execution to ano
## Verification
-Server tests cover ownership, native fallback, answer validation, cancellation, and transport failure. Client tests cover concurrent bidirectional transport and cancellation. Python tests also cover reentrant requests and a late callback after process replacement. Real shipped-profile tests drive the built-in question in standard and PTC modes and close a runtime while the host is answering. The [server tests](../../../../packages/sdk/server/tests/user-questions.spec.ts), [client tests](../../../../packages/sdk/client/tests/user-questions.spec.ts), [Python tests](../../../../python/sdk/tests/test_user_questions.py), and [profile tests](../../../../apps/cli/tests/profiles/sdk/user-questions.e2e.ts) retain this evidence.
+Server tests cover ownership, native fallback, answer validation, cancellation, and transport failure. Client tests cover concurrent bidirectional transport and cancellation, including callback advertisement and answer delivery with either explicit startup or a direct `initialize()` call. Initialization starts the runtime before constructing the handshake capabilities so both entry paths advertise the installed callback. Python tests also cover reentrant requests and a late callback after process replacement. Real shipped-profile tests drive the built-in question in standard and PTC modes and close a runtime while the host is answering. The [server tests](../../../../packages/sdk/server/tests/user-questions.spec.ts), [client tests](../../../../packages/sdk/client/tests/user-questions.spec.ts), [Python tests](../../../../python/sdk/tests/test_user_questions.py), and [profile tests](../../../../apps/cli/tests/profiles/sdk/user-questions.e2e.ts) retain this evidence.
diff --git a/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.zh.md b/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.zh.md
index 6b26d8cb72..bacf788ab9 100644
--- a/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.zh.md
+++ b/.agents/notes/implemented/feature/2026-10-19-sdk-native-user-questions.zh.md
@@ -26,4 +26,4 @@ Web Session controller 不能充当 SDK 的交互提供方。提醒恢复使用
## 验证
-服务器测试覆盖所有权、原生回退、答案校验、取消和传输失败。客户端测试覆盖并发双向传输和取消。Python 测试还覆盖回调内再次请求,以及进程替换后的晚到回调。真实发行 profile 测试在标准模式和 PTC 模式调用内建问答,并在宿主回答期间关闭运行时。[服务器测试](../../../../packages/sdk/server/tests/user-questions.spec.ts)、[客户端测试](../../../../packages/sdk/client/tests/user-questions.spec.ts)、[Python 测试](../../../../python/sdk/tests/test_user_questions.py)和 [profile 测试](../../../../apps/cli/tests/profiles/sdk/user-questions.e2e.ts)保留这些依据。
+服务器测试覆盖所有权、原生回退、答案校验、取消和传输失败。客户端测试覆盖并发双向传输和取消,包括显式启动和直接调用 `initialize()` 两条路径的回调声明与答案投递。初始化在构造握手能力之前启动运行时,使两条入口均声明已安装的回调。Python 测试还覆盖回调内再次请求,以及进程替换后的晚到回调。真实发行 profile 测试在标准模式和 PTC 模式调用内建问答,并在宿主回答期间关闭运行时。[服务器测试](../../../../packages/sdk/server/tests/user-questions.spec.ts)、[客户端测试](../../../../packages/sdk/client/tests/user-questions.spec.ts)、[Python 测试](../../../../python/sdk/tests/test_user_questions.py)和 [profile 测试](../../../../apps/cli/tests/profiles/sdk/user-questions.e2e.ts)保留这些依据。
diff --git a/packages/sdk/client/README.i18n.yaml b/packages/sdk/client/README.i18n.yaml
index fa616c99f1..c986db7993 100644
--- a/packages/sdk/client/README.i18n.yaml
+++ b/packages/sdk/client/README.i18n.yaml
@@ -24,8 +24,8 @@
en: 2eb13706964444cb
zh: a2640ec27b83113f
/deepseek-ai-dsh-sdk-client/use-this-package/native-user-questions:
- en: 68e4bd95f31c77bb
- zh: 74ceff119831784a
+ en: ea40f378444c8153
+ zh: 96e29c99aa5c15f0
/deepseek-ai-dsh-sdk-client/understand-the-implementation:
en: 8773f50c51da85f0
zh: 4649ebc69357deb0
diff --git a/packages/sdk/client/README.md b/packages/sdk/client/README.md
index 21d1368f5a..ae9661faab 100644
--- a/packages/sdk/client/README.md
+++ b/packages/sdk/client/README.md
@@ -62,7 +62,7 @@ The client exports typed errors for every failure mode: `JsonRpcResponseError` (
### Native user questions
-Pass `onUserQuestion` to `DeepSeekHarness` or `HarnessClient` to answer the built-in `ask_user_question` tool through the native SDK protocol. The callback receives `{ requestId, sessionId, questions }` and `{ signal }`; return `{ answers: [{ id, selected, custom? }] }`, using one item per question id. Your application owns the UI or simulated-user policy. The SDK automatically advertises the callback at initialization and can receive answers while other requests are pending.
+Pass `onUserQuestion` to `DeepSeekHarness` or `HarnessClient` to answer the built-in `ask_user_question` tool through the native SDK protocol. The callback receives `{ requestId, sessionId, questions }` and `{ signal }`; return `{ answers: [{ id, selected, custom? }] }`, using one item per question id. Your application owns the UI or simulated-user policy. The SDK automatically advertises the callback at initialization and can receive answers while other requests are pending. `HarnessClient.initialize()` starts the process when needed; an explicit `start()` is optional.
Observe the signal to release host work when the runtime cancels the question, disconnects, or the client closes. The SDK stops awaiting cancelled callbacks and ignores their later answers. A throwing callback reports a provider error; missing or malformed answers never count as approval. Without a callback, native in-process answerers remain available, and a missing answerer reports `NO_PROVIDER`.
diff --git a/packages/sdk/client/README.zh.md b/packages/sdk/client/README.zh.md
index 26cdd1b078..9edfd4a4cb 100644
--- a/packages/sdk/client/README.zh.md
+++ b/packages/sdk/client/README.zh.md
@@ -62,7 +62,7 @@ console.log(result.finalResponse)
### 原生用户问答
-向 `DeepSeekHarness` 或 `HarnessClient` 传入 `onUserQuestion`,即可通过原生 SDK 协议回答内建的 `ask_user_question` 工具。回调接收 `{ requestId, sessionId, questions }` 与 `{ signal }`;返回 `{ answers: [{ id, selected, custom? }] }`,每个问题 id 对应一项。应用自行提供 UI 或模拟用户策略。SDK 在初始化时自动声明此回调,也能在其他请求待决时接收答案。
+向 `DeepSeekHarness` 或 `HarnessClient` 传入 `onUserQuestion`,即可通过原生 SDK 协议回答内建的 `ask_user_question` 工具。回调接收 `{ requestId, sessionId, questions }` 与 `{ signal }`;返回 `{ answers: [{ id, selected, custom? }] }`,每个问题 id 对应一项。应用自行提供 UI 或模拟用户策略。SDK 在初始化时自动声明此回调,也能在其他请求待决时接收答案。`HarnessClient.initialize()` 会在需要时启动进程,无需显式调用 `start()`。
运行时取消问题、断开连接或客户端关闭时,应响应 signal 释放宿主工作。SDK 停止等待已取消的回调,并忽略晚到答案。回调抛错会报告提供方错误;缺失或格式错误的答案均不构成批准。未提供回调时,原生进程内回答方仍可用,缺少回答方则报告 `NO_PROVIDER`。
diff --git a/packages/sdk/client/src/client.ts b/packages/sdk/client/src/client.ts
index 0714e6c6a8..5b7fdd6cc3 100644
--- a/packages/sdk/client/src/client.ts
+++ b/packages/sdk/client/src/client.ts
@@ -305,11 +305,12 @@ export class HarnessClient {
}
/**
- * Perform the process-wide handshake.
+ * Start the runtime if needed, then perform the process-wide handshake.
* @param params - workspace cwd plus the provider/model route.
* @returns the runtime's wire identity.
*/
async initialize(params: InitializeParams): Promise {
+ this.start()
const result = await this.request('initialize', {
...params,
...this.userQuestionHandler === undefined ? {} : { capabilities: { ...params.capabilities, userQuestions: true } },
diff --git a/packages/sdk/client/tests/user-questions.spec.ts b/packages/sdk/client/tests/user-questions.spec.ts
index d49f0f0458..820ba6aba4 100644
--- a/packages/sdk/client/tests/user-questions.spec.ts
+++ b/packages/sdk/client/tests/user-questions.spec.ts
@@ -3,7 +3,7 @@ import { expect, it, onTestFinished } from 'vitest'
import type { HarnessClientOptions } from '../src/types.ts'
import { createProcessHarnessClient } from '../src/client.ts'
-async function client(onUserQuestion?: HarnessClientOptions['onUserQuestion']) {
+async function client(onUserQuestion?: HarnessClientOptions['onUserQuestion'], explicitStart = true) {
const result = createProcessHarnessClient({
command: process.execPath,
args: [fileURLToPath(new URL('./fixtures/user-questions.ts', import.meta.url))],
@@ -12,20 +12,20 @@ async function client(onUserQuestion?: HarnessClientOptions['onUserQuestion']) {
})
onTestFinished(() => result.close())
if (onUserQuestion !== undefined) result.options.onUserQuestion = onUserQuestion
- result.start()
+ if (explicitStart) result.start()
const initialized = await result.initialize({ cwd: process.cwd(), provider: 'mock', model: 'mock' })
const capabilities: unknown = JSON.parse(initialized.serverInfo.version)
return { client: result, capabilities }
}
-it('advertises a callback and answers a runtime request during an outbound request', async () => {
+it.each([false, true])('advertises a callback and answers a runtime request with explicit start: %s', async (explicitStart) => {
const answer = { answers: [{ id: 'format', selected: ['CSV'] }] }
const seen: string[] = []
const f = await client(async (request, { signal }) => {
seen.push(request.sessionId)
expect(signal.aborted).toBe(false)
return answer
- })
+ }, explicitStart)
expect(f.capabilities).toEqual({ userQuestions: true })
await expect(f.client.request('test/question', {})).resolves.toEqual({ answer, error: null })
expect(seen).toEqual(['main'])