-

{
- const query = useConversationsQuery(projectId, sessionId);
+export const useSession = (projectId: string, sessionId: string) => {
+ const query = useSessionQuery(projectId, sessionId);
const toolResultMap = useMemo(() => {
const entries = query.data.session.conversations.flatMap((conversation) => {
@@ -34,10 +34,11 @@ export const useConversations = (projectId: string, sessionId: string) => {
(toolUseId: string) => {
return toolResultMap.get(toolUseId);
},
- [toolResultMap]
+ [toolResultMap],
);
return {
+ session: query.data.session,
conversations: query.data.session.conversations,
getToolResult,
};
diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useConversationsQuery.ts b/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSessionQuery.ts
similarity index 85%
rename from src/app/projects/[projectId]/sessions/[sessionId]/hooks/useConversationsQuery.ts
rename to src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSessionQuery.ts
index 2be0dd5..4929c4b 100644
--- a/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useConversationsQuery.ts
+++ b/src/app/projects/[projectId]/sessions/[sessionId]/hooks/useSessionQuery.ts
@@ -1,7 +1,7 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { honoClient } from "../../../../../../lib/api/client";
-export const useConversationsQuery = (projectId: string, sessionId: string) => {
+export const useSessionQuery = (projectId: string, sessionId: string) => {
return useSuspenseQuery({
queryKey: ["conversations", sessionId],
queryFn: async () => {
diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx
deleted file mode 100644
index 9de175b..0000000
--- a/src/app/projects/[projectId]/sessions/[sessionId]/loading.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Card, CardContent, CardHeader } from "@/components/ui/card";
-import { Skeleton } from "@/components/ui/skeleton";
-
-export default function SessionLoading() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx b/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx
index 23385e5..bebdb95 100644
--- a/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx
+++ b/src/app/projects/[projectId]/sessions/[sessionId]/page.tsx
@@ -1,8 +1,5 @@
-import Link from "next/link";
-import { ArrowLeftIcon, MessageSquareIcon } from "lucide-react";
-import { Button } from "@/components/ui/button";
import type { Metadata } from "next";
-import { ConversationList } from "./components/conversationList/ConversationList";
+import { SessionPageContent } from "./components/SessionPageContent";
type PageParams = {
projectId: string;
@@ -28,31 +25,5 @@ interface SessionPageProps {
export default async function SessionPage({ params }: SessionPageProps) {
const { projectId, sessionId } = await params;
- return (
-
- );
+ return
;
}
diff --git a/src/app/projects/components/ProjectList.tsx b/src/app/projects/components/ProjectList.tsx
index 8047692..f6763d7 100644
--- a/src/app/projects/components/ProjectList.tsx
+++ b/src/app/projects/components/ProjectList.tsx
@@ -1,16 +1,16 @@
"use client";
-import type { FC } from "react";
-import {
- Card,
- CardHeader,
- CardTitle,
- CardDescription,
- CardContent,
-} from "@/components/ui/card";
import { FolderIcon } from "lucide-react";
import Link from "next/link";
+import type { FC } from "react";
import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
import { useProjects } from "../hooks/useProjects";
export const ProjectList: FC = () => {
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
index 1421354..ef36696 100644
--- a/src/components/ui/alert.tsx
+++ b/src/components/ui/alert.tsx
@@ -1,7 +1,7 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
+import { cva, type VariantProps } from "class-variance-authority";
+import type * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
@@ -16,8 +16,8 @@ const alertVariants = cva(
defaultVariants: {
variant: "default",
},
- }
-)
+ },
+);
function Alert({
className,
@@ -31,7 +31,7 @@ function Alert({
className={cn(alertVariants({ variant }), className)}
{...props}
/>
- )
+ );
}
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -40,11 +40,11 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AlertDescription({
@@ -56,11 +56,11 @@ function AlertDescription({
data-slot="alert-description"
className={cn(
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Alert, AlertTitle, AlertDescription }
+export { Alert, AlertTitle, AlertDescription };
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
index 71e428b..11c21d9 100644
--- a/src/components/ui/avatar.tsx
+++ b/src/components/ui/avatar.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+import type * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Avatar({
className,
@@ -14,11 +14,11 @@ function Avatar({
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function AvatarImage({
@@ -31,7 +31,7 @@ function AvatarImage({
className={cn("aspect-square size-full", className)}
{...props}
/>
- )
+ );
}
function AvatarFallback({
@@ -43,11 +43,11 @@ function AvatarFallback({
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { Avatar, AvatarImage, AvatarFallback }
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
index 0205413..d9ebd4a 100644
--- a/src/components/ui/badge.tsx
+++ b/src/components/ui/badge.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import type * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
@@ -22,8 +22,8 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
- }
-)
+ },
+);
function Badge({
className,
@@ -32,7 +32,7 @@ function Badge({
...props
}: React.ComponentProps<"span"> &
VariantProps
& { asChild?: boolean }) {
- const Comp = asChild ? Slot : "span"
+ const Comp = asChild ? Slot : "span";
return (
- )
+ );
}
-export { Badge, badgeVariants }
+export { Badge, badgeVariants };
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index a2df8dc..10a0797 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -1,8 +1,8 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import type * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -32,8 +32,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- }
-)
+ },
+);
function Button({
className,
@@ -43,9 +43,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps & {
- asChild?: boolean
+ asChild?: boolean;
}) {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : "button";
return (
- )
+ );
}
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index d05bbc6..13631ed 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import type * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -8,11 +8,11 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -21,11 +21,11 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
@@ -35,7 +35,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
className={cn("leading-none font-semibold", className)}
{...props}
/>
- )
+ );
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
@@ -45,7 +45,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
- )
+ );
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
@@ -54,11 +54,11 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
@@ -68,7 +68,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
className={cn("px-6", className)}
{...props}
/>
- )
+ );
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -78,7 +78,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
- )
+ );
}
export {
@@ -89,4 +89,4 @@ export {
CardAction,
CardDescription,
CardContent,
-}
+};
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
index ae9fad0..90935c6 100644
--- a/src/components/ui/collapsible.tsx
+++ b/src/components/ui/collapsible.tsx
@@ -1,11 +1,11 @@
-"use client"
+"use client";
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
function Collapsible({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function CollapsibleTrigger({
@@ -16,7 +16,7 @@ function CollapsibleTrigger({
data-slot="collapsible-trigger"
{...props}
/>
- )
+ );
}
function CollapsibleContent({
@@ -27,7 +27,7 @@ function CollapsibleContent({
data-slot="collapsible-content"
{...props}
/>
- )
+ );
}
-export { Collapsible, CollapsibleTrigger, CollapsibleContent }
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx
index e754186..0ef6109 100644
--- a/src/components/ui/hover-card.tsx
+++ b/src/components/ui/hover-card.tsx
@@ -1,14 +1,14 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
+import type * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function HoverCard({
...props
}: React.ComponentProps) {
- return
+ return ;
}
function HoverCardTrigger({
@@ -16,7 +16,7 @@ function HoverCardTrigger({
}: React.ComponentProps) {
return (
- )
+ );
}
function HoverCardContent({
@@ -33,12 +33,12 @@ function HoverCardContent({
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
- className
+ className,
)}
{...props}
/>
- )
+ );
}
-export { HoverCard, HoverCardTrigger, HoverCardContent }
+export { HoverCard, HoverCardTrigger, HoverCardContent };
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
index 32ea0ef..0168998 100644
--- a/src/components/ui/skeleton.tsx
+++ b/src/components/ui/skeleton.tsx
@@ -1,4 +1,4 @@
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
@@ -7,7 +7,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props}
/>
- )
+ );
}
-export { Skeleton }
+export { Skeleton };
diff --git a/src/lib/conversation-schema/content/ImageContentSchema.ts b/src/lib/conversation-schema/content/ImageContentSchema.ts
index a08df21..92fa1ac 100644
--- a/src/lib/conversation-schema/content/ImageContentSchema.ts
+++ b/src/lib/conversation-schema/content/ImageContentSchema.ts
@@ -1,10 +1,12 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const ImageContentSchema = z.object({
- type: z.literal('image'),
- source: z.object({
- type: z.literal('base64'),
- data: z.string(),
- media_type: z.enum(['image/png']),
- }),
-}).strict()
+export const ImageContentSchema = z
+ .object({
+ type: z.literal("image"),
+ source: z.object({
+ type: z.literal("base64"),
+ data: z.string(),
+ media_type: z.enum(["image/png"]),
+ }),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/content/TextContentSchema.ts b/src/lib/conversation-schema/content/TextContentSchema.ts
index 46eb68b..d2f0739 100644
--- a/src/lib/conversation-schema/content/TextContentSchema.ts
+++ b/src/lib/conversation-schema/content/TextContentSchema.ts
@@ -1,6 +1,8 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const TextContentSchema = z.object({
- type: z.literal('text'),
- text: z.string(),
-}).strict()
+export const TextContentSchema = z
+ .object({
+ type: z.literal("text"),
+ text: z.string(),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/content/ThinkingContentSchema.ts b/src/lib/conversation-schema/content/ThinkingContentSchema.ts
index b77bb1d..601e4a5 100644
--- a/src/lib/conversation-schema/content/ThinkingContentSchema.ts
+++ b/src/lib/conversation-schema/content/ThinkingContentSchema.ts
@@ -1,7 +1,9 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const ThinkingContentSchema = z.object({
- type: z.literal('thinking'),
- thinking: z.string(),
- signature: z.string().optional(),
-}).strict()
+export const ThinkingContentSchema = z
+ .object({
+ type: z.literal("thinking"),
+ thinking: z.string(),
+ signature: z.string().optional(),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/content/ToolResultContentSchema.ts b/src/lib/conversation-schema/content/ToolResultContentSchema.ts
index 0fe2a49..78ccd07 100644
--- a/src/lib/conversation-schema/content/ToolResultContentSchema.ts
+++ b/src/lib/conversation-schema/content/ToolResultContentSchema.ts
@@ -1,16 +1,17 @@
-import { z } from 'zod'
-import { TextContentSchema } from './TextContentSchema'
-import { ImageContentSchema } from './ImageContentSchema'
+import { z } from "zod";
+import { ImageContentSchema } from "./ImageContentSchema";
+import { TextContentSchema } from "./TextContentSchema";
-export const ToolResultContentSchema = z.object({
- type: z.literal('tool_result'),
- tool_use_id: z.string(),
- content: z.union([z.string(), z.array(z.union([
- TextContentSchema,
- ImageContentSchema
- ]))]),
- is_error: z.boolean().optional(),
-}).strict()
+export const ToolResultContentSchema = z
+ .object({
+ type: z.literal("tool_result"),
+ tool_use_id: z.string(),
+ content: z.union([
+ z.string(),
+ z.array(z.union([TextContentSchema, ImageContentSchema])),
+ ]),
+ is_error: z.boolean().optional(),
+ })
+ .strict();
-
-export type ToolResultContent = z.infer
+export type ToolResultContent = z.infer;
diff --git a/src/lib/conversation-schema/content/ToolUseContentSchema.ts b/src/lib/conversation-schema/content/ToolUseContentSchema.ts
index 799285c..9852c69 100644
--- a/src/lib/conversation-schema/content/ToolUseContentSchema.ts
+++ b/src/lib/conversation-schema/content/ToolUseContentSchema.ts
@@ -1,8 +1,10 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const ToolUseContentSchema = z.object({
- type: z.literal('tool_use'),
- id: z.string(),
- name: z.string(),
- input: z.record(z.string(), z.unknown()),
-}).strict()
+export const ToolUseContentSchema = z
+ .object({
+ type: z.literal("tool_use"),
+ id: z.string(),
+ name: z.string(),
+ input: z.record(z.string(), z.unknown()),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/entry/AssistantEntrySchema.ts b/src/lib/conversation-schema/entry/AssistantEntrySchema.ts
index d4130fc..80200a4 100644
--- a/src/lib/conversation-schema/entry/AssistantEntrySchema.ts
+++ b/src/lib/conversation-schema/entry/AssistantEntrySchema.ts
@@ -1,10 +1,10 @@
-import { z } from 'zod'
-import { AssistantMessageSchema } from '../message/AssistantMessageSchema'
-import { BaseEntrySchema } from './BaseEntrySchema'
+import { z } from "zod";
+import { AssistantMessageSchema } from "../message/AssistantMessageSchema";
+import { BaseEntrySchema } from "./BaseEntrySchema";
export const AssistantEntrySchema = BaseEntrySchema.extend({
// discriminator
- type: z.literal('assistant'),
+ type: z.literal("assistant"),
// required
message: AssistantMessageSchema,
@@ -12,4 +12,4 @@ export const AssistantEntrySchema = BaseEntrySchema.extend({
// optional
requestId: z.string().optional(),
isApiErrorMessage: z.boolean().optional(),
-}).strict()
+}).strict();
diff --git a/src/lib/conversation-schema/entry/BaseEntrySchema.ts b/src/lib/conversation-schema/entry/BaseEntrySchema.ts
index 12d1498..e97aec1 100644
--- a/src/lib/conversation-schema/entry/BaseEntrySchema.ts
+++ b/src/lib/conversation-schema/entry/BaseEntrySchema.ts
@@ -1,21 +1,23 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const BaseEntrySchema = z.object({
- // required
- isSidechain: z.boolean(),
- userType: z.enum(['external']),
- cwd: z.string(),
- sessionId: z.string(),
- version: z.string(),
- uuid: z.uuid(),
- timestamp: z.string(),
+export const BaseEntrySchema = z
+ .object({
+ // required
+ isSidechain: z.boolean(),
+ userType: z.enum(["external"]),
+ cwd: z.string(),
+ sessionId: z.string(),
+ version: z.string(),
+ uuid: z.uuid(),
+ timestamp: z.string(),
- // nullable
- parentUuid: z.uuid().nullable(),
+ // nullable
+ parentUuid: z.uuid().nullable(),
- // optional
- isMeta: z.boolean().optional(),
- toolUseResult: z.unknown().optional(), // スキーマがツールごとに異なりすぎるし利用もしなそうなので unknown
- gitBranch: z.string().optional(),
- isCompactSummary: z.boolean().optional(),
-}).strict()
+ // optional
+ isMeta: z.boolean().optional(),
+ toolUseResult: z.unknown().optional(), // スキーマがツールごとに異なりすぎるし利用もしなそうなので unknown
+ gitBranch: z.string().optional(),
+ isCompactSummary: z.boolean().optional(),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/entry/SummaryEntrySchema.ts b/src/lib/conversation-schema/entry/SummaryEntrySchema.ts
index 7c798d1..1fac63c 100644
--- a/src/lib/conversation-schema/entry/SummaryEntrySchema.ts
+++ b/src/lib/conversation-schema/entry/SummaryEntrySchema.ts
@@ -1,7 +1,9 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const SummaryEntrySchema = z.object({
- type: z.literal('summary'),
- summary: z.string(),
- leafUuid: z.string().uuid(),
-}).strict()
+export const SummaryEntrySchema = z
+ .object({
+ type: z.literal("summary"),
+ summary: z.string(),
+ leafUuid: z.string().uuid(),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/entry/SystemEntrySchema.ts b/src/lib/conversation-schema/entry/SystemEntrySchema.ts
index a232731..d6fa9eb 100644
--- a/src/lib/conversation-schema/entry/SystemEntrySchema.ts
+++ b/src/lib/conversation-schema/entry/SystemEntrySchema.ts
@@ -1,12 +1,12 @@
-import { z } from 'zod'
-import { BaseEntrySchema } from './BaseEntrySchema'
+import { z } from "zod";
+import { BaseEntrySchema } from "./BaseEntrySchema";
export const SystemEntrySchema = BaseEntrySchema.extend({
// discriminator
- type: z.literal('system'),
+ type: z.literal("system"),
// required
content: z.string(),
toolUseID: z.string(),
- level: z.enum(['info']),
-}).strict()
+ level: z.enum(["info"]),
+}).strict();
diff --git a/src/lib/conversation-schema/entry/UserEntrySchema.ts b/src/lib/conversation-schema/entry/UserEntrySchema.ts
index 56f1f13..c2e5794 100644
--- a/src/lib/conversation-schema/entry/UserEntrySchema.ts
+++ b/src/lib/conversation-schema/entry/UserEntrySchema.ts
@@ -1,11 +1,11 @@
-import { z } from 'zod'
-import { UserMessageSchema } from '../message/UserMessageSchema'
-import { BaseEntrySchema } from './BaseEntrySchema'
+import { z } from "zod";
+import { UserMessageSchema } from "../message/UserMessageSchema";
+import { BaseEntrySchema } from "./BaseEntrySchema";
export const UserEntrySchema = BaseEntrySchema.extend({
// discriminator
- type: z.literal('user'),
+ type: z.literal("user"),
// required
message: UserMessageSchema,
-}).strict()
+}).strict();
diff --git a/src/lib/conversation-schema/index.ts b/src/lib/conversation-schema/index.ts
index 1f533b9..9224a8c 100644
--- a/src/lib/conversation-schema/index.ts
+++ b/src/lib/conversation-schema/index.ts
@@ -1,14 +1,14 @@
-import { z } from 'zod'
-import { UserEntrySchema } from './entry/UserEntrySchema'
-import { AssistantEntrySchema } from './entry/AssistantEntrySchema'
-import { SummaryEntrySchema } from './entry/SummaryEntrySchema'
-import { SystemEntrySchema } from './entry/SystemEntrySchema'
+import { z } from "zod";
+import { AssistantEntrySchema } from "./entry/AssistantEntrySchema";
+import { SummaryEntrySchema } from "./entry/SummaryEntrySchema";
+import { SystemEntrySchema } from "./entry/SystemEntrySchema";
+import { UserEntrySchema } from "./entry/UserEntrySchema";
export const ConversationSchema = z.union([
UserEntrySchema,
AssistantEntrySchema,
SummaryEntrySchema,
SystemEntrySchema,
-])
+]);
-export type Conversation = z.infer
+export type Conversation = z.infer;
diff --git a/src/lib/conversation-schema/message/AssistantMessageSchema.ts b/src/lib/conversation-schema/message/AssistantMessageSchema.ts
index de16f6d..39105a1 100644
--- a/src/lib/conversation-schema/message/AssistantMessageSchema.ts
+++ b/src/lib/conversation-schema/message/AssistantMessageSchema.ts
@@ -1,38 +1,46 @@
-import { z } from 'zod'
-import { ThinkingContentSchema } from '../content/ThinkingContentSchema'
-import { TextContentSchema } from '../content/TextContentSchema'
-import { ToolUseContentSchema } from '../content/ToolUseContentSchema'
-import { ToolResultContentSchema } from '../content/ToolResultContentSchema'
+import { z } from "zod";
+import { TextContentSchema } from "../content/TextContentSchema";
+import { ThinkingContentSchema } from "../content/ThinkingContentSchema";
+import { ToolResultContentSchema } from "../content/ToolResultContentSchema";
+import { ToolUseContentSchema } from "../content/ToolUseContentSchema";
const AssistantMessageContentSchema = z.union([
ThinkingContentSchema,
TextContentSchema,
ToolUseContentSchema,
ToolResultContentSchema,
-])
+]);
-export type AssistantMessageContent = z.infer
+export type AssistantMessageContent = z.infer<
+ typeof AssistantMessageContentSchema
+>;
-export const AssistantMessageSchema = z.object({
- id: z.string(),
- type: z.literal('message'),
- role: z.literal('assistant'),
- model: z.string(),
- content: z.array(AssistantMessageContentSchema),
- stop_reason: z.string().nullable(),
- stop_sequence: z.string().nullable(),
- usage: z.object({
- input_tokens: z.number(),
- cache_creation_input_tokens: z.number().optional(),
- cache_read_input_tokens: z.number().optional(),
- cache_creation: z.object({
- ephemeral_5m_input_tokens: z.number(),
- ephemeral_1h_input_tokens: z.number(),
- }).optional(),
- output_tokens: z.number(),
- service_tier: z.string().nullable().optional(),
- server_tool_use: z.object({
- web_search_requests: z.number(),
- }).optional(),
- }),
-}).strict()
+export const AssistantMessageSchema = z
+ .object({
+ id: z.string(),
+ type: z.literal("message"),
+ role: z.literal("assistant"),
+ model: z.string(),
+ content: z.array(AssistantMessageContentSchema),
+ stop_reason: z.string().nullable(),
+ stop_sequence: z.string().nullable(),
+ usage: z.object({
+ input_tokens: z.number(),
+ cache_creation_input_tokens: z.number().optional(),
+ cache_read_input_tokens: z.number().optional(),
+ cache_creation: z
+ .object({
+ ephemeral_5m_input_tokens: z.number(),
+ ephemeral_1h_input_tokens: z.number(),
+ })
+ .optional(),
+ output_tokens: z.number(),
+ service_tier: z.string().nullable().optional(),
+ server_tool_use: z
+ .object({
+ web_search_requests: z.number(),
+ })
+ .optional(),
+ }),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/message/UserMessageSchema.ts b/src/lib/conversation-schema/message/UserMessageSchema.ts
index 675e496..85239c7 100644
--- a/src/lib/conversation-schema/message/UserMessageSchema.ts
+++ b/src/lib/conversation-schema/message/UserMessageSchema.ts
@@ -1,25 +1,23 @@
-import { z } from 'zod'
-import { ToolResultContentSchema } from '../content/ToolResultContentSchema'
-import { TextContentSchema } from '../content/TextContentSchema'
-import { ImageContentSchema } from '../content/ImageContentSchema'
-
+import { z } from "zod";
+import { ImageContentSchema } from "../content/ImageContentSchema";
+import { TextContentSchema } from "../content/TextContentSchema";
+import { ToolResultContentSchema } from "../content/ToolResultContentSchema";
const UserMessageContentSchema = z.union([
z.string(),
TextContentSchema,
ToolResultContentSchema,
- ImageContentSchema
-])
+ ImageContentSchema,
+]);
-export type UserMessageContent = z.infer
+export type UserMessageContent = z.infer;
-export const UserMessageSchema = z.object({
- role: z.literal('user'),
- content: z.union([
- z.string(),
- z.array(z.union([
+export const UserMessageSchema = z
+ .object({
+ role: z.literal("user"),
+ content: z.union([
z.string(),
- UserMessageContentSchema
- ]))
- ]),
-}).strict()
+ z.array(z.union([z.string(), UserMessageContentSchema])),
+ ]),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/tool/CommonToolSchema.ts b/src/lib/conversation-schema/tool/CommonToolSchema.ts
index 132982f..ffcf75b 100644
--- a/src/lib/conversation-schema/tool/CommonToolSchema.ts
+++ b/src/lib/conversation-schema/tool/CommonToolSchema.ts
@@ -1,59 +1,71 @@
-import { z } from 'zod'
-import { StructuredPatchSchema } from './StructuredPatchSchema'
+import { z } from "zod";
+import { StructuredPatchSchema } from "./StructuredPatchSchema";
export const CommonToolResultSchema = z.union([
- z.object({
- stdout: z.string(),
- stderr: z.string(),
- interrupted: z.boolean(),
- isImage: z.boolean(),
- }).strict(),
+ z
+ .object({
+ stdout: z.string(),
+ stderr: z.string(),
+ interrupted: z.boolean(),
+ isImage: z.boolean(),
+ })
+ .strict(),
// create
- z.object({
- type: z.literal('create'),
- filePath: z.string(),
- content: z.string(),
- structuredPatch: z.array(StructuredPatchSchema)
- }).strict(),
-
- // update
- z.object({
- filePath: z.string(),
- oldString: z.string(),
- newString: z.string(),
- originalFile: z.string(),
- userModified: z.boolean(),
- replaceAll: z.boolean(),
- structuredPatch: z.array(StructuredPatchSchema)
- }).strict(),
-
- // search?
- z.object({
- filenames: z.array(z.string()),
- durationMs: z.number(),
- numFiles: z.number(),
- truncated: z.boolean(),
- }).strict(),
-
- // text
- z.object({
- type: z.literal('text'),
- file: z.object({
+ z
+ .object({
+ type: z.literal("create"),
filePath: z.string(),
content: z.string(),
- numLines: z.number(),
- startLine: z.number(),
- totalLines: z.number(),
+ structuredPatch: z.array(StructuredPatchSchema),
})
- }).strict(),
+ .strict(),
+
+ // update
+ z
+ .object({
+ filePath: z.string(),
+ oldString: z.string(),
+ newString: z.string(),
+ originalFile: z.string(),
+ userModified: z.boolean(),
+ replaceAll: z.boolean(),
+ structuredPatch: z.array(StructuredPatchSchema),
+ })
+ .strict(),
+
+ // search?
+ z
+ .object({
+ filenames: z.array(z.string()),
+ durationMs: z.number(),
+ numFiles: z.number(),
+ truncated: z.boolean(),
+ })
+ .strict(),
+
+ // text
+ z
+ .object({
+ type: z.literal("text"),
+ file: z.object({
+ filePath: z.string(),
+ content: z.string(),
+ numLines: z.number(),
+ startLine: z.number(),
+ totalLines: z.number(),
+ }),
+ })
+ .strict(),
// content
- z.object({
- mode: z.literal('content'),
- numFiles: z.number(),
- filenames: z.array(z.string()),
- content: z.string(),
- numLines: z.number(),
- }).strict(),
-])
+ z
+ .object({
+ mode: z.literal("content"),
+ numFiles: z.number(),
+ filenames: z.array(z.string()),
+ content: z.string(),
+ numLines: z.number(),
+ })
+ .strict(),
+]);
diff --git a/src/lib/conversation-schema/tool/StructuredPatchSchema.ts b/src/lib/conversation-schema/tool/StructuredPatchSchema.ts
index 8922a65..5db20c8 100644
--- a/src/lib/conversation-schema/tool/StructuredPatchSchema.ts
+++ b/src/lib/conversation-schema/tool/StructuredPatchSchema.ts
@@ -1,9 +1,11 @@
-import { z } from 'zod'
+import { z } from "zod";
-export const StructuredPatchSchema = z.object({
- oldStart: z.number(),
- oldLines: z.number(),
- newStart: z.number(),
- newLines: z.number(),
- lines: z.array(z.string()),
-}).strict()
+export const StructuredPatchSchema = z
+ .object({
+ oldStart: z.number(),
+ oldLines: z.number(),
+ newStart: z.number(),
+ newLines: z.number(),
+ lines: z.array(z.string()),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/tool/TodoSchema.ts b/src/lib/conversation-schema/tool/TodoSchema.ts
index 61e16b4..914eac3 100644
--- a/src/lib/conversation-schema/tool/TodoSchema.ts
+++ b/src/lib/conversation-schema/tool/TodoSchema.ts
@@ -1,13 +1,17 @@
import z from "zod";
-const TodoSchema = z.object({
- content: z.string(),
- status: z.enum(['pending', 'in_progress', 'completed']),
- priority: z.enum(['low', 'medium', 'high']),
- id: z.string(),
-}).strict()
+const TodoSchema = z
+ .object({
+ content: z.string(),
+ status: z.enum(["pending", "in_progress", "completed"]),
+ priority: z.enum(["low", "medium", "high"]),
+ id: z.string(),
+ })
+ .strict();
-export const TodoToolResultSchema = z.object({
- oldTodos: z.array(TodoSchema).optional(),
- newTodos: z.array(TodoSchema).optional(),
-}).strict()
+export const TodoToolResultSchema = z
+ .object({
+ oldTodos: z.array(TodoSchema).optional(),
+ newTodos: z.array(TodoSchema).optional(),
+ })
+ .strict();
diff --git a/src/lib/conversation-schema/tool/index.ts b/src/lib/conversation-schema/tool/index.ts
index 48d2fa5..305d24d 100644
--- a/src/lib/conversation-schema/tool/index.ts
+++ b/src/lib/conversation-schema/tool/index.ts
@@ -1,6 +1,6 @@
import { z } from "zod";
-import { TodoToolResultSchema } from "./TodoSchema";
import { CommonToolResultSchema } from "./CommonToolSchema";
+import { TodoToolResultSchema } from "./TodoSchema";
export const ToolUseResultSchema = z.union([
z.string(),
diff --git a/src/server/hono/route.ts b/src/server/hono/route.ts
index 70b590b..fc501aa 100644
--- a/src/server/hono/route.ts
+++ b/src/server/hono/route.ts
@@ -1,8 +1,8 @@
-import type { HonoAppType } from "./app";
-import { getProjects } from "../service/project/getProjects";
import { getProject } from "../service/project/getProject";
-import { getSessions } from "../service/session/getSessions";
+import { getProjects } from "../service/project/getProjects";
import { getSession } from "../service/session/getSession";
+import { getSessions } from "../service/session/getSessions";
+import type { HonoAppType } from "./app";
export const routes = (app: HonoAppType) => {
return app
diff --git a/src/server/service/parseCommandXml.ts b/src/server/service/parseCommandXml.ts
index d00388a..4b936be 100644
--- a/src/server/service/parseCommandXml.ts
+++ b/src/server/service/parseCommandXml.ts
@@ -7,9 +7,7 @@ const matchSchema = z.object({
content: z.string(),
});
-export const parseCommandXml = (
- content: string
-):
+export type ParsedCommand =
| {
kind: "command";
commandName: string;
@@ -28,7 +26,9 @@ export const parseCommandXml = (
| {
kind: "text";
content: string;
- } => {
+ };
+
+export const parseCommandXml = (content: string): ParsedCommand => {
const matches = Array.from(content.matchAll(regExp))
.map((match) => matchSchema.safeParse(match.groups))
.filter((result) => result.success)
@@ -42,16 +42,16 @@ export const parseCommandXml = (
}
const commandName = matches.find(
- (match) => match.tag === "command-name"
+ (match) => match.tag === "command-name",
)?.content;
const commandArgs = matches.find(
- (match) => match.tag === "command-args"
+ (match) => match.tag === "command-args",
)?.content;
const commandMessage = matches.find(
- (match) => match.tag === "command-message"
+ (match) => match.tag === "command-message",
)?.content;
const localCommandStdout = matches.find(
- (match) => match.tag === "local-command-stdout"
+ (match) => match.tag === "local-command-stdout",
)?.content;
switch (true) {
diff --git a/src/server/service/project/getProject.ts b/src/server/service/project/getProject.ts
index 09b9e24..4af646d 100644
--- a/src/server/service/project/getProject.ts
+++ b/src/server/service/project/getProject.ts
@@ -1,11 +1,11 @@
import { existsSync } from "node:fs";
import type { Project } from "../types";
-import { decodeProjectId } from "./id";
import { getProjectMeta } from "./getProjectMeta";
+import { decodeProjectId } from "./id";
export const getProject = async (
- projectId: string
+ projectId: string,
): Promise<{ project: Project }> => {
const fullPath = decodeProjectId(projectId);
if (!existsSync(fullPath)) {
diff --git a/src/server/service/project/getProjectMeta.ts b/src/server/service/project/getProjectMeta.ts
index 5713616..aeb8618 100644
--- a/src/server/service/project/getProjectMeta.ts
+++ b/src/server/service/project/getProjectMeta.ts
@@ -1,13 +1,18 @@
import { statSync } from "node:fs";
import { readdir, readFile } from "node:fs/promises";
-import { basename, dirname, resolve } from "node:path";
+import { basename, resolve } from "node:path";
import { parseJsonl } from "../parseJsonl";
import type { ProjectMeta } from "../types";
-const projectMetaCache = new Map();
+const projectPathCache = new Map();
+
+const extractProjectPathFromJsonl = async (filePath: string) => {
+ const cached = projectPathCache.get(filePath);
+ if (cached !== undefined) {
+ return cached;
+ }
-const extractMetaFromJsonl = async (filePath: string) => {
const content = await readFile(filePath, "utf-8");
const lines = content.split("\n");
@@ -25,19 +30,16 @@ const extractMetaFromJsonl = async (filePath: string) => {
break;
}
- return {
- cwd,
- } as const;
+ if (cwd !== null) {
+ projectPathCache.set(filePath, cwd);
+ }
+
+ return cwd;
};
export const getProjectMeta = async (
- claudeProjectPath: string
+ claudeProjectPath: string,
): Promise => {
- const cached = projectMetaCache.get(claudeProjectPath);
- if (cached !== undefined) {
- return cached;
- }
-
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
const files = dirents
.filter((d) => d.isFile() && d.name.endsWith(".jsonl"))
@@ -46,7 +48,7 @@ export const getProjectMeta = async (
({
fullPath: resolve(d.parentPath, d.name),
stats: statSync(resolve(d.parentPath, d.name)),
- } as const)
+ }) as const,
)
.toSorted((a, b) => {
return a.stats.ctime.getTime() - b.stats.ctime.getTime();
@@ -54,30 +56,26 @@ export const getProjectMeta = async (
const lastModifiedUnixTime = files.at(-1)?.stats.ctime.getTime();
- let cwd: string | null = null;
+ let projectPath: string | null = null;
for (const file of files) {
- const result = await extractMetaFromJsonl(file.fullPath);
+ projectPath = await extractProjectPathFromJsonl(file.fullPath);
- if (result.cwd === null) {
+ if (projectPath === null) {
continue;
}
- cwd = result.cwd;
-
break;
}
const projectMeta: ProjectMeta = {
- projectName: cwd ? basename(cwd) : null,
- projectPath: cwd,
+ projectName: projectPath ? basename(projectPath) : null,
+ projectPath,
lastModifiedAt: lastModifiedUnixTime
? new Date(lastModifiedUnixTime)
: null,
sessionCount: files.length,
};
- projectMetaCache.set(claudeProjectPath, projectMeta);
-
return projectMeta;
};
diff --git a/src/server/service/project/getProjects.ts b/src/server/service/project/getProjects.ts
index 731c5cf..7b75553 100644
--- a/src/server/service/project/getProjects.ts
+++ b/src/server/service/project/getProjects.ts
@@ -3,8 +3,8 @@ import { resolve } from "node:path";
import { claudeProjectPath } from "../paths";
import type { Project } from "../types";
-import { encodeProjectId } from "./id";
import { getProjectMeta } from "./getProjectMeta";
+import { encodeProjectId } from "./id";
export const getProjects = async (): Promise<{ projects: Project[] }> => {
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
@@ -24,6 +24,11 @@ export const getProjects = async (): Promise<{ projects: Project[] }> => {
);
return {
- projects,
+ projects: projects.sort((a, b) => {
+ return (
+ (b.meta.lastModifiedAt?.getTime() ?? 0) -
+ (a.meta.lastModifiedAt?.getTime() ?? 0)
+ );
+ }),
};
};
diff --git a/src/server/service/session/getSession.ts b/src/server/service/session/getSession.ts
index 509f078..7c7e612 100644
--- a/src/server/service/session/getSession.ts
+++ b/src/server/service/session/getSession.ts
@@ -1,13 +1,13 @@
import { readFile } from "node:fs/promises";
-import { decodeProjectId } from "../project/id";
import { resolve } from "node:path";
import { parseJsonl } from "../parseJsonl";
+import { decodeProjectId } from "../project/id";
import type { SessionDetail } from "../types";
import { getSessionMeta } from "./getSessionMeta";
export const getSession = async (
projectId: string,
- sessionId: string
+ sessionId: string,
): Promise<{
session: SessionDetail;
}> => {
diff --git a/src/server/service/session/getSessionMeta.ts b/src/server/service/session/getSessionMeta.ts
index bbc371c..3862b1d 100644
--- a/src/server/service/session/getSessionMeta.ts
+++ b/src/server/service/session/getSessionMeta.ts
@@ -1,26 +1,21 @@
import { statSync } from "node:fs";
import { readFile } from "node:fs/promises";
-
-import { parseJsonl } from "../parseJsonl";
import type { Conversation } from "../../../lib/conversation-schema";
+import { type ParsedCommand, parseCommandXml } from "../parseCommandXml";
+import { parseJsonl } from "../parseJsonl";
import type { SessionMeta } from "../types";
-const sessionMetaCache = new Map();
+const firstCommandCache = new Map();
-export const getSessionMeta = async (
- jsonlFilePath: string
-): Promise => {
- const cached = sessionMetaCache.get(jsonlFilePath);
+const getFirstCommand = (
+ jsonlFilePath: string,
+ lines: string[],
+): ParsedCommand | null => {
+ const cached = firstCommandCache.get(jsonlFilePath);
if (cached !== undefined) {
return cached;
}
- const stats = statSync(jsonlFilePath);
- const lastModifiedUnixTime = stats.ctime.getTime();
-
- const content = await readFile(jsonlFilePath, "utf-8");
- const lines = content.split("\n");
-
let firstUserMessage: Conversation | null = null;
for (const line of lines) {
@@ -35,12 +30,10 @@ export const getSessionMeta = async (
break;
}
- const sessionMeta: SessionMeta = {
- messageCount: lines.length,
- firstContent:
- firstUserMessage === null
- ? null
- : typeof firstUserMessage.message.content === "string"
+ const firstMessageText =
+ firstUserMessage === null
+ ? null
+ : typeof firstUserMessage.message.content === "string"
? firstUserMessage.message.content
: (() => {
const firstContent = firstUserMessage.message.content.at(0);
@@ -48,13 +41,34 @@ export const getSessionMeta = async (
if (typeof firstContent === "string") return firstContent;
if (firstContent.type === "text") return firstContent.text;
return null;
- })(),
+ })();
+
+ const firstCommand =
+ firstMessageText === null ? null : parseCommandXml(firstMessageText);
+
+ if (firstCommand !== null) {
+ firstCommandCache.set(jsonlFilePath, firstCommand);
+ }
+
+ return firstCommand;
+};
+
+export const getSessionMeta = async (
+ jsonlFilePath: string,
+): Promise => {
+ const stats = statSync(jsonlFilePath);
+ const lastModifiedUnixTime = stats.ctime.getTime();
+
+ const content = await readFile(jsonlFilePath, "utf-8");
+ const lines = content.split("\n");
+
+ const sessionMeta: SessionMeta = {
+ messageCount: lines.length,
+ firstCommand: getFirstCommand(jsonlFilePath, lines),
lastModifiedAt: lastModifiedUnixTime
? new Date(lastModifiedUnixTime)
: null,
};
- sessionMetaCache.set(jsonlFilePath, sessionMeta);
-
return sessionMeta;
};
diff --git a/src/server/service/session/getSessions.ts b/src/server/service/session/getSessions.ts
index 0c8f2da..6b24043 100644
--- a/src/server/service/session/getSessions.ts
+++ b/src/server/service/session/getSessions.ts
@@ -26,6 +26,11 @@ export const getSessions = async (
);
return {
- sessions,
+ sessions: sessions.sort((a, b) => {
+ return (
+ (b.meta.lastModifiedAt?.getTime() ?? 0) -
+ (a.meta.lastModifiedAt?.getTime() ?? 0)
+ );
+ }),
};
};
diff --git a/src/server/service/types.ts b/src/server/service/types.ts
index 9e120fc..f32c4c9 100644
--- a/src/server/service/types.ts
+++ b/src/server/service/types.ts
@@ -1,4 +1,5 @@
import type { Conversation } from "../../lib/conversation-schema";
+import type { ParsedCommand } from "./parseCommandXml";
export type Project = {
id: string;
@@ -21,7 +22,7 @@ export type Session = {
export type SessionMeta = {
messageCount: number;
- firstContent: string | null;
+ firstCommand: ParsedCommand | null;
lastModifiedAt: Date | null;
};