Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 24 additions & 55 deletions apps/web/src/app/(app)/cloud/sessions/SessionsPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { OpenInEditorButton } from '@/app/share/[shareId]/open-in-editor-button';
import { OpenInCliButton } from '@/app/share/[shareId]/open-in-cli-button';
import { CopyableCommand } from '@/components/CopyableCommand';
import { usePathname } from 'next/navigation';
import Link from 'next/link';
Expand Down Expand Up @@ -53,7 +52,7 @@ export function SessionsPageContent() {
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
const [platformFilter, setPlatformFilter] = useState<PlatformFilterValue>('all');
const [includeSubSessions, setIncludeSubSessions] = useState(false);
type SessionWithSource = SessionsListItem & { source: 'v1' | 'v2' };
type SessionWithSource = SessionsListItem & { source: 'v2' };
const [selectedSession, setSelectedSession] = useState<SessionWithSource | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);

Expand All @@ -76,7 +75,7 @@ export function SessionsPageContent() {
// Query for listing sessions (when not searching)
// Order by updated_at and filter by organization and platform
const { data: listData, isLoading: isListLoading } = useQuery(
trpc.unifiedSessions.list.queryOptions({
trpc.cliSessionsV2.list.queryOptions({
limit: 50,
orderBy: 'updated_at',
organizationId: organizationId ?? null,
Expand All @@ -86,13 +85,13 @@ export function SessionsPageContent() {
: platformFilter === 'cloud-agent'
? ['cloud-agent', 'cloud-agent-web']
: platformFilter,
includeSubSessions,
includeChildren: includeSubSessions,
})
);

// Query for searching sessions (uses debounced value)
const { data: searchData, isLoading: isSearchLoading } = useQuery({
...trpc.unifiedSessions.search.queryOptions({
...trpc.cliSessionsV2.search.queryOptions({
search_string: debouncedSearchQuery.trim(),
limit: 50,
offset: 0,
Expand All @@ -103,21 +102,20 @@ export function SessionsPageContent() {
: platformFilter === 'cloud-agent'
? ['cloud-agent', 'cloud-agent-web']
: platformFilter,
includeSubSessions,
includeChildren: includeSubSessions,
}),
enabled: isSearching,
});

// Convert API session to StoredSession format
const convertToStoredSession = (session: {
session_id: string;
title: string;
title: string | null;
git_url: string | null;
created_at: string;
updated_at: string;
created_on_platform: string;
cloud_agent_session_id: string | null;
source: 'v1' | 'v2';
}): SessionWithSource => {
const repository = extractRepoFromGitUrl(session.git_url) ?? null;
const prompt = session.title || 'Untitled';
Expand All @@ -129,7 +127,7 @@ export function SessionsPageContent() {
repository,
sessionId: session.session_id,
mode: '',
source: session.source,
source: 'v2' as const,
};
};

Expand Down Expand Up @@ -261,53 +259,24 @@ export function SessionsPageContent() {
Fork this session to continue working on it in your editor or CLI
</p>

{selectedSession.source === 'v1' && (
<>
{/* Open in Editor (v1 only) */}
<div className="flex justify-center">
<OpenInEditorButton sessionId={selectedSession.sessionId} />
</div>
{/* Open in Editor */}
<div className="flex justify-center">
<OpenInEditorButton
sessionId={selectedSession.sessionId}
pathOverride={`/s/${selectedSession.sessionId}`}
/>
</div>

{/* Open in CLI (v1 only) */}
<div className="flex justify-center">
<OpenInCliButton command={`kilocode --fork ${selectedSession.sessionId}`} />
</div>

{/* Manual fork command (v1) */}
<div className="space-y-2">
<p className="text-muted-foreground text-xs">
Or use the fork command manually:
</p>
<CopyableCommand
command={`/session fork ${selectedSession.sessionId}`}
className="bg-muted rounded-md px-3 py-2 text-sm"
/>
</div>
</>
)}

{selectedSession.source === 'v2' && (
<>
{/* Open in Editor (v2) */}
<div className="flex justify-center">
<OpenInEditorButton
sessionId={selectedSession.sessionId}
pathOverride={`/s/${selectedSession.sessionId}`}
/>
</div>

{/* Fork in CLI (v2) */}
<div className="space-y-2">
<p className="text-muted-foreground text-xs">
Or use the CLI to fork this session:
</p>
<CopyableCommand
command={`kilo --session ${selectedSession.sessionId} --cloud-fork`}
className="bg-muted rounded-md px-3 py-2 text-sm"
/>
</div>
</>
)}
{/* Fork in CLI */}
<div className="space-y-2">
<p className="text-muted-foreground text-xs">
Or use the CLI to fork this session:
</p>
<CopyableCommand
command={`kilo --session ${selectedSession.sessionId} --cloud-fork`}
className="bg-muted rounded-md px-3 py-2 text-sm"
/>
</div>
</div>
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/cloud-agent-next/CloudChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export default function CloudChatPage({ organizationId }: CloudChatPageProps) {
useEffect(() => {
if (prevActivityRef.current === 'busy' && activity.type === 'idle') {
playCelebrationSound();
void queryClient.invalidateQueries(trpc.unifiedSessions.list.pathFilter());
void queryClient.invalidateQueries(trpc.cliSessionsV2.list.pathFilter());
}
prevActivityRef.current = activity.type;
}, [activity.type, playCelebrationSound, queryClient, trpc]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay
const trpc = useTRPC();

const { data: recentReposData } = useQuery({
...trpc.unifiedSessions.recentRepositories.queryOptions({
...trpc.cliSessionsV2.recentRepositories.queryOptions({
organizationId,
updatedSince: repoUpdatedSince,
}),
Expand Down Expand Up @@ -135,7 +135,7 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay
toast.error('Failed to delete session');
}

void queryClient.invalidateQueries(trpc.unifiedSessions.list.pathFilter());
void queryClient.invalidateQueries(trpc.cliSessionsV2.list.pathFilter());
refetchSessions();
},
[
Expand All @@ -155,8 +155,8 @@ export function CloudSidebarLayout({ organizationId, children }: CloudSidebarLay
async (sessionId: string, title: string) => {
await renameCliSessionV2({ session_id: sessionId, title });
renameSessionLocally(sessionId, title);
void queryClient.invalidateQueries(trpc.unifiedSessions.list.pathFilter());
void queryClient.invalidateQueries(trpc.unifiedSessions.search.pathFilter());
void queryClient.invalidateQueries(trpc.cliSessionsV2.list.pathFilter());
void queryClient.invalidateQueries(trpc.cliSessionsV2.search.pathFilter());
refetchSessions();
},
[renameCliSessionV2, renameSessionLocally, queryClient, trpc, refetchSessions]
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/cloud-agent-next/NewSessionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ export function NewSessionPanel({ organizationId }: NewSessionPanelProps) {

const repoUpdatedSince = useMemo(() => startOfDay(subDays(new Date(), 5)).toISOString(), []);
const { data: recentRepoData } = useQuery(
trpc.unifiedSessions.recentRepositories.queryOptions({
trpc.cliSessionsV2.recentRepositories.queryOptions({
organizationId: organizationId ?? null,
updatedSince: repoUpdatedSince,
})
Expand Down Expand Up @@ -702,7 +702,7 @@ export function NewSessionPanel({ organizationId }: NewSessionPanelProps) {
}

void queryClient.invalidateQueries({
queryKey: trpc.unifiedSessions.list.queryKey({
queryKey: trpc.cliSessionsV2.list.queryKey({
limit: 3,
createdOnPlatform: 'cloud-agent',
orderBy: 'updated_at',
Expand Down Expand Up @@ -734,7 +734,7 @@ export function NewSessionPanel({ organizationId }: NewSessionPanelProps) {
selectedPlatform,
selectedRepo,
selectedProfile,
trpc.unifiedSessions.list,
trpc.cliSessionsV2.list,
trpcClient,
variant,
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Hook for managing sidebar session list
*
* Fetches sessions from the unified sessions router and maintains them in Jotai atoms
* Fetches sessions from the CLI sessions v2 router and maintains them in Jotai atoms
* for reactive updates across the UI. Supports search and platform filtering.
*/

Expand Down Expand Up @@ -43,13 +43,13 @@ function dbSessionToStoredSession(session: DbSession | DbSessionV2): StoredSessi
mode: v1?.last_mode ?? 'code',
model: v1?.last_model ?? '',
status: session.cloud_agent_session_id ? 'active' : 'completed',
createdAt: session.created_at.toISOString(),
updatedAt: session.updated_at.toISOString(),
createdAt: session.created_at,
updatedAt: session.updated_at,
messages: [],
cloudAgentSessionId: session.cloud_agent_session_id,
createdOnPlatform: v1?.created_on_platform ?? null,
sessionStatus: session.status,
sessionStatusUpdatedAt: session.status_updated_at?.toISOString() ?? null,
sessionStatusUpdatedAt: session.status_updated_at ?? null,
};
}

Expand Down Expand Up @@ -86,10 +86,10 @@ export function useSidebarSessions(options?: UseSidebarSessionsOptions): UseSide
createdOnPlatform,
gitUrl,
};
const listQueryKey = trpc.unifiedSessions.list.queryKey(listInput);
const listQueryKey = trpc.cliSessionsV2.list.queryKey(listInput);

const { data: listData, isLoading: isListLoading } = useQuery({
...trpc.unifiedSessions.list.queryOptions(listInput),
...trpc.cliSessionsV2.list.queryOptions(listInput),
staleTime: 5000,
enabled: !isSearchActive,
});
Expand All @@ -98,7 +98,7 @@ export function useSidebarSessions(options?: UseSidebarSessionsOptions): UseSide
const searchInput = { search_string: searchQuery, createdOnPlatform, organizationId, gitUrl };

const { data: searchData, isLoading: isSearchLoading } = useQuery({
...trpc.unifiedSessions.search.queryOptions(searchInput),
...trpc.cliSessionsV2.search.queryOptions(searchInput),
staleTime: 5000,
enabled: isSearchActive,
});
Expand Down Expand Up @@ -135,8 +135,8 @@ export function useSidebarSessions(options?: UseSidebarSessionsOptions): UseSide
repository: extractRepoDisplay(row.git_url),
branch: row.git_branch,
prompt: row.title || `Session ${row.session_id.substring(0, 8)}`,
mode: row.last_mode ?? 'code',
model: row.last_model ?? '',
mode: 'code',
model: '',
status: row.cloud_agent_session_id ? ('active' as const) : ('completed' as const),
createdAt: row.created_at,
updatedAt: row.updated_at,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ describe('getSessionDisplayTitle', () => {
git_branch: null,
cloud_agent_session_id: null,
created_on_platform: 'unknown',
created_at: new Date(),
updated_at: new Date(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
last_mode: null,
last_model: null,
version: 0,
Expand Down
35 changes: 17 additions & 18 deletions apps/web/src/components/cloud-agent-next/store/db-session-atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ function getSessionStore(): MiniDb<IndexedDbSessionData> {
// ============================================================================

/**
* API session type - matches the shape returned by cli-sessions-router.list (V1)
* Dates are returned as strings from the tRPC API
* API session type - matches the shape returned by cliSessionsV2 router list procedure.
* The cli_sessions_v2 schema uses `mode: 'string'` for timestamps, so dates are strings.
*/
type ApiSession = {
session_id: string;
Expand All @@ -160,16 +160,15 @@ type ApiSession = {
created_on_platform: string;
created_at: string;
updated_at: string;
last_mode: string | null;
last_model: string | null;
version: number;
organization_id: string | null;
status: string | null;
status_updated_at: string | null;
parent_session_id: string | null;
};

/**
* Database session type - with Date objects for convenient manipulation
* Database session type - timestamps are ISO strings (Drizzle `mode: 'string'`).
*/
export type DbSession = {
session_id: string;
Expand All @@ -178,40 +177,40 @@ export type DbSession = {
git_branch: string | null;
cloud_agent_session_id: string | null;
created_on_platform: string;
created_at: Date;
updated_at: Date;
created_at: string;
updated_at: string;
last_mode: string | null;
last_model: string | null;
version: number;
organization_id: string | null;
status: string | null;
status_updated_at: Date | null;
status_updated_at: string | null;
};

/**
* Database session type for V2 - with Date objects
* Database session type for V2 - timestamps are ISO strings.
* V2 sessions don't have git_url, organization_id, or mode/model fields
*/
export type DbSessionV2 = {
session_id: string;
title: string | null;
cloud_agent_session_id: string | null;
created_at: Date;
updated_at: Date;
created_at: string;
updated_at: string;
version: number;
status: string | null;
status_updated_at: Date | null;
status_updated_at: string | null;
};

/**
* Convert an API session (with string dates) to DbSession format (with Date objects)
* Convert an API session from the v2 router to DbSession format.
* Dates are already Date objects; last_mode/last_model are not present in v2.
*/
export function apiSessionToDbSession(apiSession: ApiSession): DbSession {
return {
...apiSession,
created_at: new Date(apiSession.created_at),
updated_at: new Date(apiSession.updated_at),
status_updated_at: apiSession.status_updated_at ? new Date(apiSession.status_updated_at) : null,
last_mode: null,
last_model: null,
};
}

Expand All @@ -226,8 +225,8 @@ export type DbSessionDetails = {
title: string | null;
cloud_agent_session_id: string | null;
organization_id: string | null;
created_at: Date;
updated_at: Date;
created_at: Date | string;
updated_at: Date | string;
// V1-only fields (optional for V2 compatibility)
kilo_user_id?: string;
git_url?: string | null;
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/cloud-agent/CloudSessionsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,11 @@ export function CloudSessionsPage({ organizationId }: CloudSessionsPageProps) {
}
}

// Invalidate the sessions list cache so the sidebar shows the new session
// Invalidate the sessions list cache so the sidebar shows the new session.
// This legacy page goes through cloudAgent.prepareSession which writes to
// cli_sessions (v1), so the sidebar/list data it produces still comes from
// the unified router (which UNIONs v1 and v2). Invalidating cliSessionsV2.list
// would miss the newly-created v1 row.
void queryClient.invalidateQueries({
queryKey: trpc.unifiedSessions.list.queryKey({
limit: 3,
Expand Down
Loading