fix(opencode): order legacy message loop by time (#40990)

Co-authored-by: Dax <mail@thdxr.com>
This commit is contained in:
opencode-agent[bot]
2026-08-07 04:08:23 +00:00
committed by GitHub
parent a54a693af2
commit db581e47a3
4 changed files with 120 additions and 10 deletions
+12 -9
View File
@@ -577,29 +577,32 @@ export const filterCompactedEffect = Effect.fnUntraced(function* (sessionID: Ses
// filterCompacted reorders messages for model consumption
// ([compaction-user, summary, ...retained tail..., continue-user]), so array
// position is not chronological. Derive each binding by max id (MessageID
// is monotonic via MessageID.ascending) so a pre-compaction overflowing tail
// assistant doesn't get mistaken for the most recent turn. tasks are
// compaction/subtask parts attached to user messages newer than the latest
// finished assistant — i.e. unprocessed work.
// position is not chronological. IDs are only a deterministic tie-breaker
// because imported messages do not necessarily have monotonic IDs.
export function latest(msgs: WithParts[]) {
let user: User | undefined
let assistant: Assistant | undefined
let finished: Assistant | undefined
for (const msg of msgs) {
const info = msg.info
if (info.role === "user" && (!user || info.id > user.id)) user = info
if (info.role === "assistant" && (!assistant || info.id > assistant.id)) assistant = info
if (info.role === "assistant" && info.finish && (!finished || info.id > finished.id)) finished = info
if (info.role === "user" && isAfter(info, user)) user = info
if (info.role === "assistant" && isAfter(info, assistant)) assistant = info
if (info.role === "assistant" && info.finish && isAfter(info, finished)) finished = info
}
const tasks = msgs.flatMap((m) =>
finished && m.info.id <= finished.id
finished && !isAfter(m.info, finished)
? []
: m.parts.filter((p): p is CompactionPart | SubtaskPart => p.type === "compaction" || p.type === "subtask"),
)
return { user, assistant, finished, tasks }
}
function isAfter(info: Info, other?: Info) {
if (!other) return true
if (info.time.created !== other.time.created) return info.time.created > other.time.created
return info.id > other.id
}
export function fromError(
e: unknown,
ctx: { providerID: ProviderV2.ID; aborted?: boolean },
+1 -1
View File
@@ -1112,7 +1112,7 @@ const layer = Layer.effect(
lastAssistant?.finish &&
!["tool-calls"].includes(lastAssistant.finish) &&
!hasToolCalls &&
lastUser.id < lastAssistant.id
lastAssistant.parentID === lastUser.id
) {
const orphan = lastAssistantMsg?.parts.find(
(part): part is SessionV1.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part),