Keep conversation turns distinct and history stable (#7990)

* fix(app): keep autonomous turns distinct and readable

Hidden system prompts merged autonomous work into the previous response and reopened completed turns. Preserve canonical turn boundaries and expose their triggers, while making received messages attributable and expanded turn details readable in one step.

* fix(timeline): mount history at actual run boundaries

* fix(timeline): load and retain complete available turns

* fix(app): summarize actions and preserve conversation events

* test(timeline): verify whole-turn paging stays responsive

* fix(app): group reasoning according to thinking preference

* test(app): align history journeys with whole-turn loading
This commit is contained in:
Mohamed Boudra
2026-10-27 13:57:17 +07:00
committed by GitHub
parent 3a1b146690
commit b12eefb28f
72 changed files with 1690 additions and 409 deletions
@@ -880,6 +880,66 @@ test("retries provider history hydration after a stream failure", async () => {
}
});
test("notification batches share the run they start or join", async () => {
const session = new SteeringTestSession({ provider: "codex", cwd: process.cwd() });
const workdir = mkdtempSync(join(tmpdir(), "agent-notification-turns-"));
const manager = new AgentManager({
clients: {
codex: new (class extends TestAgentClient {
override async createSession(): Promise<AgentSession> {
return session;
}
})(),
},
logger,
});
const agent = await manager.createAgent({ provider: "codex", cwd: workdir }, undefined, {
workspaceId: undefined,
});
const batch = (prefix: string) =>
["one", "two"]
.map((id) =>
formatAgentMessage({
id: `${prefix}-${id}`,
source: { kind: "agent-notification", agentId: "worker", event: "finished" },
text: "Worker finished",
}),
)
.join("\n\n");
const consume = async (prompt: string, clientMessageId: string) => {
for await (const _event of manager.streamAgent(agent.id, prompt, { clientMessageId })) {
}
};
try {
const first = consume("human", "human");
await manager.waitForAgentRunStart(agent.id);
await expect(
manager.steerAgentRun(agent.id, batch("joined"), { clientMessageId: "joined-one" }),
).resolves.toEqual({ status: "accepted" });
session.pushEvent({ type: "turn_completed", provider: "codex", turnId: "active-turn-1" });
await first;
const second = consume(batch("idle"), "idle-one");
await manager.waitForAgentRunStart(agent.id);
session.pushEvent({ type: "turn_completed", provider: "codex", turnId: "active-turn-2" });
await second;
expect(
manager.fetchTimeline(agent.id, { limit: 0 }).rows.map((row) => ({
turnId: row.turnId,
id: row.item.type === "tool_call" ? row.item.callId : row.item.type,
})),
).toEqual([
{ turnId: "active-turn-1", id: "user_message" },
{ turnId: "active-turn-1", id: "paseo-agent-message:joined-one" },
{ turnId: "active-turn-1", id: "paseo-agent-message:joined-two" },
{ turnId: "active-turn-2", id: "paseo-agent-message:idle-one" },
{ turnId: "active-turn-2", id: "paseo-agent-message:idle-two" },
]);
} finally {
await manager.closeAgent(agent.id);
rmSync(workdir, { recursive: true, force: true });
}
});
test("unavailable steer interrupts once and starts one replacement turn", async () => {
const session = new SteeringTestSession({ provider: "codex", cwd: process.cwd() });
session.steerResult = "unavailable";
@@ -11341,7 +11401,7 @@ test("listImportableSessions searches every provider result before global rankin
]);
});
test("user_message events wrapping a paseo-system envelope are not added to the timeline", async () => {
test("system-injected prompts appear as trigger tools in the live timeline", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-envelope-live-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
@@ -11370,13 +11430,16 @@ test("user_message events wrapping a paseo-system envelope are not added to the
await manager.runAgent(snapshot.id, { text: "do something" });
const timeline = manager.getTimeline(snapshot.id);
expect(timeline).toContainEqual(
expect.objectContaining({ type: "tool_call", name: "heartbeat", status: "completed" }),
);
const userMessages = timeline.filter((item) => item.type === "user_message");
expect(userMessages).toHaveLength(1);
expect(userMessages[0].text).toBe("plain user message");
});
test("user_message events wrapping a paseo-system envelope are not restored during history replay", async () => {
test("system-injected prompts appear as trigger tools during history replay", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-envelope-history-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
@@ -11411,6 +11474,9 @@ test("user_message events wrapping a paseo-system envelope are not restored duri
await manager.hydrateTimelineFromProvider(snapshot.id, { force: true });
const timeline = manager.getTimeline(snapshot.id);
expect(timeline).toContainEqual(
expect.objectContaining({ type: "tool_call", name: "heartbeat", status: "completed" }),
);
const userMessages = timeline.filter((item) => item.type === "user_message");
expect(userMessages).toHaveLength(1);