diff --git a/packages/core-agent/src/runtime/chat-orchestrator-runtime.test.ts b/packages/core-agent/src/runtime/chat-orchestrator-runtime.test.ts index 30f483c47..ce3b1fd08 100644 --- a/packages/core-agent/src/runtime/chat-orchestrator-runtime.test.ts +++ b/packages/core-agent/src/runtime/chat-orchestrator-runtime.test.ts @@ -236,6 +236,47 @@ describe('createChatOrchestratorRuntime', () => { expect(harness.promptProjections).toHaveLength(1) }) + it('keeps timestamp prefixes stable for legacy user messages without createdAt', async () => { + const harness = createHarness() + const legacyUserMessage = { + role: 'user' as const, + content: 'legacy prompt', + id: 'legacy-user', + } + harness.sessionMessages['session-1'] = [ + { role: 'system', content: 'system prompt', createdAt: 1, id: 'system' }, + legacyUserMessage, + ] + const firstMessages: Message[][] = [] + const secondMessages: Message[][] = [] + + harness.stream.mockImplementationOnce(async (_model, _chatProvider, messages, options) => { + firstMessages.push(structuredClone(messages)) + await options?.onStreamEvent?.({ type: 'finish', finishReason: 'stop' }) + }) + harness.now.set(new Date(2026, 3, 25, 18, 47).getTime()) + + await harness.runtime.ingest('first send', { + model: 'gpt-test', + chatProvider: provider, + }) + + harness.stream.mockImplementationOnce(async (_model, _chatProvider, messages, options) => { + secondMessages.push(structuredClone(messages)) + await options?.onStreamEvent?.({ type: 'finish', finishReason: 'stop' }) + }) + harness.now.set(new Date(2026, 3, 25, 19, 12).getTime()) + + await harness.runtime.ingest('second send', { + model: 'gpt-test', + chatProvider: provider, + }) + + expect(firstMessages[0]?.[1]?.content).toBe('[2026-04-25 18:47] legacy prompt') + expect(secondMessages[0]?.[1]?.content).toBe('[2026-04-25 18:47] legacy prompt') + expect(legacyUserMessage.createdAt).toBe(new Date(2026, 3, 25, 18, 47).getTime()) + }) + /** * @example * deps.getSystemPromptSupplement() returns tool guidance. diff --git a/packages/core-agent/src/runtime/chat-orchestrator-runtime.ts b/packages/core-agent/src/runtime/chat-orchestrator-runtime.ts index 55ae4f3dd..e7957024f 100644 --- a/packages/core-agent/src/runtime/chat-orchestrator-runtime.ts +++ b/packages/core-agent/src/runtime/chat-orchestrator-runtime.ts @@ -386,15 +386,23 @@ export function createChatOrchestratorRuntime(deps: ChatOrchestratorRuntimeDeps) } } + function getStablePromptTimestamp(message: ChatHistoryItem, fallbackCreatedAt: number) { + if (typeof message.createdAt === 'number') + return message.createdAt + + message.createdAt = fallbackCreatedAt + return fallbackCreatedAt + } + function buildProviderMessages(sessionMessagesForSend: ChatHistoryItem[]) { const nowTs = now() return sessionMessagesForSend.map((msg) => { - const { context: _context, id: _id, createdAt, ...withoutContext } = msg + const { context: _context, id: _id, createdAt: _createdAt, ...withoutContext } = msg const rawMessage = unwrapMessage(withoutContext) if (rawMessage.role === 'user') { - return prependTextToContent(rawMessage, formatTimePrefix(createdAt ?? nowTs)) + return prependTextToContent(rawMessage, formatTimePrefix(getStablePromptTimestamp(msg, nowTs))) } if (rawMessage.role === 'assistant') {