Compare commits

...
Author SHA1 Message Date
b62af7287b docs(qa): retain native question reproduction evidence
Co-authored-by: luke <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
2026-10-17 07:23:14 +02:00
paseo-bot[bot] 4ce6704d5e test(acp): reproduce unanswered native question requests 2026-10-17 07:10:58 +02:00
2 changed files with 121 additions and 0 deletions
@@ -0,0 +1,27 @@
# Native Grok question reproduction
This branch retains an intentionally failing regression for [#3490](https://github.com/getpaseo/paseo/issues/3490). It changes no production behavior.
Run from the repository root after installing dependencies and building server dependencies:
```sh
npm ci --no-audit --no-fund
npm run build:server-deps
npm run test:unit --workspace=@getpaseo/server -- src/server/agent/providers/grok-question-repro.test.ts
```
On main `f472c9d1f4339c0f36583e84bf62c80c12867110`, the test fails:
```text
Question permissions: 0 (expected 1)
Expected: {"outcome":"accepted","answers":{"Choose a color":["Blue"]}}
Received: {"code":-32601,"message":"\"Method not found\": _x.ai/ask_user_question"}
```
The test uses a deterministic subprocess with the real ACP SDK and stdio transport, through the public session API. It emits the native request, chooses Blue if a question permission arrives, and checks the response. It does not use a live model or render the app.
The same test passes unchanged on [#7015](https://github.com/getpaseo/paseo/pull/7015) at `b8a48e4825cf49d09f352a4b3d7793847657908c`. All 165 targeted local checks passed there: 146 ACP tests including this independent regression, 10 shared form-core tests, two serialization tests, and seven Chromium question-form tests.
The real Grok attempt ended during session initialization, before any question was emitted. Live question generation, the rendered card, lettered fallback, OpenCode comparison, and the contributor's newer report of Grok merging labels after answering remain unverified. Reproduction confidence is 3/5; diagnosis confidence is 5/5 for the reproduced protocol failure only.
The missing-handler analysis from [luoph](https://github.com/luoph) holds at the ACP boundary. The proposed fix and subsequent answer-identity work are by luoph and [RoundRedDot](https://github.com/RRD-dev); this branch retains independent verification rather than a replacement fix. Updates stay on [the issue](https://github.com/getpaseo/paseo/issues/3490).
@@ -0,0 +1,94 @@
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { pathToFileURL } from "node:url";
import { expect, test } from "vitest";
import { ACPAgentSession } from "./acp-agent.js";
import { createTestLogger } from "../../../test-utils/test-logger.js";
// A deterministic ACP agent emits the native request captured in issue #3490.
// This exercises the public session API and SDK transport, without a live model.
const agentSource = `
import { Readable, Writable } from 'node:stream';
const { AgentSideConnection, PROTOCOL_VERSION, ndJsonStream } = await import(process.env.ACP_SDK_URL);
new AgentSideConnection(connection => ({
async initialize() { return {protocolVersion:PROTOCOL_VERSION}; },
async newSession() { return {sessionId:'question-repro'}; },
async authenticate() {}, async cancel() {},
async prompt() {
let result;
try {
result = await connection.extMethod('_x.ai/ask_user_question', {
sessionId:'question-repro', toolCallId:'question-1', mode:'default',
questions:[{question:'Choose a color',options:[{label:'Red'},{label:'Blue'}],multiSelect:false}]
});
} catch(error) { result = {code:error.code,message:error.message}; }
await connection.sessionUpdate({sessionId:'question-repro',update:{
sessionUpdate:'agent_message_chunk', content:{type:'text',text:JSON.stringify(result)}
}});
return {stopReason:'end_turn'};
}
}), ndJsonStream(Writable.toWeb(process.stdout),Readable.toWeb(process.stdin)));
`;
test("native Grok question reaches the permission interface and accepts the selected answer", async () => {
const directory = await mkdtemp(path.join(tmpdir(), "paseo-grok-question-"));
const script = path.join(directory, "agent.mjs");
await writeFile(script, agentSource);
const session = new ACPAgentSession(
{ provider: "grok", cwd: directory },
{
provider: "grok",
logger: createTestLogger(),
defaultCommand: [process.execPath, script],
defaultModes: [],
capabilities: {
supportsStreaming: true,
supportsSessionPersistence: true,
supportsDynamicModes: true,
supportsMcpServers: true,
supportsReasoningStream: true,
supportsToolInvocations: true,
},
launchEnv: {
ACP_SDK_URL: pathToFileURL(
createRequire(import.meta.url).resolve("@agentclientprotocol/sdk"),
).href,
},
},
);
const questions: unknown[] = [];
const replies: string[] = [];
let finish!: () => void;
const done = new Promise<void>((resolve) => {
finish = resolve;
});
session.subscribe((event) => {
if (event.type === "permission_requested") {
questions.push(event.request);
void session.respondToPermission(event.request.id, {
behavior: "allow",
updatedInput: {
questionAnswers: { "Question 1": { selectedOptionIds: ["1"] } },
},
});
}
if (event.type === "timeline" && event.item.type === "assistant_message")
replies.push(event.item.text);
if (event.type === "turn_completed" || event.type === "turn_failed") finish();
});
try {
await session.initializeNewSession();
await session.startTurn("Ask me to choose a color.");
await done;
expect.soft(questions).toHaveLength(1);
expect(replies.join("")).toBe(
JSON.stringify({ outcome: "accepted", answers: { "Choose a color": ["Blue"] } }),
);
expect(session.getPendingPermissions()).toEqual([]);
} finally {
await session.close();
await rm(directory, { recursive: true, force: true });
}
});