mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-10 01:29:17 +00:00
fix(daemon): preserve user message source metadata (#6385)
This commit is contained in:
parent
5d2bfbd21b
commit
7e0e79b6bc
8 changed files with 95 additions and 3 deletions
|
|
@ -113,6 +113,7 @@ export class HistoryReplayer {
|
|||
await this.messageEmitter.emitUserMessage(
|
||||
displayText,
|
||||
record.timestamp,
|
||||
record.subtype === 'cron' ? { source: 'cron' } : undefined,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,21 @@ describe('MessageEmitter', () => {
|
|||
content: { type: 'text', text: multilineText },
|
||||
});
|
||||
});
|
||||
|
||||
it('should include source metadata when provided', async () => {
|
||||
await emitter.emitUserMessage('scheduled prompt', 1_700_000_000_000, {
|
||||
source: 'cron',
|
||||
});
|
||||
|
||||
expect(sendUpdateSpy).toHaveBeenCalledWith({
|
||||
sessionUpdate: 'user_message_chunk',
|
||||
content: { type: 'text', text: 'scheduled prompt' },
|
||||
_meta: {
|
||||
timestamp: 1_700_000_000_000,
|
||||
source: 'cron',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('emitAgentMessage', () => {
|
||||
|
|
|
|||
|
|
@ -90,12 +90,17 @@ export class MessageEmitter extends BaseEmitter {
|
|||
async emitUserMessage(
|
||||
text: string,
|
||||
timestamp?: string | number,
|
||||
options: { source?: string } = {},
|
||||
): Promise<void> {
|
||||
const epochMs = BaseEmitter.toEpochMs(timestamp);
|
||||
const _meta = {
|
||||
...(epochMs != null ? { timestamp: epochMs } : {}),
|
||||
...(options.source ? { source: options.source } : {}),
|
||||
};
|
||||
await this.sendUpdate({
|
||||
sessionUpdate: 'user_message_chunk',
|
||||
content: { type: 'text', text },
|
||||
...(epochMs != null && { _meta: { timestamp: epochMs } }),
|
||||
...(Object.keys(_meta).length > 0 ? { _meta } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -534,6 +534,7 @@ function normalizeSessionUpdate(
|
|||
) {
|
||||
return [];
|
||||
}
|
||||
const meta = extractUpdateMeta(update);
|
||||
const content = update['content'];
|
||||
const part = extractContentPart(content);
|
||||
if (part) {
|
||||
|
|
@ -558,13 +559,29 @@ function normalizeSessionUpdate(
|
|||
}
|
||||
if (part.kind === 'text') {
|
||||
return part.text
|
||||
? [{ ...base, type: 'user.text.delta', text: part.text }]
|
||||
? [
|
||||
{
|
||||
...base,
|
||||
type: 'user.text.delta',
|
||||
text: part.text,
|
||||
...(meta ? { meta } : {}),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
const text = getTextContent(content);
|
||||
return text ? [{ ...base, type: 'user.text.delta', text }] : [];
|
||||
return text
|
||||
? [
|
||||
{
|
||||
...base,
|
||||
type: 'user.text.delta',
|
||||
text,
|
||||
...(meta ? { meta } : {}),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
}
|
||||
case 'agent_message_chunk': {
|
||||
const text = getTextContent(update['content']);
|
||||
|
|
|
|||
|
|
@ -522,6 +522,39 @@ describe('daemon UI normalizer and transcript reducer', () => {
|
|||
).toMatchObject([{ type: 'user.text.delta', text: 'hello' }]);
|
||||
});
|
||||
|
||||
it('preserves user message metadata on transcript blocks', () => {
|
||||
const events = normalizeDaemonEvent({
|
||||
id: 23,
|
||||
v: 1,
|
||||
type: 'session_update',
|
||||
data: {
|
||||
update: {
|
||||
sessionUpdate: 'user_message_chunk',
|
||||
content: { type: 'text', text: 'scheduled prompt' },
|
||||
_meta: { source: 'cron' },
|
||||
},
|
||||
},
|
||||
} as const);
|
||||
|
||||
expect(events).toMatchObject([
|
||||
{
|
||||
type: 'user.text.delta',
|
||||
text: 'scheduled prompt',
|
||||
meta: { source: 'cron' },
|
||||
},
|
||||
]);
|
||||
|
||||
const state = reduceDaemonTranscriptEvents(
|
||||
createDaemonTranscriptState({ now: 1 }),
|
||||
events,
|
||||
);
|
||||
expect(state.blocks[0]).toMatchObject({
|
||||
kind: 'user',
|
||||
text: 'scheduled prompt',
|
||||
meta: { source: 'cron' },
|
||||
});
|
||||
});
|
||||
|
||||
it('carries user shell command metadata into user shell transcript blocks', () => {
|
||||
let state = createDaemonTranscriptState({ now: 1 });
|
||||
const commandEvents = normalizeDaemonEvent({
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ export interface DaemonUserMessage extends DaemonMessageMeta {
|
|||
role: 'user';
|
||||
content: string;
|
||||
images?: Array<{ data: string; mimeType: string }>;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface DaemonAssistantMessage extends DaemonMessageMeta {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,21 @@ function toolBlock(
|
|||
}
|
||||
|
||||
describe('transcriptBlocksToDaemonMessages', () => {
|
||||
it('preserves user source metadata', () => {
|
||||
const messages = transcriptBlocksToDaemonMessages([
|
||||
textBlock('user-1', 'user', 'scheduled prompt', 1, false, {
|
||||
meta: { source: 'cron' },
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(messages[0]).toMatchObject({
|
||||
id: 'user-1',
|
||||
role: 'user',
|
||||
content: 'scheduled prompt',
|
||||
source: 'cron',
|
||||
});
|
||||
});
|
||||
|
||||
it('hides background task notifications by metadata', () => {
|
||||
const messages = transcriptBlocksToDaemonMessages([
|
||||
textBlock(
|
||||
|
|
|
|||
|
|
@ -195,11 +195,16 @@ export function transcriptBlocksToDaemonMessages(
|
|||
currentThinkingIdx = null;
|
||||
needsNewContentMessage = false;
|
||||
const textBlock = block as DaemonTextTranscriptBlock;
|
||||
const meta = getRecord(
|
||||
(textBlock as ExtendedDaemonTextTranscriptBlock).meta,
|
||||
);
|
||||
const source = getString(meta, 'source');
|
||||
const msg: DaemonUserMessage = {
|
||||
id: block.id,
|
||||
role: 'user',
|
||||
content: textBlock.text,
|
||||
timestamp: blockTime,
|
||||
...(source ? { source } : {}),
|
||||
};
|
||||
// Attach images if present
|
||||
if (textBlock.images && textBlock.images.length > 0) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue