fix: preserve structured attachment outputs (#17)
This commit is contained in:
@@ -370,6 +370,71 @@ describe('MessageTurnController outbound audit logging', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('sends final attachments as a fresh final message instead of replacing text-only progress', async () => {
|
||||
const channel = {
|
||||
...makeChannel(),
|
||||
name: 'discord',
|
||||
} satisfies Channel;
|
||||
const deliverFinalText = vi.fn().mockResolvedValue(true);
|
||||
const attachments = [
|
||||
{
|
||||
path: '/tmp/ejclaw-discord-image-final.png',
|
||||
name: 'final.png',
|
||||
mime: 'image/png',
|
||||
},
|
||||
];
|
||||
const controller = new MessageTurnController({
|
||||
chatJid: 'dc:test-room',
|
||||
group: makeGroup(),
|
||||
runId: 'run-owner-final-attachment-send',
|
||||
channel,
|
||||
idleTimeout: 1_000,
|
||||
failureFinalText: '실패',
|
||||
isClaudeCodeAgent: true,
|
||||
clearSession: vi.fn(),
|
||||
requestClose: vi.fn(),
|
||||
deliverFinalText,
|
||||
deliveryRole: 'owner',
|
||||
pairedTurnIdentity: {
|
||||
turnId: 'task-1:2026-04-10T14:22:00.000Z:owner-turn',
|
||||
taskId: 'task-1',
|
||||
taskUpdatedAt: '2026-04-10T14:22:00.000Z',
|
||||
intentKind: 'finalize-owner-turn',
|
||||
role: 'owner',
|
||||
},
|
||||
});
|
||||
|
||||
await controller.start();
|
||||
await controller.handleOutput({
|
||||
status: 'success',
|
||||
phase: 'progress',
|
||||
result: '이미지 렌더링 중',
|
||||
} as any);
|
||||
await controller.handleOutput({
|
||||
status: 'success',
|
||||
phase: 'progress',
|
||||
result: '이미지 파일 쓰는 중',
|
||||
} as any);
|
||||
await flushAsync();
|
||||
await controller.handleOutput({
|
||||
status: 'success',
|
||||
phase: 'final',
|
||||
result: '이미지 렌더 완료.',
|
||||
output: {
|
||||
visibility: 'public',
|
||||
text: '이미지 렌더 완료.',
|
||||
attachments,
|
||||
},
|
||||
} as any);
|
||||
await controller.finish('success');
|
||||
|
||||
expect(channel.sendAndTrack).toHaveBeenCalledTimes(1);
|
||||
expect(deliverFinalText).toHaveBeenCalledWith('이미지 렌더 완료.', {
|
||||
replaceMessageId: null,
|
||||
attachments,
|
||||
});
|
||||
});
|
||||
|
||||
it('replaces the tracked progress message when an owner final arrives', async () => {
|
||||
const channel = {
|
||||
...makeChannel(),
|
||||
|
||||
@@ -666,7 +666,10 @@ export class MessageTurnController {
|
||||
await this.flushPendingProgress(options.flushPendingText);
|
||||
}
|
||||
|
||||
const replaceMessageId = this.consumeProgressForFinalDelivery();
|
||||
const hasAttachments = (options?.attachments?.length ?? 0) > 0;
|
||||
const replaceMessageId = hasAttachments
|
||||
? this.discardProgressForAttachmentFinalDelivery()
|
||||
: this.consumeProgressForFinalDelivery();
|
||||
await this.deliverFinalText(text, {
|
||||
...(options?.attachments?.length
|
||||
? { attachments: options.attachments }
|
||||
@@ -690,6 +693,20 @@ export class MessageTurnController {
|
||||
return replaceMessageId;
|
||||
}
|
||||
|
||||
private discardProgressForAttachmentFinalDelivery(): null {
|
||||
this.log.info(
|
||||
{
|
||||
progressMessageId: this.progressMessageId,
|
||||
latestProgressText: this.latestProgressText,
|
||||
},
|
||||
this.progressMessageId
|
||||
? 'Discarding tracked progress replacement for final attachment delivery'
|
||||
: 'Delivering final attachment output without a tracked progress message to replace',
|
||||
);
|
||||
this.resetProgressState();
|
||||
return null;
|
||||
}
|
||||
|
||||
private async deliverFinalText(
|
||||
text: string,
|
||||
options?: {
|
||||
|
||||
Reference in New Issue
Block a user