Repository navigation
fix(server): queue messages sent during context compaction #9620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import { | ||
| ApprovalRequestId, | ||
| type ChatAttachment, | ||
| type MessageId, | ||
| type OrchestrationEvent, | ||
| type OrchestrationSessionStatus, | ||
| ThreadId, | ||
|
|
@@ -503,6 +504,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti | |
| const fileSystem = yield* FileSystem.FileSystem; | ||
| const path = yield* Path.Path; | ||
| const serverConfig = yield* ServerConfig; | ||
| const compactRequestIds = new Map<ThreadId, MessageId>(); | ||
|
|
||
| const applyProjectsProjection: ProjectorDefinition["apply"] = Effect.fn( | ||
| "applyProjectsProjection", | ||
|
|
@@ -1263,35 +1265,35 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti | |
| )(function* (event, _attachmentSideEffects) { | ||
| switch (event.type) { | ||
| case "thread.created": | ||
| compactRequestIds.delete(event.payload.threadId); | ||
| yield* projectionTurnRepository.deleteByThreadId({ | ||
| threadId: event.payload.threadId, | ||
| }); | ||
| return; | ||
|
|
||
| case "thread.turn-start-requested": { | ||
| const pendingTurnStart = yield* projectionTurnRepository.getPendingTurnStartByThreadId({ | ||
| threadId: event.payload.threadId, | ||
| }); | ||
| if (Option.isSome(pendingTurnStart)) { | ||
| const pendingMessage = yield* projectionThreadMessageRepository.getByMessageId({ | ||
| messageId: pendingTurnStart.value.messageId, | ||
| }); | ||
| if ( | ||
| Option.isSome(pendingMessage) && | ||
| pendingMessage.value.role === "user" && | ||
| (pendingMessage.value.attachments?.length ?? 0) === 0 && | ||
| pendingMessage.value.text.trim().toLowerCase() === "/compact" | ||
| ) { | ||
| return; | ||
| } | ||
| } | ||
| yield* projectionTurnRepository.replacePendingTurnStart({ | ||
| const nextPendingTurnStart = { | ||
| threadId: event.payload.threadId, | ||
| messageId: event.payload.messageId, | ||
| sourceProposedPlanThreadId: event.payload.sourceProposedPlan?.threadId ?? null, | ||
| sourceProposedPlanId: event.payload.sourceProposedPlan?.planId ?? null, | ||
| requestedAt: event.payload.createdAt, | ||
| }; | ||
| const requestedMessage = yield* projectionThreadMessageRepository.getByMessageId({ | ||
| messageId: event.payload.messageId, | ||
| }); | ||
| const isCompactRequest = | ||
| Option.isSome(requestedMessage) && | ||
| requestedMessage.value.role === "user" && | ||
| (requestedMessage.value.attachments?.length ?? 0) === 0 && | ||
| requestedMessage.value.text.trim().toLowerCase() === "/compact"; | ||
| if (isCompactRequest) { | ||
| if (compactRequestIds.has(event.payload.threadId)) { | ||
| return; | ||
| } | ||
| compactRequestIds.set(event.payload.threadId, event.payload.messageId); | ||
| } | ||
| yield* projectionTurnRepository.replacePendingTurnStart(nextPendingTurnStart); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -1328,16 +1330,29 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti | |
| case "thread.session-set": { | ||
| const turnId = event.payload.session.activeTurnId; | ||
| if (turnId === null || event.payload.session.status !== "running") { | ||
| if ( | ||
| (event.payload.session.status === "ready" && | ||
| event.commandId?.startsWith("server:provider-session-set:") === true) || | ||
| const restoredReadySession = | ||
| event.payload.session.status === "ready" && | ||
| event.commandId?.startsWith("server:provider-session-set:") === true; | ||
| const terminalSession = | ||
| event.payload.session.status === "error" || | ||
| event.payload.session.status === "stopped" || | ||
| event.payload.session.status === "interrupted" | ||
| ) { | ||
| yield* projectionTurnRepository.deletePendingTurnStartByThreadId({ | ||
| threadId: event.payload.threadId, | ||
| }); | ||
| event.payload.session.status === "interrupted"; | ||
| if (restoredReadySession || terminalSession) { | ||
| const pendingTurnStart = | ||
| yield* projectionTurnRepository.getPendingTurnStartByThreadId({ | ||
| threadId: event.payload.threadId, | ||
| }); | ||
| const compactRequestId = compactRequestIds.get(event.payload.threadId); | ||
| const pendingTurnBelongsToCompaction = | ||
| compactRequestId === undefined || | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium After a restart, a restoring 🤖 Copy this AI Prompt to have your agent fix this: |
||
| Option.isNone(pendingTurnStart) || | ||
| pendingTurnStart.value.messageId === compactRequestId; | ||
| if (terminalSession || pendingTurnBelongsToCompaction) { | ||
| yield* projectionTurnRepository.deletePendingTurnStartByThreadId({ | ||
| threadId: event.payload.threadId, | ||
| }); | ||
| } | ||
| compactRequestIds.delete(event.payload.threadId); | ||
| } | ||
| // Leaving the "running" session status is the turn-end signal: | ||
| // settle still-running turns so their duration reflects the whole | ||
|
|
@@ -1461,6 +1476,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti | |
| yield* projectionTurnRepository.deletePendingTurnStartByThreadId({ | ||
| threadId: event.payload.threadId, | ||
| }); | ||
| compactRequestIds.delete(event.payload.threadId); | ||
| return; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Medium
Layers/ProjectionPipeline.ts:1294A failed
replacePendingTurnStartleavescompactRequestIdspopulated even though the database transaction rolled back, so the next/compactfor that thread returns at the duplicate check without creating a pending-start row. Move the map update until afterreplacePendingTurnStartsucceeds.🤖 Copy this AI Prompt to have your agent fix this: