mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 16:44:36 +00:00
fix(vscode-ide-companion): echo user prompt into WebShell transcript
This commit is contained in:
parent
c2cffbb7b7
commit
bd09e19d86
2 changed files with 137 additions and 0 deletions
|
|
@ -225,6 +225,64 @@ describe('SessionMessageHandler', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('echoes the user prompt into the ACP transcript as a user_message_chunk', async () => {
|
||||
mockProcessImageAttachments.mockResolvedValue({
|
||||
formattedText: 'hello transcript',
|
||||
displayText: 'hello transcript',
|
||||
savedImageCount: 0,
|
||||
promptImages: [],
|
||||
});
|
||||
|
||||
const agentManager = {
|
||||
isConnected: true,
|
||||
currentSessionId: 'session-1',
|
||||
sendMessage: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
const conversationStore = {
|
||||
createConversation: vi.fn().mockResolvedValue({ id: 'conversation-1' }),
|
||||
getConversation: vi.fn().mockResolvedValue(null),
|
||||
addMessage: vi.fn(),
|
||||
renameConversationId: vi.fn().mockResolvedValue(true),
|
||||
};
|
||||
const sendToWebView = vi.fn();
|
||||
|
||||
const handler = new SessionMessageHandler(
|
||||
agentManager as never,
|
||||
conversationStore as never,
|
||||
null,
|
||||
sendToWebView,
|
||||
);
|
||||
|
||||
await handler.handle({
|
||||
type: 'sendMessage',
|
||||
data: { text: 'hello transcript' },
|
||||
});
|
||||
|
||||
// The direct stdio ACP channel never emits user_message_chunk for an
|
||||
// interactive prompt; the handler must synthesize it so the user's own
|
||||
// turn renders in the WebShell transcript timeline.
|
||||
expect(sendToWebView).toHaveBeenCalledWith({
|
||||
type: 'transcriptUpdate',
|
||||
data: {
|
||||
sessionId: 'session-1',
|
||||
update: {
|
||||
sessionUpdate: 'user_message_chunk',
|
||||
content: { type: 'text', text: 'hello transcript' },
|
||||
},
|
||||
},
|
||||
});
|
||||
// The echo must be posted before the prompt is dispatched so the user
|
||||
// block renders ahead of this turn's assistant frames.
|
||||
const echoCallIndex = sendToWebView.mock.calls.findIndex(
|
||||
(call) =>
|
||||
(call[0] as { type?: string } | undefined)?.type === 'transcriptUpdate',
|
||||
);
|
||||
expect(echoCallIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(sendToWebView.mock.invocationCallOrder[echoCallIndex]).toBeLessThan(
|
||||
agentManager.sendMessage.mock.invocationCallOrder[0],
|
||||
);
|
||||
});
|
||||
|
||||
it('sends image file context as prompt image blocks', async () => {
|
||||
mockProcessImageAttachments.mockImplementation(
|
||||
async (promptText: string) => ({
|
||||
|
|
@ -990,6 +1048,66 @@ describe('SessionMessageHandler', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('publishes the fresh ACP session id as liveSessionId in the load-failure fallback boundary', async () => {
|
||||
const archivedSessionId = 'archived-session';
|
||||
const agentManager: {
|
||||
isConnected: boolean;
|
||||
currentSessionId: string | null;
|
||||
getSessionList: ReturnType<typeof vi.fn>;
|
||||
loadSessionViaAcp: ReturnType<typeof vi.fn>;
|
||||
getSessionMessages: ReturnType<typeof vi.fn>;
|
||||
createNewSession: ReturnType<typeof vi.fn>;
|
||||
} = {
|
||||
isConnected: true,
|
||||
currentSessionId: 'old-acp-session',
|
||||
getSessionList: vi
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: archivedSessionId, cwd: '/workspace' }]),
|
||||
loadSessionViaAcp: vi
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('session not found on server')),
|
||||
getSessionMessages: vi.fn().mockResolvedValue([]),
|
||||
createNewSession: vi.fn(),
|
||||
};
|
||||
// Mirror the real manager: session/new flips currentSessionId to the
|
||||
// freshly created ACP session.
|
||||
agentManager.createNewSession.mockImplementation(async () => {
|
||||
agentManager.currentSessionId = 'new-acp-session';
|
||||
return 'new-acp-session';
|
||||
});
|
||||
const conversationStore = {
|
||||
createConversation: vi.fn(),
|
||||
getConversation: vi.fn(),
|
||||
addMessage: vi.fn(),
|
||||
};
|
||||
const sendToWebView = vi.fn();
|
||||
|
||||
const handler = new SessionMessageHandler(
|
||||
agentManager as never,
|
||||
conversationStore as never,
|
||||
null,
|
||||
sendToWebView,
|
||||
);
|
||||
|
||||
await handler.handle({
|
||||
type: 'switchQwenSession',
|
||||
data: { sessionId: archivedSessionId },
|
||||
});
|
||||
|
||||
// The transcript filter must learn the live session id from the
|
||||
// boundary; otherwise every live frame of the fresh session is
|
||||
// dropped because it does not carry the archived id.
|
||||
expect(sendToWebView).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'qwenSessionSwitched',
|
||||
data: expect.objectContaining({
|
||||
sessionId: archivedSessionId,
|
||||
liveSessionId: 'new-acp-session',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('forces a fresh ACP session when the webview requests a new session', async () => {
|
||||
const agentManager = {
|
||||
isConnected: true,
|
||||
|
|
|
|||
|
|
@ -871,6 +871,25 @@ export class SessionMessageHandler extends BaseMessageHandler {
|
|||
},
|
||||
});
|
||||
|
||||
// The companion's direct stdio ACP channel never receives a
|
||||
// user_message_chunk for an interactive prompt, so echo the user's
|
||||
// own turn into the transcript timeline (mirrors the daemon-bridge
|
||||
// echo on the SSE bus). Posted before the prompt is dispatched so
|
||||
// the user block renders ahead of this turn's assistant frames.
|
||||
const transcriptEchoSessionId = this.agentManager.currentSessionId;
|
||||
if (transcriptEchoSessionId && displayText) {
|
||||
this.sendToWebView({
|
||||
type: 'transcriptUpdate',
|
||||
data: {
|
||||
sessionId: transcriptEchoSessionId,
|
||||
update: {
|
||||
sessionUpdate: 'user_message_chunk',
|
||||
content: { type: 'text', text: displayText },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await this.agentManager.sendMessage(
|
||||
buildPromptBlocks(promptText, promptImages),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue