| ... | @@ -3296,6 +3296,9 @@ describe("ClaudeAdapterLive", () => { | ... | @@ -3296,6 +3296,9 @@ describe("ClaudeAdapterLive", () => { |
| 3296 | assert.equal(typeof requestId, "string"); | 3296 | assert.equal(typeof requestId, "string"); |
| 3297 | assert.equal(requestedEvent.value.payload.questions.length, 1); | 3297 | assert.equal(requestedEvent.value.payload.questions.length, 1); |
| 3298 | assert.equal(requestedEvent.value.payload.questions[0]?.question, "Which framework?"); | 3298 | assert.equal(requestedEvent.value.payload.questions[0]?.question, "Which framework?"); |
| | 3299 | // Regression for #2388: `id` must equal the full question text so the |
| | 3300 | // UI's draft-answer key matches what the SDK looks up downstream. |
| | 3301 | assert.equal(requestedEvent.value.payload.questions[0]?.id, "Which framework?"); |
| 3299 | assert.deepEqual(requestedEvent.value.providerRefs, { | 3302 | assert.deepEqual(requestedEvent.value.providerRefs, { |
| 3300 | providerItemId: ProviderItemId.make("tool-ask-1"), | 3303 | providerItemId: ProviderItemId.make("tool-ask-1"), |
| 3301 | }); | 3304 | }); |
| ... | @@ -3330,6 +3333,34 @@ describe("ClaudeAdapterLive", () => { | ... | @@ -3330,6 +3333,34 @@ describe("ClaudeAdapterLive", () => { |
| 3330 | assert.deepEqual(updatedInput.answers, { "Which framework?": "React" }); | 3333 | assert.deepEqual(updatedInput.answers, { "Which framework?": "React" }); |
| 3331 | // Original questions should be passed through. | 3334 | // Original questions should be passed through. |
| 3332 | assert.deepEqual(updatedInput.questions, askInput.questions); | 3335 | assert.deepEqual(updatedInput.questions, askInput.questions); |
| | 3336 | |
| | 3337 | // Compatibility check for #2388: the answers shape we hand to the SDK |
| | 3338 | // must produce a non-empty rendered tool_result on BOTH SDK iteration |
| | 3339 | // patterns we have seen, so we don't regress the issue and we don't |
| | 3340 | // break users still on the older Claude CLI. |
| | 3341 | const sdkAnswers = updatedInput.answers as Record<string, unknown>; |
| | 3342 | const sdkQuestions = updatedInput.questions as ReadonlyArray<{ |
| | 3343 | readonly question: string; |
| | 3344 | }>; |
| | 3345 | |
| | 3346 | // Claude CLI 2.1.119 — key-agnostic Object.entries iteration. Any key |
| | 3347 | // works here, but it must at least round-trip into a non-empty string. |
| | 3348 | const v119Rendered = Object.entries(sdkAnswers) |
| | 3349 | .map(([key, value]) => `"${key}"="${String(value)}"`) |
| | 3350 | .join(", "); |
| | 3351 | assert.equal(v119Rendered, '"Which framework?"="React"'); |
| | 3352 | |
| | 3353 | // Claude CLI 2.1.121 — lookup by full question text. This is the path |
| | 3354 | // that regressed in #2388 when the answers were keyed by `header`. |
| | 3355 | const v121Rendered = sdkQuestions |
| | 3356 | .map(({ question }) => { |
| | 3357 | const answer = sdkAnswers[question]; |
| | 3358 | return answer === undefined ? null : `"${question}"="${String(answer)}"`; |
| | 3359 | }) |
| | 3360 | .filter((entry): entry is string => entry !== null) |
| | 3361 | .join(", "); |
| | 3362 | assert.notEqual(v121Rendered, "", "Expected non-empty SDK 2.1.121 tool_result (#2388)"); |
| | 3363 | assert.equal(v121Rendered, '"Which framework?"="React"'); |
| 3333 | }).pipe( | 3364 | }).pipe( |
| 3334 | Effect.provideService(Random.Random, makeDeterministicRandomService()), | 3365 | Effect.provideService(Random.Random, makeDeterministicRandomService()), |
| 3335 | Effect.provide(harness.layer), | 3366 | Effect.provide(harness.layer), |