fix(coding-agent): restore compaction-queued messages on Alt+Up dequeue

restoreQueuedMessagesToEditor only drained the agent steering/follow-up
queue via session.clearQueue(), but the "Alt+Up to edit" pending-bar
hint is rendered for both that queue and ctx.compactionQueuedMessages.
Messages typed while the session was compacting -- including /skill:*
follow-ups, which the follow-up path routes to the compaction queue
before its skill check -- were advertised by the hint yet unreachable,
so Alt+Up reported "No queued messages to restore".

Drain compactionQueuedMessages alongside the agent queue, merged in the
same order the pending bar renders (session-steer, compaction-steer,
session-follow-up, compaction-follow-up). The existing text-join, image
hand-back, and abort paths operate on the merged list unchanged.
This commit is contained in:
metaphorics
2026-06-14 08:38:59 +09:00
parent 2703422986
commit f6d69620f9
3 changed files with 54 additions and 3 deletions
@@ -17,6 +17,7 @@
import { beforeAll, describe, expect, mock, test } from "bun:test";
import type { ImageContent } from "@oh-my-pi/pi-ai";
import { InputController } from "@oh-my-pi/pi-coding-agent/modes/controllers/input-controller";
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import type { CompactionQueuedMessage, InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
import { UiHelpers } from "@oh-my-pi/pi-coding-agent/modes/utils/ui-helpers";
@@ -50,6 +51,8 @@ function makeCtx(initialQueue: CompactionQueuedMessage[] = []) {
}),
};
let editorText = "";
const ctx = {
session,
compactionQueuedMessages: [...initialQueue],
@@ -58,8 +61,10 @@ function makeCtx(initialQueue: CompactionQueuedMessage[] = []) {
pendingMessagesContainer: { clear: () => {}, addChild: () => {}, removeChild: () => {} },
editor: {
addToHistory: () => {},
setText: () => {},
getText: () => "",
setText: (text: string) => {
editorText = text;
},
getText: () => editorText,
imageLinks: undefined as (string | undefined)[] | undefined,
},
keybindings: { getDisplayString: () => "Alt+Up" },
@@ -127,3 +132,35 @@ describe("compaction queue image forwarding", () => {
expect(followUpCalls).toEqual([{ text: "and this one", images: [image] }]);
});
});
describe("compaction queue Alt+Up restore", () => {
test("restoreQueuedMessagesToEditor drains a compaction-queued skill", () => {
const { ctx } = makeCtx([{ text: "/skill:foo bar", mode: "followUp", images: undefined }]);
const restored = new InputController(ctx).restoreQueuedMessagesToEditor();
expect(restored).toBe(1);
expect(ctx.editor.getText()).toBe("/skill:foo bar");
expect(ctx.compactionQueuedMessages).toEqual([]);
});
test("restored compaction images return to the pending-image buffer", () => {
const image = img("YmF6");
const { ctx } = makeCtx([{ text: "look", mode: "steer", images: [image] }]);
const restored = new InputController(ctx).restoreQueuedMessagesToEditor();
expect(restored).toBe(1);
expect(ctx.pendingImages).toEqual([image]);
});
test("session and compaction queues restore in pending-bar order", () => {
const { ctx, session } = makeCtx([
{ text: "compaction steer", mode: "steer", images: undefined },
{ text: "compaction followup", mode: "followUp", images: undefined },
]);
(session as unknown as { clearQueue: () => unknown }).clearQueue = () => ({
steering: [{ text: "session steer" }],
followUp: [{ text: "session followup" }],
});
const restored = new InputController(ctx).restoreQueuedMessagesToEditor();
expect(restored).toBe(4);
expect(ctx.editor.getText()).toBe("session steer\n\ncompaction steer\n\nsession followup\n\ncompaction followup");
});
});