feat(app): add chat outline navigation (#2792)
* feat(app): add chat outline navigation Long agent sessions need prompt navigation without coupling viewport movement to disposable server windows. Keep fetched timeline pages in client state so repeated jumps stay local, and settle prompt jumps against virtualized row measurement changes. * fix(app): preserve chat outline timeline coverage * fix(app): reconcile outline window submissions * fix(app): harden outline timeline navigation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
AgentTimelineListPromptsResponseMessageSchema,
|
||||
AgentStreamMessageSchema,
|
||||
FetchAgentTimelineResponseMessageSchema,
|
||||
SessionInboundMessageSchema,
|
||||
@@ -9,6 +10,33 @@ import {
|
||||
} from "./messages.js";
|
||||
|
||||
describe("shared messages stream parsing", () => {
|
||||
it("parses a full timeline prompt index response", () => {
|
||||
const parsed = AgentTimelineListPromptsResponseMessageSchema.parse({
|
||||
type: "agent.timeline.list_prompts.response",
|
||||
payload: {
|
||||
requestId: "req-prompts-1",
|
||||
agentId: "agent_live",
|
||||
epoch: "epoch-1",
|
||||
prompts: [
|
||||
{
|
||||
seq: 42,
|
||||
timestamp: "2026-02-08T20:10:00.000Z",
|
||||
preview: "Review the pagination behavior",
|
||||
},
|
||||
],
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(parsed.payload.prompts).toEqual([
|
||||
{
|
||||
seq: 42,
|
||||
timestamp: "2026-02-08T20:10:00.000Z",
|
||||
preview: "Review the pagination behavior",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("parses representative fetch_agent_timeline_response payload", () => {
|
||||
const parsed = FetchAgentTimelineResponseMessageSchema.parse({
|
||||
type: "fetch_agent_timeline_response",
|
||||
|
||||
@@ -1409,6 +1409,14 @@ export const FetchAgentTimelineRequestMessageSchema = z.object({
|
||||
limit: z.number().int().nonnegative().optional(),
|
||||
// Default should be projected for app timeline loading.
|
||||
projection: z.enum(["projected", "canonical"]).optional(),
|
||||
// Allow the client to merge this bounded page outside its contiguous loaded range.
|
||||
mergeWindow: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const AgentTimelineListPromptsRequestMessageSchema = z.object({
|
||||
type: z.literal("agent.timeline.list_prompts.request"),
|
||||
agentId: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ProviderSubagentListRequestMessageSchema = z.object({
|
||||
@@ -2545,6 +2553,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
RestartServerRequestMessageSchema,
|
||||
DaemonUpdateRequestMessageSchema,
|
||||
FetchAgentTimelineRequestMessageSchema,
|
||||
AgentTimelineListPromptsRequestMessageSchema,
|
||||
ProviderSubagentListRequestMessageSchema,
|
||||
ProviderSubagentTimelineRequestMessageSchema,
|
||||
SetAgentTimelineSubscriptionRequestMessageSchema,
|
||||
@@ -2832,6 +2841,8 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
"terminal-restore-modes": z.boolean().optional(),
|
||||
// COMPAT(rewind): added in v0.1.X, drop the gate when floor >= v0.1.X.
|
||||
rewind: z.boolean().optional(),
|
||||
// COMPAT(agentTimelinePromptIndex): added in v0.2.X, drop the gate when floor >= v0.2.X.
|
||||
agentTimelinePromptIndex: z.boolean().optional(),
|
||||
// COMPAT(checkoutRefresh): added in v0.1.86, remove gate after 2026-11-29.
|
||||
checkoutRefresh: z.boolean().optional(),
|
||||
// COMPAT(workspaceMultiplicity): added in v0.1.97, drop the gate when floor >= v0.1.97
|
||||
@@ -3608,11 +3619,29 @@ export const FetchAgentTimelineResponseMessageSchema = z.object({
|
||||
endCursor: AgentTimelineCursorSchema.nullable(),
|
||||
hasOlder: z.boolean(),
|
||||
hasNewer: z.boolean(),
|
||||
mergeWindow: z.boolean().optional(),
|
||||
entries: z.array(AgentTimelineEntryPayloadSchema),
|
||||
error: z.string().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const AgentTimelineListPromptsResponseMessageSchema = z.object({
|
||||
type: z.literal("agent.timeline.list_prompts.response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
agentId: z.string(),
|
||||
epoch: z.string(),
|
||||
prompts: z.array(
|
||||
z.object({
|
||||
seq: z.number().int().nonnegative(),
|
||||
timestamp: z.string(),
|
||||
preview: z.string(),
|
||||
}),
|
||||
),
|
||||
error: z.string().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ProviderSubagentDescriptorPayloadSchema = z.object({
|
||||
id: z.string(),
|
||||
parentAgentId: z.string(),
|
||||
@@ -5308,6 +5337,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
ArchiveWorkspaceResponseMessageSchema,
|
||||
FetchAgentResponseMessageSchema,
|
||||
FetchAgentTimelineResponseMessageSchema,
|
||||
AgentTimelineListPromptsResponseMessageSchema,
|
||||
ProviderSubagentListResponseMessageSchema,
|
||||
ProviderSubagentTimelineResponseMessageSchema,
|
||||
ProviderSubagentUpdateMessageSchema,
|
||||
@@ -5512,6 +5542,9 @@ export type FetchAgentResponseMessage = z.infer<typeof FetchAgentResponseMessage
|
||||
export type FetchAgentTimelineResponseMessage = z.infer<
|
||||
typeof FetchAgentTimelineResponseMessageSchema
|
||||
>;
|
||||
export type AgentTimelineListPromptsResponseMessage = z.infer<
|
||||
typeof AgentTimelineListPromptsResponseMessageSchema
|
||||
>;
|
||||
export type AgentForkContextResponseMessage = z.infer<typeof AgentForkContextResponseMessageSchema>;
|
||||
export type CancelAgentResponseMessage = z.infer<typeof CancelAgentResponseMessageSchema>;
|
||||
export type SendAgentMessageResponseMessage = z.infer<typeof SendAgentMessageResponseMessageSchema>;
|
||||
|
||||
Reference in New Issue
Block a user