From 683b8f940fdfd07314f476c7d967d5b904df6a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:25:56 +0100 Subject: [PATCH 01/71] feat(kilo-gateway): add fetchCloudSessions API function --- packages/kilo-gateway/src/api/sessions.ts | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/kilo-gateway/src/api/sessions.ts diff --git a/packages/kilo-gateway/src/api/sessions.ts b/packages/kilo-gateway/src/api/sessions.ts new file mode 100644 index 00000000000..98edc16ff9a --- /dev/null +++ b/packages/kilo-gateway/src/api/sessions.ts @@ -0,0 +1,53 @@ +import { KILO_API_BASE } from "./constants.js" +import { buildKiloHeaders } from "../headers.js" + +export interface CloudSession { + session_id: string + title: string | null + cloud_agent_session_id: string | null + created_at: string + updated_at: string + version: number +} + +export interface CloudSessionsResponse { + cliSessions: CloudSession[] + nextCursor: string | null +} + +export async function fetchCloudSessions( + token: string, + params?: { + cursor?: string + limit?: number + gitUrl?: string + }, +): Promise { + const input: Record = { + orderBy: "created_at", + includeChildren: false, + } + if (params?.cursor !== undefined) input.cursor = params.cursor + if (params?.limit !== undefined) input.limit = params.limit + if (params?.gitUrl !== undefined) input.gitUrl = params.gitUrl + + const url = `${KILO_API_BASE}/api/trpc/cliSessionsV2.list?input=${encodeURIComponent(JSON.stringify({ json: input }))}` + + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, + }) + + if (!response.ok) { + const text = await response.text() + throw new Error(`Failed to fetch cloud sessions: ${response.status} ${text}`) + } + + const data = (await response.json()) as { + result: { data: { json: CloudSessionsResponse } } + } + + return data.result.data.json +} From 051e546975a7db197f1d3cf9e9a23e879b373c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:26:01 +0100 Subject: [PATCH 02/71] feat(kilo-gateway): add /cloud-sessions proxy route --- packages/kilo-gateway/src/server/routes.ts | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 7393122c71b..2b92e184943 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -8,6 +8,7 @@ import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" +import { fetchCloudSessions, type CloudSession } from "../api/sessions.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE import { buildKiloHeaders } from "../headers.js" // kilocode_change @@ -281,4 +282,53 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { return c.json(notifications) }, ) + .get( + "/cloud-sessions", + describeRoute({ + summary: "Get cloud sessions", + description: "Fetch cloud CLI sessions from Kilo API", + operationId: "kilo.cloudSessions", + responses: { + 200: { + description: "Cloud sessions list", + content: { + "application/json": { + schema: resolver( + z.object({ + cliSessions: z.array( + z.object({ + session_id: z.string(), + title: z.string().nullable(), + cloud_agent_session_id: z.string().nullable(), + created_at: z.string(), + updated_at: z.string(), + version: z.number(), + }), + ), + nextCursor: z.string().nullable(), + }), + ), + }, + }, + }, + ...errors(400, 401), + }, + }), + async (c: any) => { + const auth = await Auth.get("kilo") + if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) + + const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined + if (!token) return c.json({ error: "No valid token found" }, 401) + + const query = c.req.query() + const params: Record = {} + if (query.cursor !== undefined) params.cursor = query.cursor + if (query.limit !== undefined) params.limit = Number(query.limit) + if (query.gitUrl !== undefined) params.gitUrl = query.gitUrl + + const result = await fetchCloudSessions(token, params as { cursor?: string; limit?: number; gitUrl?: string }) + return c.json(result) + }, + ) } From d3a584d98d70905d52ffa6cd7e13a8189deabb1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:28:23 +0100 Subject: [PATCH 03/71] feat(kilo-vscode): add CloudSessionInfo and CloudSessionsResponse types --- .../kilo-vscode/src/services/cli-backend/types.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/kilo-vscode/src/services/cli-backend/types.ts b/packages/kilo-vscode/src/services/cli-backend/types.ts index bf143c7144b..d23e2dca0bd 100644 --- a/packages/kilo-vscode/src/services/cli-backend/types.ts +++ b/packages/kilo-vscode/src/services/cli-backend/types.ts @@ -336,3 +336,18 @@ export interface Config { layout?: "auto" | "stretch" experimental?: ExperimentalConfig } + +// Cloud session from the Kilo cloud API (cli_sessions_v2) +export interface CloudSessionInfo { + session_id: string + title: string | null + cloud_agent_session_id: string | null + created_at: string + updated_at: string + version: number +} + +export interface CloudSessionsResponse { + cliSessions: CloudSessionInfo[] + nextCursor: string | null +} From fa516d5bad74d5db58b0537a3eaf4267dbf3defa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:28:23 +0100 Subject: [PATCH 04/71] feat(kilo-vscode): add getCloudSessions method to HttpClient --- .../src/services/cli-backend/http-client.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/kilo-vscode/src/services/cli-backend/http-client.ts b/packages/kilo-vscode/src/services/cli-backend/http-client.ts index 8892525c4bc..fc75b9a2170 100644 --- a/packages/kilo-vscode/src/services/cli-backend/http-client.ts +++ b/packages/kilo-vscode/src/services/cli-backend/http-client.ts @@ -12,6 +12,7 @@ import type { McpConfig, Config, KilocodeNotification, + CloudSessionsResponse, } from "./types" import { extractHttpErrorMessage, parseSSEDataLine } from "./http-utils" @@ -340,6 +341,28 @@ export class HttpClient { } } + /** + * Fetch cloud CLI sessions from the Kilo cloud API. + * Returns null if not logged in or if the request fails. + */ + async getCloudSessions(params?: { + cursor?: string + limit?: number + gitUrl?: string + }): Promise { + try { + const query = new URLSearchParams() + if (params?.cursor) query.set("cursor", params.cursor) + if (params?.limit) query.set("limit", String(params.limit)) + if (params?.gitUrl) query.set("gitUrl", params.gitUrl) + const qs = query.toString() + return await this.request("GET", `/kilo/cloud-sessions${qs ? `?${qs}` : ""}`) + } catch (err) { + console.warn("[Kilo] Failed to fetch cloud sessions:", err) + return null + } + } + /** * Switch the active organization. * Pass null to switch back to personal account. From 6c449ea00da1e8dad3e44f1f64c563d1c4b6af40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:28:24 +0100 Subject: [PATCH 05/71] feat(kilo-vscode): register cloudHistoryButtonClicked command --- packages/kilo-vscode/src/extension.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 904de6fe23c..9d2534c1c40 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -61,6 +61,9 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("kilo-code.new.historyButtonClicked", () => { provider.postMessage({ type: "action", action: "historyButtonClicked" }) }), + vscode.commands.registerCommand("kilo-code.new.cloudHistoryButtonClicked", () => { + provider.postMessage({ type: "action", action: "cloudHistoryButtonClicked" }) + }), vscode.commands.registerCommand("kilo-code.new.profileButtonClicked", () => { provider.postMessage({ type: "action", action: "profileButtonClicked" }) }), From 815cfbd4c8cb9ec700e1bd5711a19f68e63f8c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:28:25 +0100 Subject: [PATCH 06/71] feat(kilo-vscode): add Cloud History toolbar button to sidebar --- packages/kilo-vscode/package.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index c9a1decdd2e..170b491a3b8 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -59,6 +59,11 @@ "title": "History", "icon": "$(history)" }, + { + "command": "kilo-code.new.cloudHistoryButtonClicked", + "title": "Cloud History", + "icon": "$(cloud)" + }, { "command": "kilo-code.new.profileButtonClicked", "title": "Profile", @@ -217,14 +222,19 @@ "when": "view == kilo-code.new.sidebarView" }, { - "command": "kilo-code.new.profileButtonClicked", + "command": "kilo-code.new.cloudHistoryButtonClicked", "group": "navigation@4", "when": "view == kilo-code.new.sidebarView" }, { - "command": "kilo-code.new.settingsButtonClicked", + "command": "kilo-code.new.profileButtonClicked", "group": "navigation@5", "when": "view == kilo-code.new.sidebarView" + }, + { + "command": "kilo-code.new.settingsButtonClicked", + "group": "navigation@6", + "when": "view == kilo-code.new.sidebarView" } ], "scm/title": [ From dd360840d436cf11aa7c983fe20539561f4d5dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:28:26 +0100 Subject: [PATCH 07/71] feat(kilo-vscode): handle requestCloudSessions and requestGitRemoteUrl messages --- packages/kilo-vscode/src/KiloProvider.ts | 66 ++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 5c882e7f0a9..6d7d8888c8d 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -432,6 +432,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "requestNotifications": await this.fetchAndSendNotifications() break + case "requestCloudSessions": + await this.handleRequestCloudSessions(message) + break + case "requestGitRemoteUrl": + void this.getGitRemoteUrl().then((url) => { + this.postMessage({ type: "gitRemoteUrlLoaded", gitUrl: url ?? null }) + }) + break case "dismissNotification": await this.handleDismissNotification(message.notificationId) break @@ -952,6 +960,44 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + /** + * Handle cloud sessions request from webview. + * Fetches sessions from the Kilo cloud API and sends them back. + */ + private async handleRequestCloudSessions(message: { + cursor?: string + limit?: number + gitUrl?: string + }): Promise { + if (!this.httpClient) { + this.postMessage({ + type: "error", + message: "Not connected to CLI backend", + }) + return + } + + try { + const result = await this.httpClient.getCloudSessions({ + cursor: message.cursor, + limit: message.limit, + gitUrl: message.gitUrl, + }) + + this.postMessage({ + type: "cloudSessionsLoaded", + sessions: result?.cliSessions ?? [], + nextCursor: result?.nextCursor ?? null, + }) + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to fetch cloud sessions:", error) + this.postMessage({ + type: "error", + message: error instanceof Error ? error.message : "Failed to fetch cloud sessions", + }) + } + } + /** * Persist a dismissed notification ID in globalState and push updated lists to webview. */ @@ -1506,6 +1552,26 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper }) } + /** + * Get the git remote URL for the current workspace using VS Code's built-in Git API. + * Returns undefined if not in a git repo or no remotes are configured. + */ + private async getGitRemoteUrl(): Promise { + try { + const extension = vscode.extensions.getExtension("vscode.git") + if (!extension) return undefined + const api = extension.isActive ? extension.exports?.getAPI(1) : (await extension.activate())?.getAPI(1) + if (!api) return undefined + const repo = api.repositories?.[0] + if (!repo) return undefined + const remote = repo.state?.remotes?.find((r: { name: string }) => r.name === "origin") + return remote?.fetchUrl ?? remote?.pushUrl + } catch (error) { + console.warn("[Kilo New] KiloProvider: Failed to get git remote URL:", error) + return undefined + } + } + /** * Get the workspace directory for a session. * Checks session directory overrides first (e.g., worktree paths), then falls back to workspace root. From 42fdd4ec4f2bcc3091e3cdd0c7136481981c6c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:32:23 +0100 Subject: [PATCH 08/71] feat(kilo-vscode): add cloud session and git remote URL message types --- .../webview-ui/src/types/messages.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index d6e9b9bd417..31e7e98f5f6 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -104,6 +104,14 @@ export interface SessionInfo { updatedAt: string } +// Cloud session info (from Kilo cloud API) +export interface CloudSessionInfo { + session_id: string + title: string | null + created_at: string + updated_at: string +} + // Permission request export interface PermissionRequest { id: string @@ -399,6 +407,17 @@ export interface SessionsLoadedMessage { sessions: SessionInfo[] } +export interface CloudSessionsLoadedMessage { + type: "cloudSessionsLoaded" + sessions: CloudSessionInfo[] + nextCursor: string | null +} + +export interface GitRemoteUrlLoadedMessage { + type: "gitRemoteUrlLoaded" + gitUrl: string | null +} + export interface ActionMessage { type: "action" action: string @@ -441,7 +460,7 @@ export interface DeviceAuthCancelledMessage { export interface NavigateMessage { type: "navigate" - view: "newTask" | "marketplace" | "history" | "profile" | "settings" + view: "newTask" | "marketplace" | "history" | "cloudHistory" | "profile" | "settings" } export interface ProvidersLoadedMessage { @@ -643,6 +662,8 @@ export type ExtensionMessage = | MessagesLoadedMessage | MessageCreatedMessage | SessionsLoadedMessage + | CloudSessionsLoadedMessage + | GitRemoteUrlLoadedMessage | ActionMessage | ProfileDataMessage | DeviceAuthStartedMessage @@ -724,6 +745,17 @@ export interface LoadSessionsRequest { type: "loadSessions" } +export interface RequestCloudSessionsMessage { + type: "requestCloudSessions" + cursor?: string + limit?: number + gitUrl?: string +} + +export interface RequestGitRemoteUrlMessage { + type: "requestGitRemoteUrl" +} + export interface LoginRequest { type: "login" } @@ -979,6 +1011,8 @@ export type WebviewMessage = | ClearSessionRequest | LoadMessagesRequest | LoadSessionsRequest + | RequestCloudSessionsMessage + | RequestGitRemoteUrlMessage | LoginRequest | LogoutRequest | RefreshProfileRequest From ecf719940b698f099dcd78c2a8632ee44668c395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:32:24 +0100 Subject: [PATCH 09/71] feat(kilo-vscode): add CloudSessionList component for cloud history view --- .../components/history/CloudSessionList.tsx | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx diff --git a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx new file mode 100644 index 00000000000..a2e838ea30c --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx @@ -0,0 +1,157 @@ +/** + * CloudSessionList component + * Displays cloud sessions from the Kilo cloud API, grouped by date. + * Supports filtering by repository (git URL) and search by title. + */ + +import { Component, createSignal, createEffect, onMount, onCleanup, Show } from "solid-js" +import { List } from "@kilocode/kilo-ui/list" +import { Button } from "@kilocode/kilo-ui/button" +import { useVSCode } from "../../context/vscode" +import { useLanguage } from "../../context/language" +import { formatRelativeDate } from "../../utils/date" +import type { CloudSessionInfo, ExtensionMessage } from "../../types/messages" + +const DATE_GROUP_KEYS = ["time.today", "time.yesterday", "time.thisWeek", "time.thisMonth", "time.older"] as const + +function dateGroupKey(iso: string): (typeof DATE_GROUP_KEYS)[number] { + const now = new Date() + const then = new Date(iso) + + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) + const yesterday = new Date(today.getTime() - 86400000) + const weekAgo = new Date(today.getTime() - 7 * 86400000) + const monthAgo = new Date(today.getTime() - 30 * 86400000) + + if (then >= today) return DATE_GROUP_KEYS[0] + if (then >= yesterday) return DATE_GROUP_KEYS[1] + if (then >= weekAgo) return DATE_GROUP_KEYS[2] + if (then >= monthAgo) return DATE_GROUP_KEYS[3] + return DATE_GROUP_KEYS[4] +} + +interface DisplaySession { + id: string + title: string + updatedAt: string + createdAt: string +} + +function toDisplay(s: CloudSessionInfo): DisplaySession { + return { + id: s.session_id, + title: s.title ?? "Untitled", + updatedAt: s.updated_at, + createdAt: s.created_at, + } +} + +interface CloudSessionListProps { + onSelectSession?: (id: string) => void +} + +const CloudSessionList: Component = (props) => { + const vscode = useVSCode() + const language = useLanguage() + + const [sessions, setSessions] = createSignal([]) + const [loading, setLoading] = createSignal(false) + const [nextCursor, setNextCursor] = createSignal(null) + const [gitUrl, setGitUrl] = createSignal(null) + const [repoOnly, setRepoOnly] = createSignal(true) + const [initialized, setInitialized] = createSignal(false) + + const unsub = vscode.onMessage((message: ExtensionMessage) => { + if (message.type === "cloudSessionsLoaded") { + const incoming = message.sessions.map(toDisplay) + if (nextCursor() && incoming.length > 0) { + setSessions((prev) => { + const seen = new Set(prev.map((s) => s.id)) + return [...prev, ...incoming.filter((s) => !seen.has(s.id))] + }) + } else if (!nextCursor()) { + setSessions(incoming) + } + setNextCursor(message.nextCursor) + setLoading(false) + } + if (message.type === "gitRemoteUrlLoaded") { + setGitUrl(message.gitUrl) + setInitialized(true) + } + }) + + onCleanup(unsub) + + onMount(() => { + vscode.postMessage({ type: "requestGitRemoteUrl" }) + }) + + createEffect(() => { + if (!initialized()) return + const url = repoOnly() ? gitUrl() : undefined + setLoading(true) + setSessions([]) + setNextCursor(null) + vscode.postMessage({ + type: "requestCloudSessions", + gitUrl: url ?? undefined, + }) + }) + + function loadMore() { + const cursor = nextCursor() + if (!cursor || loading()) return + const url = repoOnly() ? gitUrl() : undefined + setLoading(true) + vscode.postMessage({ + type: "requestCloudSessions", + cursor, + gitUrl: url ?? undefined, + }) + } + + return ( +
+
+ +
+ + items={sessions()} + key={(s) => s.id} + filterKeys={["title"]} + onSelect={(s) => { + if (s) props.onSelectSession?.(s.id) + }} + search={{ placeholder: language.t("session.search.placeholder"), autofocus: false }} + emptyMessage={ + loading() ? (language.t("common.loading") ?? "Loading...") : (language.t("session.empty") ?? "No sessions") + } + groupBy={(s) => language.t(dateGroupKey(s.updatedAt))} + sortGroupsBy={(a, b) => { + const rank = Object.fromEntries(DATE_GROUP_KEYS.map((k, i) => [language.t(k), i])) + return (rank[a.category] ?? 99) - (rank[b.category] ?? 99) + }} + > + {(s) => ( + <> + {s.title} + {formatRelativeDate(s.updatedAt)} + + )} + + +
+ +
+
+
+ ) +} + +export default CloudSessionList From b5b966167bc83b5f1b32bd3e8ed3fac3c1ed411e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:32:25 +0100 Subject: [PATCH 10/71] feat(kilo-vscode): add CSS for cloud session list filters and layout --- .../webview-ui/src/styles/chat.css | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index adf9bca17c0..a800424337a 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -764,6 +764,40 @@ white-space: nowrap; } +/* Cloud Session List */ +.cloud-session-list { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; +} + +.cloud-session-filters { + flex-shrink: 0; + padding: 8px 12px; + border-bottom: 1px solid var(--vscode-panel-border); +} + +.cloud-session-checkbox { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--vscode-foreground); + cursor: pointer; +} + +.cloud-session-checkbox input[type="checkbox"] { + accent-color: var(--vscode-focusBorder); +} + +.cloud-session-load-more { + flex-shrink: 0; + display: flex; + justify-content: center; + padding: 8px; +} + /* ============================================ Kilo Notifications ============================================ */ From 88b8d7e2061e93ce72ee74ae6920841157f43af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:32:26 +0100 Subject: [PATCH 11/71] feat(kilo-vscode): wire cloudHistory view into App router --- packages/kilo-vscode/webview-ui/src/App.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index 1826be90736..319209fff01 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -19,12 +19,13 @@ import { LanguageProvider } from "./context/language" import { ChatView } from "./components/chat" import { KiloNotifications } from "./components/chat/KiloNotifications" import SessionList from "./components/history/SessionList" +import CloudSessionList from "./components/history/CloudSessionList" import { NotificationsProvider } from "./context/notifications" import type { Message as SDKMessage, Part as SDKPart } from "@kilocode/sdk/v2" import "./styles/chat.css" -type ViewType = "newTask" | "marketplace" | "history" | "profile" | "settings" -const VALID_VIEWS = new Set(["newTask", "marketplace", "history", "profile", "settings"]) +type ViewType = "newTask" | "marketplace" | "history" | "cloudHistory" | "profile" | "settings" +const VALID_VIEWS = new Set(["newTask", "marketplace", "history", "cloudHistory", "profile", "settings"]) const DummyView: Component<{ title: string }> = (props) => { return ( @@ -121,6 +122,9 @@ const AppContent: Component = () => { case "historyButtonClicked": setCurrentView("history") break + case "cloudHistoryButtonClicked": + setCurrentView("cloudHistory") + break case "profileButtonClicked": setCurrentView("profile") break @@ -166,6 +170,9 @@ const AppContent: Component = () => { + + + Date: Mon, 23 Feb 2026 16:45:40 +0100 Subject: [PATCH 12/71] fix(kilo-gateway): use tRPC batch format for cloud sessions API call --- packages/kilo-gateway/src/api/sessions.ts | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/kilo-gateway/src/api/sessions.ts b/packages/kilo-gateway/src/api/sessions.ts index 98edc16ff9a..c493d44b088 100644 --- a/packages/kilo-gateway/src/api/sessions.ts +++ b/packages/kilo-gateway/src/api/sessions.ts @@ -23,19 +23,20 @@ export async function fetchCloudSessions( gitUrl?: string }, ): Promise { - const input: Record = { - orderBy: "created_at", - includeChildren: false, - } + const input: Record = {} if (params?.cursor !== undefined) input.cursor = params.cursor if (params?.limit !== undefined) input.limit = params.limit if (params?.gitUrl !== undefined) input.gitUrl = params.gitUrl - const url = `${KILO_API_BASE}/api/trpc/cliSessionsV2.list?input=${encodeURIComponent(JSON.stringify({ json: input }))}` + const query = new URLSearchParams({ + batch: "1", + input: JSON.stringify({ "0": input }), + }) - const response = await fetch(url, { + const response = await fetch(`${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${query.toString()}`, { headers: { Authorization: `Bearer ${token}`, + "Content-Type": "application/json", ...buildKiloHeaders(), }, }) @@ -45,9 +46,20 @@ export async function fetchCloudSessions( throw new Error(`Failed to fetch cloud sessions: ${response.status} ${text}`) } - const data = (await response.json()) as { - result: { data: { json: CloudSessionsResponse } } - } + const json = (await response.json()) as unknown[] + const data = (json[0] as any)?.result?.data + const result = data?.json ?? data - return data.result.data.json + if (!result) return { cliSessions: [], nextCursor: null } + + const sessions = (result.cliSessions ?? []).map((s: any) => ({ + session_id: s.session_id, + title: s.title ?? null, + cloud_agent_session_id: s.cloud_agent_session_id ?? null, + created_at: typeof s.created_at === "string" ? s.created_at : new Date(s.created_at).toISOString(), + updated_at: typeof s.updated_at === "string" ? s.updated_at : new Date(s.updated_at).toISOString(), + version: s.version ?? 0, + })) + + return { cliSessions: sessions, nextCursor: result.nextCursor ?? null } } From 56216f05426ebedb7b2311122f621c32261e74d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:47:07 +0100 Subject: [PATCH 13/71] feat(kilo-vscode): move cloud history button before local history in toolbar --- packages/kilo-vscode/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 170b491a3b8..6a253461a7b 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -218,12 +218,12 @@ }, { "command": "kilo-code.new.historyButtonClicked", - "group": "navigation@3", + "group": "navigation@4", "when": "view == kilo-code.new.sidebarView" }, { "command": "kilo-code.new.cloudHistoryButtonClicked", - "group": "navigation@4", + "group": "navigation@3", "when": "view == kilo-code.new.sidebarView" }, { From 03997620e56c8511cf77f34dfedf6e69916d55cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:49:37 +0100 Subject: [PATCH 14/71] fix(kilo-gateway): inline tRPC call in cloud-sessions route to prevent empty responses --- packages/kilo-gateway/src/server/routes.ts | 49 ++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 2b92e184943..fb44e8c103b 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -8,7 +8,6 @@ import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" -import { fetchCloudSessions, type CloudSession } from "../api/sessions.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE import { buildKiloHeaders } from "../headers.js" // kilocode_change @@ -321,14 +320,48 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined if (!token) return c.json({ error: "No valid token found" }, 401) - const query = c.req.query() - const params: Record = {} - if (query.cursor !== undefined) params.cursor = query.cursor - if (query.limit !== undefined) params.limit = Number(query.limit) - if (query.gitUrl !== undefined) params.gitUrl = query.gitUrl + const cursor = c.req.query("cursor") + const limit = c.req.query("limit") + const gitUrl = c.req.query("gitUrl") - const result = await fetchCloudSessions(token, params as { cursor?: string; limit?: number; gitUrl?: string }) - return c.json(result) + const input: Record = {} + if (cursor) input.cursor = cursor + if (limit) input.limit = Number(limit) + if (gitUrl) input.gitUrl = gitUrl + + const params = new URLSearchParams({ + batch: "1", + input: JSON.stringify({ "0": input }), + }) + + const response = await fetch(`${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${params.toString()}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + ...buildKiloHeaders(), + }, + }) + + if (!response.ok) { + const text = await response.text() + return c.json({ error: `Cloud sessions fetch failed: ${response.status} ${text}` }, response.status as any) + } + + const json = await response.json() + const data = Array.isArray(json) ? json[0]?.result?.data : null + const result = data?.json ?? data + if (!result) return c.json({ cliSessions: [], nextCursor: null }) + + const sessions = (result.cliSessions ?? []).map((s: any) => ({ + session_id: s.session_id, + title: s.title ?? null, + cloud_agent_session_id: s.cloud_agent_session_id ?? null, + created_at: typeof s.created_at === "string" ? s.created_at : new Date(s.created_at).toISOString(), + updated_at: typeof s.updated_at === "string" ? s.updated_at : new Date(s.updated_at).toISOString(), + version: s.version ?? 0, + })) + + return c.json({ cliSessions: sessions, nextCursor: result.nextCursor ?? null }) }, ) } From 332a7eccb4e725140420c3483189f9ac83fed9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:56:02 +0100 Subject: [PATCH 15/71] debug(kilo-gateway): add logging to cloud-sessions route for empty response diagnosis --- packages/kilo-gateway/src/server/routes.ts | 94 ++++++++++++---------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index fb44e8c103b..f24552346c4 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -314,54 +314,66 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { }, }), async (c: any) => { - const auth = await Auth.get("kilo") - if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) + try { + const auth = await Auth.get("kilo") + if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) - const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined - if (!token) return c.json({ error: "No valid token found" }, 401) + const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined + if (!token) return c.json({ error: "No valid token found" }, 401) - const cursor = c.req.query("cursor") - const limit = c.req.query("limit") - const gitUrl = c.req.query("gitUrl") + const cursor = c.req.query("cursor") + const limit = c.req.query("limit") + const gitUrl = c.req.query("gitUrl") - const input: Record = {} - if (cursor) input.cursor = cursor - if (limit) input.limit = Number(limit) - if (gitUrl) input.gitUrl = gitUrl + const input: Record = {} + if (cursor) input.cursor = cursor + if (limit) input.limit = Number(limit) + if (gitUrl) input.gitUrl = gitUrl - const params = new URLSearchParams({ - batch: "1", - input: JSON.stringify({ "0": input }), - }) + const params = new URLSearchParams({ + batch: "1", + input: JSON.stringify({ "0": input }), + }) - const response = await fetch(`${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${params.toString()}`, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - ...buildKiloHeaders(), - }, - }) + const url = `${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${params.toString()}` - if (!response.ok) { - const text = await response.text() - return c.json({ error: `Cloud sessions fetch failed: ${response.status} ${text}` }, response.status as any) + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + ...buildKiloHeaders(), + }, + }) + + if (!response.ok) { + const text = await response.text() + console.error("[Kilo Gateway] cloud-sessions: tRPC request failed", { + status: response.status, + body: text.slice(0, 500), + }) + return c.json({ error: `Cloud sessions fetch failed: ${response.status} ${text}` }, response.status as any) + } + + const raw = await response.text() + const json = JSON.parse(raw) + const data = Array.isArray(json) ? json[0]?.result?.data : null + const result = data?.json ?? data + if (!result) return c.json({ cliSessions: [], nextCursor: null }) + + const sessions = (result.cliSessions ?? []).map((s: any) => ({ + session_id: s.session_id, + title: s.title ?? null, + cloud_agent_session_id: s.cloud_agent_session_id ?? null, + created_at: typeof s.created_at === "string" ? s.created_at : new Date(s.created_at).toISOString(), + updated_at: typeof s.updated_at === "string" ? s.updated_at : new Date(s.updated_at).toISOString(), + version: s.version ?? 0, + })) + + return c.json({ cliSessions: sessions, nextCursor: result.nextCursor ?? null }) + } catch (err: any) { + console.error("[Kilo Gateway] cloud-sessions: unhandled error", err?.message ?? err) + return c.json({ error: err?.message ?? "Unknown error" }, 500) } - - const json = await response.json() - const data = Array.isArray(json) ? json[0]?.result?.data : null - const result = data?.json ?? data - if (!result) return c.json({ cliSessions: [], nextCursor: null }) - - const sessions = (result.cliSessions ?? []).map((s: any) => ({ - session_id: s.session_id, - title: s.title ?? null, - cloud_agent_session_id: s.cloud_agent_session_id ?? null, - created_at: typeof s.created_at === "string" ? s.created_at : new Date(s.created_at).toISOString(), - updated_at: typeof s.updated_at === "string" ? s.updated_at : new Date(s.updated_at).toISOString(), - version: s.version ?? 0, - })) - - return c.json({ cliSessions: sessions, nextCursor: result.nextCursor ?? null }) }, ) } From 53118b0efb4f4ff20b29300b0da6340b7aadb2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 16:58:28 +0100 Subject: [PATCH 16/71] feat(kilo-vscode): fetch 50 cloud sessions per page --- .../webview-ui/src/components/history/CloudSessionList.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx index a2e838ea30c..f2e1a4030f8 100644 --- a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx @@ -95,6 +95,7 @@ const CloudSessionList: Component = (props) => { setNextCursor(null) vscode.postMessage({ type: "requestCloudSessions", + limit: 50, gitUrl: url ?? undefined, }) }) @@ -107,6 +108,7 @@ const CloudSessionList: Component = (props) => { vscode.postMessage({ type: "requestCloudSessions", cursor, + limit: 50, gitUrl: url ?? undefined, }) } From cb901ce9af66d20642f42c4991fef40b12c025ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:01:32 +0100 Subject: [PATCH 17/71] feat(kilo-vscode): add i18n translations for session.cloud.repoOnly --- packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/br.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/da.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/de.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/en.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/es.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/no.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/th.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 1 + 16 files changed, 16 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 366f650c488..ff683753fb4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -688,6 +688,7 @@ export const dict = { "session.recent": "الأخيرة", "session.search.placeholder": "البحث في الجلسات...", "session.empty": "لا توجد جلسات بعد. انقر + لبدء محادثة جديدة.", + "session.cloud.repoOnly": "هذا المستودع فقط", "workspace.new": "مساحة عمل جديدة", "workspace.type.local": "محلي", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index ae0efb2a491..1664a39f5ee 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -695,6 +695,7 @@ export const dict = { "session.recent": "Recentes", "session.search.placeholder": "Buscar sessões...", "session.empty": "Nenhuma sessão ainda. Clique + para iniciar uma nova conversa.", + "session.cloud.repoOnly": "Apenas este repositório", "workspace.new": "Novo espaço de trabalho", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index d8e23c8fe7c..34677b37935 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -722,6 +722,7 @@ export const dict = { "session.recent": "Nedavne", "session.search.placeholder": "Pretraži sesije...", "session.empty": "Još nema sesija. Kliknite + za početak novog razgovora.", + "session.cloud.repoOnly": "Samo ovaj repozitorij", "workspace.new": "Novi radni prostor", "workspace.type.local": "lokalno", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 7197fc4b92d..ed9dd484826 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -694,6 +694,7 @@ export const dict = { "session.recent": "Seneste", "session.search.placeholder": "Søg sessioner...", "session.empty": "Ingen sessioner endnu. Klik + for at starte en ny samtale.", + "session.cloud.repoOnly": "Kun dette repository", "workspace.new": "Nyt arbejdsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index a95b37fb170..6244c5f4111 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -700,6 +700,7 @@ export const dict = { "session.recent": "Kürzlich", "session.search.placeholder": "Sitzungen suchen...", "session.empty": "Noch keine Sitzungen. Klicke + um eine neue Unterhaltung zu starten.", + "session.cloud.repoOnly": "Nur dieses Repository", "workspace.new": "Neuer Arbeitsbereich", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 69c1f5dc3a5..4ff2a1e44c4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -727,6 +727,7 @@ export const dict = { "session.recent": "Recent", "session.search.placeholder": "Search sessions...", "session.empty": "No sessions yet. Click + to start a new conversation.", + "session.cloud.repoOnly": "Only this repository", "workspace.new": "New workspace", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index f75c51a052e..08c7656dc06 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -700,6 +700,7 @@ export const dict = { "session.recent": "Recientes", "session.search.placeholder": "Buscar sesiones...", "session.empty": "Aún no hay sesiones. Haz clic en + para iniciar una nueva conversación.", + "session.cloud.repoOnly": "Solo este repositorio", "workspace.new": "Nuevo espacio de trabajo", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 87cfc0ced58..9e72b4b178d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -704,6 +704,7 @@ export const dict = { "session.recent": "Récentes", "session.search.placeholder": "Rechercher des sessions...", "session.empty": "Aucune session pour l'instant. Cliquez + pour démarrer une nouvelle conversation.", + "session.cloud.repoOnly": "Uniquement ce dépôt", "workspace.new": "Nouvel espace de travail", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 3ce9b5ade14..eeb431616b0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -688,6 +688,7 @@ export const dict = { "session.recent": "最近", "session.search.placeholder": "セッションを検索...", "session.empty": "セッションがありません。+ をクリックして新しい会話を始めましょう。", + "session.cloud.repoOnly": "このリポジトリのみ", "workspace.new": "新しいワークスペース", "workspace.type.local": "ローカル", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 8b9fc944e41..68806dfab65 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -693,6 +693,7 @@ export const dict = { "session.recent": "최근", "session.search.placeholder": "세션 검색...", "session.empty": "아직 세션이 없습니다. +를 클릭하여 새 대화를 시작하세요.", + "session.cloud.repoOnly": "이 저장소만", "workspace.new": "새 작업 공간", "workspace.type.local": "로컬", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index d7787ef56ed..88bf767af9c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -697,6 +697,7 @@ export const dict = { "session.recent": "Nylige", "session.search.placeholder": "Søk i sesjoner...", "session.empty": "Ingen sesjoner ennå. Klikk + for å starte en ny samtale.", + "session.cloud.repoOnly": "Kun dette repositoriet", "workspace.new": "Nytt arbeidsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index a80af75d7f6..cf8ea38f7f7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -696,6 +696,7 @@ export const dict = { "session.recent": "Ostatnie", "session.search.placeholder": "Szukaj sesji...", "session.empty": "Brak sesji. Kliknij + aby rozpocząć nową rozmowę.", + "session.cloud.repoOnly": "Tylko to repozytorium", "workspace.new": "Nowa przestrzeń robocza", "workspace.type.local": "lokalna", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 6dd808c9022..e915f694445 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -700,6 +700,7 @@ export const dict = { "session.recent": "Недавние", "session.search.placeholder": "Поиск сессий...", "session.empty": "Сессий пока нет. Нажмите + чтобы начать новый разговор.", + "session.cloud.repoOnly": "Только этот репозиторий", "workspace.new": "Новое рабочее пространство", "workspace.type.local": "локальное", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 213e119fdad..57729a2e0b1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -686,6 +686,7 @@ export const dict = { "session.recent": "ล่าสุด", "session.search.placeholder": "ค้นหาเซสชัน...", "session.empty": "ยังไม่มีเซสชัน คลิก + เพื่อเริ่มการสนทนาใหม่", + "session.cloud.repoOnly": "เฉพาะรีโพซิทอรีนี้", "workspace.new": "พื้นที่ทำงานใหม่", "workspace.type.local": "ในเครื่อง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index ddae50509da..e8f7d086055 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -689,6 +689,7 @@ export const dict = { "session.recent": "最近", "session.search.placeholder": "搜索会话...", "session.empty": "暂无会话。点击 + 开始新的对话。", + "session.cloud.repoOnly": "仅此仓库", "workspace.new": "新建工作区", "workspace.type.local": "本地", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index f9d74f66d10..68fe26f228c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -686,6 +686,7 @@ export const dict = { "session.recent": "最近", "session.search.placeholder": "搜尋工作階段...", "session.empty": "尚無工作階段。點擊 + 開始新的對話。", + "session.cloud.repoOnly": "僅此儲存庫", "workspace.new": "新增工作區", "workspace.type.local": "本地", From 9a7db625dea38446425e02a1f71cca085bc4e8bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:24:42 +0100 Subject: [PATCH 18/71] feat(kilo-gateway): add GET /kilo/cloud/session/:id endpoint for preview Proxies the cloud ingest export API to return full session data (messages + parts) for read-only preview in the VS Code extension. Uses KILO_SESSION_INGEST_URL env var with fallback to ingest.kilosessions.ai. --- packages/kilo-gateway/src/server/routes.ts | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index f24552346c4..8cee8f3cd40 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -281,6 +281,47 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { return c.json(notifications) }, ) + .get( + "/cloud/session/:id", + describeRoute({ + summary: "Get cloud session", + description: "Fetch full session data from the Kilo cloud for preview", + operationId: "kilo.cloud.session.get", + responses: { + 200: { + description: "Cloud session data", + content: { + "application/json": { + schema: resolver(z.unknown()), + }, + }, + }, + ...errors(401, 404), + }, + }), + validator("param", z.object({ id: z.string() })), + async (c: any) => { + const auth = await Auth.get("kilo") + if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) + const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined + if (!token) return c.json({ error: "No valid token found" }, 401) + + const { id } = c.req.valid("param") + const base = process.env.KILO_SESSION_INGEST_URL || "https://ingest.kilosessions.ai" + const response = await fetch(`${base}/api/session/${id}/export`, { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, + }) + + if (response.status === 404) return c.json({ error: "Session not found" }, 404) + if (!response.ok) return c.json({ error: "Failed to fetch session" }, response.status) + + const data = await response.json() + return c.json(data) + }, + ) .get( "/cloud-sessions", describeRoute({ From baefc0d536171277b802563b234eeec1885ebddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:26:53 +0100 Subject: [PATCH 19/71] feat(kilo-gateway): add POST /kilo/cloud/session/import endpoint Imports a cloud session into local storage with fresh IDs (ses_, msg_, prt_) to avoid collisions with the cloud session during sync. Passes Storage and Instance deps from server.ts to the gateway routes. --- packages/kilo-gateway/src/server/routes.ts | 96 +++++++++++++++++++++- packages/opencode/src/server/server.ts | 3 + 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 8cee8f3cd40..c1a87a5b882 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -6,6 +6,7 @@ * This factory function accepts OpenCode dependencies to create Kilo-specific routes */ +import { randomBytes } from "crypto" import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE @@ -28,6 +29,8 @@ interface KiloRoutesDeps { errors: Errors Auth: Auth z: Z + Storage: any + Instance: any } /** @@ -53,8 +56,20 @@ interface KiloRoutesDeps { * }) * ``` */ +function generateId(prefix: string, descending: boolean): string { + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const bytes = randomBytes(14) + let rand = "" + for (let i = 0; i < 14; i++) rand += chars[bytes[i] % 62] + let now = BigInt(Date.now()) * BigInt(0x1000) + BigInt(1) + if (descending) now = ~now + const buf = Buffer.alloc(6) + for (let i = 0; i < 6; i++) buf[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) + return prefix + "_" + buf.toString("hex") + rand +} + export function createKiloRoutes(deps: KiloRoutesDeps) { - const { Hono, describeRoute, validator, resolver, errors, Auth, z } = deps + const { Hono, describeRoute, validator, resolver, errors, Auth, z, Storage, Instance } = deps const Organization = z.object({ id: z.string(), @@ -322,6 +337,85 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { return c.json(data) }, ) + .post( + "/cloud/session/import", + describeRoute({ + summary: "Import session from cloud", + description: "Download a cloud-synced session and write it to local storage with fresh IDs.", + operationId: "kilo.cloud.session.import", + responses: { + 200: { + description: "Imported session info", + content: { + "application/json": { + schema: resolver(z.unknown()), + }, + }, + }, + ...errors(400, 401, 404), + }, + }), + validator( + "json", + z.object({ + sessionId: z.string(), + }), + ), + async (c: any) => { + const { sessionId } = c.req.valid("json") + + const auth = await Auth.get("kilo") + if (!auth) return c.json({ error: "Not authenticated with Kilo" }, 401) + const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined + if (!token) return c.json({ error: "No valid token found" }, 401) + + const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" + const response = await fetch(`${base}/api/session/${sessionId}/export`, { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, + }) + + if (response.status === 404) return c.json({ error: "Session not found in cloud" }, 404) + if (!response.ok) { + const text = await response.text() + console.error("[Kilo Gateway] cloud/session/import: export failed", { + status: response.status, + body: text.slice(0, 500), + }) + return c.json({ error: `Import failed: ${response.status}` }, response.status as any) + } + + const data = (await response.json()) as any + if (!data?.info?.id) return c.json({ error: "Invalid export data" }, 400) + + const localSessionID = generateId("ses", true) + const msgMap = new Map() + const projectID = Instance.project.id + + data.info.id = localSessionID + data.info.projectID = projectID + await Storage.write(["session", projectID, localSessionID], data.info) + + for (const msg of data.messages ?? []) { + const msgID = generateId("msg", false) + msgMap.set(msg.info.id, msgID) + msg.info.id = msgID + msg.info.sessionID = localSessionID + if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID + await Storage.write(["message", localSessionID, msgID], msg.info) + for (const part of msg.parts ?? []) { + part.id = generateId("prt", false) + part.messageID = msgID + part.sessionID = localSessionID + await Storage.write(["part", msgID, part.id], part) + } + } + + return c.json(data.info) + }, + ) .get( "/cloud-sessions", describeRoute({ diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index be42471a1f1..2145d1f572c 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -31,6 +31,7 @@ import { ExperimentalRoutes } from "./routes/experimental" import { TelemetryRoutes } from "./routes/telemetry" // kilocode_change import { ProviderRoutes } from "./routes/provider" import { createKiloRoutes } from "@kilocode/kilo-gateway" // kilocode_change +import { Storage } from "../storage/storage" // kilocode_change import { lazy } from "../util/lazy" import { InstanceBootstrap } from "../project/bootstrap" import { NotFoundError } from "../storage/db" @@ -248,6 +249,8 @@ export namespace Server { errors, Auth, z, + Storage, // kilocode_change + Instance, // kilocode_change }), ) // kilocode_change end From 2e7a307076e90b2783beb438332fcc6c6be888a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:27:33 +0100 Subject: [PATCH 20/71] chore(sdk): regenerate SDK for cloud session endpoints Adds types and client methods for: - GET /kilo/cloud/session/:id (preview) - POST /kilo/cloud/session/import --- packages/sdk/js/src/v2/gen/sdk.gen.ts | 108 ++++++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 95 ++++++++++ packages/sdk/openapi.json | 222 ++++++++++++++++++++++++ 3 files changed, 425 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 0609acc42a5..2c7cd44836d 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -43,6 +43,12 @@ import type { GlobalEventResponses, GlobalHealthResponses, InstanceDisposeResponses, + KiloCloudSessionGetErrors, + KiloCloudSessionGetResponses, + KiloCloudSessionImportErrors, + KiloCloudSessionImportResponses, + KiloCloudSessionsErrors, + KiloCloudSessionsResponses, KiloFimErrors, KiloFimResponses, KiloNotificationsErrors, @@ -2303,6 +2309,84 @@ export class Organization extends HeyApiClient { } } +export class Session2 extends HeyApiClient { + /** + * Get cloud session + * + * Fetch full session data from the Kilo cloud for preview + */ + public get( + parameters: { + id: string + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "path", key: "id" }, + { in: "query", key: "directory" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/kilo/cloud/session/{id}", + ...options, + ...params, + }) + } + + /** + * Import session from cloud + * + * Download a cloud-synced session and write it to local storage with fresh IDs. + */ + public import( + parameters?: { + directory?: string + sessionId?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "body", key: "sessionId" }, + ], + }, + ], + ) + return (options?.client ?? this.client).post< + KiloCloudSessionImportResponses, + KiloCloudSessionImportErrors, + ThrowOnError + >({ + url: "/kilo/cloud/session/import", + ...options, + ...params, + headers: { + "Content-Type": "application/json", + ...options?.headers, + ...params.headers, + }, + }) + } +} + +export class Cloud extends HeyApiClient { + private _session?: Session2 + get session(): Session2 { + return (this._session ??= new Session2({ client: this.client })) + } +} + export class Kilo extends HeyApiClient { /** * Get Kilo Gateway profile @@ -2385,10 +2469,34 @@ export class Kilo extends HeyApiClient { }) } + /** + * Get cloud sessions + * + * Fetch cloud CLI sessions from Kilo API + */ + public cloudSessions( + parameters?: { + directory?: string + }, + options?: Options, + ) { + const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + return (options?.client ?? this.client).get({ + url: "/kilo/cloud-sessions", + ...options, + ...params, + }) + } + private _organization?: Organization get organization(): Organization { return (this._organization ??= new Organization({ client: this.client })) } + + private _cloud?: Cloud + get cloud(): Cloud { + return (this._cloud ??= new Cloud({ client: this.client })) + } } export class Find extends HeyApiClient { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index e449c27a647..992bae7e1b4 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -4444,6 +4444,101 @@ export type KiloNotificationsResponses = { export type KiloNotificationsResponse = KiloNotificationsResponses[keyof KiloNotificationsResponses] +export type KiloCloudSessionGetData = { + body?: never + path: { + id: string + } + query?: { + directory?: string + } + url: "/kilo/cloud/session/{id}" +} + +export type KiloCloudSessionGetErrors = { + /** + * Not found + */ + 404: NotFoundError +} + +export type KiloCloudSessionGetError = KiloCloudSessionGetErrors[keyof KiloCloudSessionGetErrors] + +export type KiloCloudSessionGetResponses = { + /** + * Cloud session data + */ + 200: unknown +} + +export type KiloCloudSessionImportData = { + body?: { + sessionId: string + } + path?: never + query?: { + directory?: string + } + url: "/kilo/cloud/session/import" +} + +export type KiloCloudSessionImportErrors = { + /** + * Bad request + */ + 400: BadRequestError + /** + * Not found + */ + 404: NotFoundError +} + +export type KiloCloudSessionImportError = KiloCloudSessionImportErrors[keyof KiloCloudSessionImportErrors] + +export type KiloCloudSessionImportResponses = { + /** + * Imported session info + */ + 200: unknown +} + +export type KiloCloudSessionsData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/kilo/cloud-sessions" +} + +export type KiloCloudSessionsErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type KiloCloudSessionsError = KiloCloudSessionsErrors[keyof KiloCloudSessionsErrors] + +export type KiloCloudSessionsResponses = { + /** + * Cloud sessions list + */ + 200: { + cliSessions: Array<{ + session_id: string + title: string | null + cloud_agent_session_id: string | null + created_at: string + updated_at: string + version: number + }> + nextCursor: string | null + } +} + +export type KiloCloudSessionsResponse = KiloCloudSessionsResponses[keyof KiloCloudSessionsResponses] + export type FindTextData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 4ef3e49b93d..248fd9bc81c 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -4514,6 +4514,228 @@ ] } }, + "/kilo/cloud/session/{id}": { + "get": { + "operationId": "kilo.cloud.session.get", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true + } + ], + "summary": "Get cloud session", + "description": "Fetch full session data from the Kilo cloud for preview", + "responses": { + "200": { + "description": "Cloud session data", + "content": { + "application/json": { + "schema": {} + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@kilocode/sdk\n\nconst client = createOpencodeClient()\nawait client.kilo.cloud.session.get({\n ...\n})" + } + ] + } + }, + "/kilo/cloud/session/import": { + "post": { + "operationId": "kilo.cloud.session.import", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Import session from cloud", + "description": "Download a cloud-synced session and write it to local storage with fresh IDs.", + "responses": { + "200": { + "description": "Imported session info", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + }, + "404": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundError" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + } + }, + "required": ["sessionId"] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@kilocode/sdk\n\nconst client = createOpencodeClient()\nawait client.kilo.cloud.session.import({\n ...\n})" + } + ] + } + }, + "/kilo/cloud-sessions": { + "get": { + "operationId": "kilo.cloudSessions", + "parameters": [ + { + "in": "query", + "name": "directory", + "schema": { + "type": "string" + } + } + ], + "summary": "Get cloud sessions", + "description": "Fetch cloud CLI sessions from Kilo API", + "responses": { + "200": { + "description": "Cloud sessions list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cliSessions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "cloud_agent_session_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "version": { + "type": "number" + } + }, + "required": [ + "session_id", + "title", + "cloud_agent_session_id", + "created_at", + "updated_at", + "version" + ] + } + }, + "nextCursor": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["cliSessions", "nextCursor"] + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@kilocode/sdk\n\nconst client = createOpencodeClient()\nawait client.kilo.cloudSessions({\n ...\n})" + } + ] + } + }, "/find": { "get": { "operationId": "find.text", From 5d57287f9a33cef3ca443a5948f34653c31548af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:29:03 +0100 Subject: [PATCH 21/71] feat(kilo-vscode): add HttpClient methods for cloud session preview and import - getCloudSession(id) fetches full session data for read-only preview - importCloudSession(id) writes cloud session to local storage with fresh IDs - CloudSessionData type for the export response shape --- .../src/services/cli-backend/http-client.ts | 27 ++++++++++++++++++ .../src/services/cli-backend/index.ts | 1 + .../src/services/cli-backend/types.ts | 28 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/packages/kilo-vscode/src/services/cli-backend/http-client.ts b/packages/kilo-vscode/src/services/cli-backend/http-client.ts index fc75b9a2170..c14349cefdc 100644 --- a/packages/kilo-vscode/src/services/cli-backend/http-client.ts +++ b/packages/kilo-vscode/src/services/cli-backend/http-client.ts @@ -13,6 +13,7 @@ import type { Config, KilocodeNotification, CloudSessionsResponse, + CloudSessionData, } from "./types" import { extractHttpErrorMessage, parseSSEDataLine } from "./http-utils" @@ -363,6 +364,32 @@ export class HttpClient { } } + /** + * Fetch full cloud session data for read-only preview. + * Returns null if the session is not found or the request fails. + */ + async getCloudSession(sessionId: string): Promise { + try { + return await this.request("GET", `/kilo/cloud/session/${sessionId}`) + } catch (err) { + console.warn("[Kilo] Failed to fetch cloud session:", err) + return null + } + } + + /** + * Import a cloud session into local storage with fresh IDs. + * Returns the imported session info, or null on failure. + */ + async importCloudSession(sessionId: string): Promise { + try { + return await this.request("POST", "/kilo/cloud/session/import", { sessionId }) + } catch (err) { + console.warn("[Kilo] Failed to import cloud session:", err) + return null + } + } + /** * Switch the active organization. * Pass null to switch back to personal account. diff --git a/packages/kilo-vscode/src/services/cli-backend/index.ts b/packages/kilo-vscode/src/services/cli-backend/index.ts index 10e9ac968da..b2446bde9ad 100644 --- a/packages/kilo-vscode/src/services/cli-backend/index.ts +++ b/packages/kilo-vscode/src/services/cli-backend/index.ts @@ -28,6 +28,7 @@ export type { Config, KilocodeNotification, KilocodeNotificationAction, + CloudSessionData, } from "./types" export { ServerManager } from "./server-manager" diff --git a/packages/kilo-vscode/src/services/cli-backend/types.ts b/packages/kilo-vscode/src/services/cli-backend/types.ts index d23e2dca0bd..7fbc206531a 100644 --- a/packages/kilo-vscode/src/services/cli-backend/types.ts +++ b/packages/kilo-vscode/src/services/cli-backend/types.ts @@ -351,3 +351,31 @@ export interface CloudSessionsResponse { cliSessions: CloudSessionInfo[] nextCursor: string | null } + +// Full cloud session data for preview (from /kilo/cloud/session/:id) +export interface CloudSessionData { + info: { + id: string + title: string + time: { created: number; updated: number } + [key: string]: unknown + } + messages: Array<{ + info: { + id: string + sessionID: string + role: "user" | "assistant" + time: { created: number; completed?: number } + cost?: { input: number; output: number; reasoning?: number; cache?: { read: number; write: number } } + tokens?: { input: number; output: number; reasoning?: number; cache?: { read: number; write: number } } + [key: string]: unknown + } + parts: Array<{ + id: string + sessionID: string + messageID: string + type: string + [key: string]: unknown + }> + }> +} From dffc721c519ed37ae120ec31896a67bbfa9b2050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:30:09 +0100 Subject: [PATCH 22/71] feat(kilo-vscode): add message types for cloud session browse-then-clone flow Adds types for: - requestCloudSessionData: webview asks extension to fetch full session - cloudSessionDataLoaded: extension sends preview data to webview - importAndSend: webview asks to import + send message on cloud session - cloudSessionImported/Failed: import result from extension --- .../webview-ui/src/types/messages.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 31e7e98f5f6..5d8e93a8520 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -418,6 +418,25 @@ export interface GitRemoteUrlLoadedMessage { gitUrl: string | null } +export interface CloudSessionDataLoadedMessage { + type: "cloudSessionDataLoaded" + cloudSessionId: string + title: string + messages: Message[] +} + +export interface CloudSessionImportedMessage { + type: "cloudSessionImported" + cloudSessionId: string + session: SessionInfo +} + +export interface CloudSessionImportFailedMessage { + type: "cloudSessionImportFailed" + cloudSessionId: string + error: string +} + export interface ActionMessage { type: "action" action: string @@ -695,6 +714,9 @@ export type ExtensionMessage = | SetChatBoxMessage | TriggerTaskMessage | VariantsLoadedMessage + | CloudSessionDataLoadedMessage + | CloudSessionImportedMessage + | CloudSessionImportFailedMessage // ============================================ // Messages FROM webview TO extension @@ -756,6 +778,22 @@ export interface RequestGitRemoteUrlMessage { type: "requestGitRemoteUrl" } +export interface RequestCloudSessionDataMessage { + type: "requestCloudSessionData" + sessionId: string +} + +export interface ImportAndSendMessage { + type: "importAndSend" + cloudSessionId: string + text: string + providerID?: string + modelID?: string + agent?: string + variant?: string + files?: FileAttachment[] +} + export interface LoginRequest { type: "login" } @@ -1059,6 +1097,8 @@ export type WebviewMessage = | SetSessionsCollapsedRequest | PersistVariantRequest | RequestVariantsMessage + | RequestCloudSessionDataMessage + | ImportAndSendMessage // ============================================ // VS Code API type From e7b70edc380347d7d2684cbf18d92e6f1d00703d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:31:41 +0100 Subject: [PATCH 23/71] feat(kilo-vscode): add KiloProvider handlers for cloud session preview and import - handleRequestCloudSessionData: fetches full session from cloud, transforms to webview format, sends cloudSessionDataLoaded - handleImportAndSend: imports cloud session to local with fresh IDs, then sends the user's new message on the local session --- packages/kilo-vscode/src/KiloProvider.ts | 142 +++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 6d7d8888c8d..551da56870c 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -440,6 +440,31 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.postMessage({ type: "gitRemoteUrlLoaded", gitUrl: url ?? null }) }) break + case "requestCloudSessionData": + void this.handleRequestCloudSessionData(message.sessionId) + break + case "importAndSend": { + const files = z + .array( + z.object({ + mime: z.string(), + url: z.string().refine((u) => u.startsWith("file://") || u.startsWith("data:")), + }), + ) + .optional() + .catch(undefined) + .parse(message.files) + void this.handleImportAndSend( + message.cloudSessionId, + message.text, + message.providerID, + message.modelID, + message.agent, + message.variant, + files, + ) + break + } case "dismissNotification": await this.handleDismissNotification(message.notificationId) break @@ -998,6 +1023,123 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + /** + * Fetch full cloud session data for read-only preview. + * Transforms the export data into webview message format and sends it back. + */ + private async handleRequestCloudSessionData(sessionId: string): Promise { + if (!this.httpClient) { + this.postMessage({ + type: "cloudSessionImportFailed", + cloudSessionId: sessionId, + error: "Not connected to CLI backend", + }) + return + } + + const data = await this.httpClient.getCloudSession(sessionId) + if (!data) { + this.postMessage({ + type: "cloudSessionImportFailed", + cloudSessionId: sessionId, + error: "Failed to fetch cloud session", + }) + return + } + + const messages = (data.messages ?? []).map((m) => ({ + id: m.info.id, + sessionID: m.info.sessionID, + role: m.info.role as "user" | "assistant", + parts: m.parts, + createdAt: new Date(m.info.time.created).toISOString(), + cost: m.info.cost, + tokens: m.info.tokens, + })) + + this.postMessage({ + type: "cloudSessionDataLoaded", + cloudSessionId: sessionId, + title: data.info.title ?? "Untitled", + messages, + }) + } + + /** + * Import a cloud session to local storage, then send a new message on it. + * This is the "clone on first message" flow — the cloud session becomes a + * local session only when the user decides to continue it. + */ + private async handleImportAndSend( + cloudSessionId: string, + text: string, + providerID?: string, + modelID?: string, + agent?: string, + variant?: string, + files?: Array<{ mime: string; url: string }>, + ): Promise { + if (!this.httpClient) { + this.postMessage({ + type: "cloudSessionImportFailed", + cloudSessionId, + error: "Not connected to CLI backend", + }) + return + } + + const workspaceDir = this.getWorkspaceDirectory() + + // Step 1: Import the cloud session with fresh IDs + const session = await this.httpClient.importCloudSession(cloudSessionId) + if (!session) { + this.postMessage({ + type: "cloudSessionImportFailed", + cloudSessionId, + error: "Failed to import session from cloud", + }) + return + } + + // Track the new local session + this.currentSession = session + this.trackedSessionIds.add(session.id) + + // Notify webview of the import success + this.postMessage({ + type: "cloudSessionImported", + cloudSessionId, + session: this.sessionToWebview(session), + }) + + // Step 2: Send the user's message on the new local session + const parts: Array<{ type: "text"; text: string } | { type: "file"; mime: string; url: string }> = [] + + const editor = vscode.window.activeTextEditor + if (editor && editor.document.uri.scheme === "file") { + const url = editor.document.uri.toString() + const already = files?.some((f) => f.url === url) + if (!already) { + parts.push({ type: "file", mime: "text/plain", url }) + } + } + + if (files) { + for (const f of files) { + parts.push({ type: "file", mime: f.mime, url: f.url }) + } + } + + parts.push({ type: "text", text }) + + await this.httpClient.sendMessage(session.id, parts, workspaceDir, { + providerID, + modelID, + agent, + variant, + }) + } + /** * Persist a dismissed notification ID in globalState and push updated lists to webview. */ From c55960c2700ef949c22dd159dde583e8d91c1eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:34:09 +0100 Subject: [PATCH 24/71] feat(kilo-vscode): add cloud session preview mode to session context - selectCloudSession() initiates read-only preview of a cloud session - Messages populate the store directly from cloud data (no local DB write) - sendMessage() intercepts cloud preview to import-then-send instead - handleCloudSessionImported() transitions from preview to local session - Uses synthetic 'cloud:' keys to avoid collision with real sessions --- .../webview-ui/src/context/session.tsx | 103 +++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 5fedced3455..84c1c39627f 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -125,6 +125,10 @@ interface SessionContextValue { deleteSession: (id: string) => void renameSession: (id: string, title: string) => void syncSession: (sessionID: string) => void + + // Cloud session preview + cloudPreviewId: Accessor + selectCloudSession: (cloudSessionId: string) => void } const SessionContext = createContext() @@ -175,6 +179,9 @@ export const SessionProvider: ParentComponent = (props) => { // Pending agent selection for before a session exists (mirrors pendingModelSelection) const [pendingAgentSelection, setPendingAgentSelection] = createSignal(null) + // Cloud session preview state + const [cloudPreviewId, setCloudPreviewId] = createSignal(null) + // Store for sessions, messages, parts, todos, modelSelections, agentSelections const [store, setStore] = createStore({ sessions: {}, @@ -376,6 +383,20 @@ export const SessionProvider: ParentComponent = (props) => { // (or has no sessionID for backwards compatibility) if (!message.sessionID || message.sessionID === currentSessionID()) setLoading(false) break + + case "cloudSessionDataLoaded": + handleCloudSessionDataLoaded(message.cloudSessionId, message.title, message.messages) + break + + case "cloudSessionImported": + handleCloudSessionImported(message.cloudSessionId, message.session) + break + + case "cloudSessionImportFailed": + setCloudPreviewId(null) + setLoading(false) + console.error("[Kilo New] Cloud session import failed:", message.error) + break } }) @@ -639,6 +660,59 @@ export const SessionProvider: ParentComponent = (props) => { }) } + function handleCloudSessionDataLoaded(cloudSessionId: string, title: string, messages: Message[]) { + const key = `cloud:${cloudSessionId}` + batch(() => { + setStore("sessions", key, { + id: key, + title, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }) + setStore("messages", key, messages) + for (const msg of messages) { + if (msg.parts && msg.parts.length > 0) { + setStore("parts", msg.id, msg.parts) + } + } + setCurrentSessionID(key) + setLoading(false) + }) + } + + function handleCloudSessionImported(cloudSessionId: string, session: SessionInfo) { + const cloudKey = `cloud:${cloudSessionId}` + batch(() => { + setStore("sessions", session.id, session) + + const pending = pendingModelSelection() + if (pending && !store.modelSelections[session.id]) { + setStore("modelSelections", session.id, pending) + } + const pendingAgent = pendingAgentSelection() + if (pendingAgent && !store.agentSelections[session.id]) { + setStore("agentSelections", session.id, pendingAgent) + } + + setCloudPreviewId(null) + setCurrentSessionID(session.id) + setLoading(true) + + setStore( + "sessions", + produce((sessions) => { + delete sessions[cloudKey] + }), + ) + setStore( + "messages", + produce((messages) => { + delete messages[cloudKey] + }), + ) + }) + } + // Actions function selectAgent(name: string) { const id = currentSessionID() @@ -655,7 +729,22 @@ export const SessionProvider: ParentComponent = (props) => { return } - // Phase 4: optimistic user message + const preview = cloudPreviewId() + if (preview) { + const agent = selectedAgentName() !== defaultAgent() ? selectedAgentName() : undefined + vscode.postMessage({ + type: "importAndSend", + cloudSessionId: preview, + text, + providerID, + modelID, + agent, + variant: currentVariant(), + files, + }) + return + } + const sid = currentSessionID() if (sid) { const tempId = `optimistic-${crypto.randomUUID()}` @@ -802,6 +891,16 @@ export const SessionProvider: ParentComponent = (props) => { vscode.postMessage({ type: "loadMessages", sessionID: id }) } + function selectCloudSession(cloudSessionId: string) { + if (!server.isConnected()) { + console.warn("[Kilo New] Cannot select cloud session: not connected") + return + } + setCloudPreviewId(cloudSessionId) + setLoading(true) + vscode.postMessage({ type: "requestCloudSessionData", sessionId: cloudSessionId }) + } + function deleteSession(id: string) { if (!server.isConnected()) { console.warn("[Kilo New] Cannot delete session: not connected") @@ -930,6 +1029,8 @@ export const SessionProvider: ParentComponent = (props) => { deleteSession, renameSession, syncSession, + cloudPreviewId, + selectCloudSession, } return {props.children} From 70f4f22ea82eb6a0e70533a70229e207089a06c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:35:14 +0100 Subject: [PATCH 25/71] feat(kilo-vscode): wire up cloud session click to browse-then-clone flow Clicking a cloud session in the list now triggers selectCloudSession() which fetches the full session data for preview, then navigates to the chat view. A local session is only created when the user sends a message. --- packages/kilo-vscode/webview-ui/src/App.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index 319209fff01..fdbc3c5e71f 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -171,7 +171,12 @@ const AppContent: Component = () => { - + { + session.selectCloudSession(cloudSessionId) + setCurrentView("newTask") + }} + /> Date: Mon, 23 Feb 2026 17:55:04 +0100 Subject: [PATCH 26/71] fix(kilo-vscode): set currentSessionID synchronously in selectCloudSession selectCloudSession was not setting currentSessionID before the async fetch, causing the ChatView to render the empty welcome state instead of the loading spinner. Now sets it to the synthetic 'cloud:' key immediately, matching selectSession's behavior. --- packages/kilo-vscode/webview-ui/src/context/session.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 84c1c39627f..d02b09ef599 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -896,7 +896,9 @@ export const SessionProvider: ParentComponent = (props) => { console.warn("[Kilo New] Cannot select cloud session: not connected") return } + const key = `cloud:${cloudSessionId}` setCloudPreviewId(cloudSessionId) + setCurrentSessionID(key) setLoading(true) vscode.postMessage({ type: "requestCloudSessionData", sessionId: cloudSessionId }) } From ee8c62c3fb693218c03b240aa3f2c4ca4e061f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 17:59:21 +0100 Subject: [PATCH 27/71] fix(kilo-vscode): hide cloud preview sessions from recents and guard selectSession - Filter out synthetic 'cloud:*' sessions from the sessions() memo so they don't appear in the recent sessions list - Guard selectSession against cloud preview IDs to prevent 500 errors when a stale cloud session entry is clicked --- packages/kilo-vscode/webview-ui/src/context/session.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index d02b09ef599..3acc1009e3a 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -886,6 +886,10 @@ export const SessionProvider: ParentComponent = (props) => { console.warn("[Kilo New] Cannot select session: not connected") return } + if (id.startsWith("cloud:")) { + console.warn("[Kilo New] Cannot select cloud preview session via selectSession") + return + } setCurrentSessionID(id) setLoading(true) vscode.postMessage({ type: "loadMessages", sessionID: id }) @@ -948,7 +952,9 @@ export const SessionProvider: ParentComponent = (props) => { } const sessions = createMemo(() => - Object.values(store.sessions).sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()), + Object.values(store.sessions) + .filter((s) => !s.id.startsWith("cloud:")) + .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()), ) const totalCost = createMemo(() => calcTotalCost(messages())) From 478421fd589a9e3a17cf7a7d8ba4b8a41fcde2a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 18:05:26 +0100 Subject: [PATCH 28/71] fix(kilo-gateway): write imported sessions to SQLite instead of filesystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The import endpoint was using Storage.write() which writes JSON files, but the CLI reads sessions from SQLite via Database. Rewrote to use Database.use() with Drizzle inserts into SessionTable, MessageTable, and PartTable — matching the pattern from the CLI's import command. --- packages/kilo-gateway/src/server/routes.ts | 76 +++++++++++++++++----- packages/opencode/src/server/server.ts | 10 ++- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index c1a87a5b882..984e4676ce3 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -29,8 +29,12 @@ interface KiloRoutesDeps { errors: Errors Auth: Auth z: Z - Storage: any + Database: any Instance: any + SessionTable: any + MessageTable: any + PartTable: any + SessionToRow: (info: any) => any } /** @@ -69,7 +73,21 @@ function generateId(prefix: string, descending: boolean): string { } export function createKiloRoutes(deps: KiloRoutesDeps) { - const { Hono, describeRoute, validator, resolver, errors, Auth, z, Storage, Instance } = deps + const { + Hono, + describeRoute, + validator, + resolver, + errors, + Auth, + z, + Database, + Instance, + SessionTable, + MessageTable, + PartTable, + SessionToRow, + } = deps const Organization = z.object({ id: z.string(), @@ -396,22 +414,48 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { data.info.id = localSessionID data.info.projectID = projectID - await Storage.write(["session", projectID, localSessionID], data.info) + data.info.slug = data.info.slug || "imported" + data.info.directory = data.info.directory || Instance.directory || "." + data.info.version = data.info.version || "2" - for (const msg of data.messages ?? []) { - const msgID = generateId("msg", false) - msgMap.set(msg.info.id, msgID) - msg.info.id = msgID - msg.info.sessionID = localSessionID - if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID - await Storage.write(["message", localSessionID, msgID], msg.info) - for (const part of msg.parts ?? []) { - part.id = generateId("prt", false) - part.messageID = msgID - part.sessionID = localSessionID - await Storage.write(["part", msgID, part.id], part) + Database.use((db: any) => { + db.insert(SessionTable).values(SessionToRow(data.info)).onConflictDoNothing().run() + + for (const msg of data.messages ?? []) { + const msgID = generateId("msg", false) + msgMap.set(msg.info.id, msgID) + msg.info.id = msgID + msg.info.sessionID = localSessionID + if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID + + db.insert(MessageTable) + .values({ + id: msgID, + session_id: localSessionID, + time_created: msg.info.time?.created ?? Date.now(), + data: msg.info, + }) + .onConflictDoNothing() + .run() + + for (const part of msg.parts ?? []) { + const partID = generateId("prt", false) + part.id = partID + part.messageID = msgID + part.sessionID = localSessionID + + db.insert(PartTable) + .values({ + id: partID, + message_id: msgID, + session_id: localSessionID, + data: part, + }) + .onConflictDoNothing() + .run() + } } - } + }) return c.json(data.info) }, diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 2145d1f572c..ab5121b9bf6 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -31,7 +31,9 @@ import { ExperimentalRoutes } from "./routes/experimental" import { TelemetryRoutes } from "./routes/telemetry" // kilocode_change import { ProviderRoutes } from "./routes/provider" import { createKiloRoutes } from "@kilocode/kilo-gateway" // kilocode_change -import { Storage } from "../storage/storage" // kilocode_change +import { Database } from "../storage/db" // kilocode_change +import { Session } from "../session" // kilocode_change +import { SessionTable, MessageTable, PartTable } from "../session/session.sql" // kilocode_change import { lazy } from "../util/lazy" import { InstanceBootstrap } from "../project/bootstrap" import { NotFoundError } from "../storage/db" @@ -249,8 +251,12 @@ export namespace Server { errors, Auth, z, - Storage, // kilocode_change + Database, // kilocode_change Instance, // kilocode_change + SessionTable, // kilocode_change + MessageTable, // kilocode_change + PartTable, // kilocode_change + SessionToRow: Session.toRow, // kilocode_change }), ) // kilocode_change end From ab094ab1c0c09fd996acac780b029916bc885290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 18:09:16 +0100 Subject: [PATCH 29/71] fix(kilo-vscode): request messages after cloud session import to clear loading After importing a cloud session and switching to the new local session, loading was set to true but never cleared because messagesLoaded was never triggered. Now posts loadMessages for the imported session which fetches the messages from the backend and clears the loading state. --- packages/kilo-vscode/webview-ui/src/context/session.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 3acc1009e3a..b419d99d7ed 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -711,6 +711,7 @@ export const SessionProvider: ParentComponent = (props) => { }), ) }) + vscode.postMessage({ type: "loadMessages", sessionID: session.id }) } // Actions From cde66c3eeac495a46f1e1aeb070251a57a326b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 18:13:44 +0100 Subject: [PATCH 30/71] fix(kilo-vscode): eliminate loader flash when transitioning from cloud to local session Carry over cloud preview messages to the new local session ID in the store instead of setting loading=true and re-fetching. Messages remain visible during the transition with zero flicker. loadMessages still fires in the background to pick up real server-assigned IDs. --- packages/kilo-vscode/webview-ui/src/context/session.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index b419d99d7ed..ce33e96368f 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -682,6 +682,7 @@ export const SessionProvider: ParentComponent = (props) => { function handleCloudSessionImported(cloudSessionId: string, session: SessionInfo) { const cloudKey = `cloud:${cloudSessionId}` + const cloudMessages = store.messages[cloudKey] ?? [] batch(() => { setStore("sessions", session.id, session) @@ -694,9 +695,11 @@ export const SessionProvider: ParentComponent = (props) => { setStore("agentSelections", session.id, pendingAgent) } + // Carry over cloud messages so there's no loading flash + setStore("messages", session.id, cloudMessages) + setCloudPreviewId(null) setCurrentSessionID(session.id) - setLoading(true) setStore( "sessions", @@ -711,6 +714,8 @@ export const SessionProvider: ParentComponent = (props) => { }), ) }) + // Load real messages in the background (picks up server-assigned IDs + // and the new user message once the send completes via SSE) vscode.postMessage({ type: "loadMessages", sessionID: session.id }) } From f8ee270c59f7fead9ee5d5fb197c96e66897fef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 18:21:41 +0100 Subject: [PATCH 31/71] fix(kilo-vscode): pass workspace directory to cloud session import importCloudSession was not sending the x-opencode-directory header, causing the server to resolve the wrong project ID (from process.cwd() instead of the workspace). The imported session was written with a mismatched project_id and excluded from Session.list() on reload. --- packages/kilo-vscode/src/KiloProvider.ts | 2 +- packages/kilo-vscode/src/services/cli-backend/http-client.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 551da56870c..d01ea0070ee 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1091,7 +1091,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper const workspaceDir = this.getWorkspaceDirectory() // Step 1: Import the cloud session with fresh IDs - const session = await this.httpClient.importCloudSession(cloudSessionId) + const session = await this.httpClient.importCloudSession(cloudSessionId, workspaceDir) if (!session) { this.postMessage({ type: "cloudSessionImportFailed", diff --git a/packages/kilo-vscode/src/services/cli-backend/http-client.ts b/packages/kilo-vscode/src/services/cli-backend/http-client.ts index c14349cefdc..7d46ccd9356 100644 --- a/packages/kilo-vscode/src/services/cli-backend/http-client.ts +++ b/packages/kilo-vscode/src/services/cli-backend/http-client.ts @@ -381,9 +381,9 @@ export class HttpClient { * Import a cloud session into local storage with fresh IDs. * Returns the imported session info, or null on failure. */ - async importCloudSession(sessionId: string): Promise { + async importCloudSession(sessionId: string, directory: string): Promise { try { - return await this.request("POST", "/kilo/cloud/session/import", { sessionId }) + return await this.request("POST", "/kilo/cloud/session/import", { sessionId }, { directory }) } catch (err) { console.warn("[Kilo] Failed to import cloud session:", err) return null From f34a8c2cd38f458d58633b26cd20d6a1e340b6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 18:32:06 +0100 Subject: [PATCH 32/71] fix(kilo-gateway): fire Session.Event.Created after cloud session import The imported session was never registered with the cloud sync system because Session.Event.Created was not fired. KiloSessions.init() subscribes to this event to call KiloSessions.create(), which registers the session with the ingest API and creates the session_share record needed for incremental sync. Now uses Database.effect() to publish the event after the DB transaction completes. --- packages/kilo-gateway/src/server/routes.ts | 6 ++++++ packages/opencode/src/server/server.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 984e4676ce3..6d4bb8d05d4 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -35,6 +35,8 @@ interface KiloRoutesDeps { MessageTable: any PartTable: any SessionToRow: (info: any) => any + Bus: { publish: (event: any, payload: any) => void } + SessionCreatedEvent: any } /** @@ -87,6 +89,8 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { MessageTable, PartTable, SessionToRow, + Bus, + SessionCreatedEvent, } = deps const Organization = z.object({ @@ -455,6 +459,8 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { .run() } } + + Database.effect(() => Bus.publish(SessionCreatedEvent, { info: data.info })) }) return c.json(data.info) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index ab5121b9bf6..c4ee7d38017 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -257,6 +257,8 @@ export namespace Server { MessageTable, // kilocode_change PartTable, // kilocode_change SessionToRow: Session.toRow, // kilocode_change + Bus, // kilocode_change + SessionCreatedEvent: Session.Event.Created, // kilocode_change }), ) // kilocode_change end From 085d378626c2604106e650f4cc1957519d14d39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 19:29:07 +0100 Subject: [PATCH 33/71] fix: address review feedback on cloud session browse flow Use Database.transaction() for atomic imports, add try/catch for sendMessage after import, guard against stale cloud session responses, clean up parts store on import, add optional chaining for time.created, and use monotonic counter in generateId. --- packages/kilo-gateway/src/server/routes.ts | 5 ++-- packages/kilo-vscode/src/KiloProvider.ts | 23 +++++++++++++------ .../webview-ui/src/context/session.tsx | 10 ++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 6d4bb8d05d4..ac1f103e204 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -62,12 +62,13 @@ interface KiloRoutesDeps { * }) * ``` */ +let counter = 0 function generateId(prefix: string, descending: boolean): string { const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" const bytes = randomBytes(14) let rand = "" for (let i = 0; i < 14; i++) rand += chars[bytes[i] % 62] - let now = BigInt(Date.now()) * BigInt(0x1000) + BigInt(1) + let now = BigInt(Date.now()) * BigInt(0x1000) + BigInt(counter++) if (descending) now = ~now const buf = Buffer.alloc(6) for (let i = 0; i < 6; i++) buf[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) @@ -422,7 +423,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { data.info.directory = data.info.directory || Instance.directory || "." data.info.version = data.info.version || "2" - Database.use((db: any) => { + Database.transaction((db: any) => { db.insert(SessionTable).values(SessionToRow(data.info)).onConflictDoNothing().run() for (const msg of data.messages ?? []) { diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index d01ea0070ee..64bf2bd72e1 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1052,7 +1052,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper sessionID: m.info.sessionID, role: m.info.role as "user" | "assistant", parts: m.parts, - createdAt: new Date(m.info.time.created).toISOString(), + createdAt: m.info.time?.created ? new Date(m.info.time.created).toISOString() : new Date().toISOString(), cost: m.info.cost, tokens: m.info.tokens, })) @@ -1132,12 +1132,21 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper parts.push({ type: "text", text }) - await this.httpClient.sendMessage(session.id, parts, workspaceDir, { - providerID, - modelID, - agent, - variant, - }) + try { + await this.httpClient.sendMessage(session.id, parts, workspaceDir, { + providerID, + modelID, + agent, + variant, + }) + } catch (err) { + console.error("[Kilo New] Failed to send message after cloud import:", err) + this.postMessage({ + type: "error", + message: err instanceof Error ? err.message : "Failed to send message after import", + sessionID: session.id, + }) + } } /** diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index ce33e96368f..53881671d65 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -661,6 +661,7 @@ export const SessionProvider: ParentComponent = (props) => { } function handleCloudSessionDataLoaded(cloudSessionId: string, title: string, messages: Message[]) { + if (cloudPreviewId() !== cloudSessionId) return const key = `cloud:${cloudSessionId}` batch(() => { setStore("sessions", key, { @@ -701,6 +702,15 @@ export const SessionProvider: ParentComponent = (props) => { setCloudPreviewId(null) setCurrentSessionID(session.id) + const msgIds = cloudMessages.map((m) => m.id) + setStore( + "parts", + produce((parts) => { + for (const id of msgIds) { + delete parts[id] + } + }), + ) setStore( "sessions", produce((sessions) => { From 0c7348e9ee7832e1153fea63935594632cbdfa8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 19:44:13 +0100 Subject: [PATCH 34/71] feat: add Import from cloud button to main screen empty state Adds an 'Import from cloud' button below recent sessions on the home screen. Clicking opens a dialog where users can paste a session ID, kilo.ai URL, or 'kilo import ' CLI command to open a cloud session as a read-only preview, reusing the existing selectCloudSession/importAndSend flow. --- .../src/components/chat/CloudImportDialog.tsx | 64 ++++++++++++++++++ .../src/components/chat/MessageList.tsx | 17 +++++ .../kilo-vscode/webview-ui/src/i18n/ar.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/br.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/bs.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/da.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/de.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/en.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/es.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/fr.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/ja.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/ko.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/no.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/pl.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/ru.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/th.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/zh.ts | 5 ++ .../kilo-vscode/webview-ui/src/i18n/zht.ts | 5 ++ .../webview-ui/src/styles/chat.css | 66 +++++++++++++++++++ 19 files changed, 227 insertions(+) create mode 100644 packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx new file mode 100644 index 00000000000..3d3dacaa15c --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx @@ -0,0 +1,64 @@ +import { Component, createSignal } from "solid-js" +import { Dialog } from "@kilocode/kilo-ui/dialog" +import { Button } from "@kilocode/kilo-ui/button" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" +import { useLanguage } from "../../context/language" + +const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i + +function extractSessionId(raw: string): string | null { + const trimmed = raw.trim() + const match = trimmed.match(UUID_RE) + return match ? match[0] : null +} + +interface CloudImportDialogProps { + onImport: (sessionId: string) => void +} + +export const CloudImportDialog: Component = (props) => { + const language = useLanguage() + const dialog = useDialog() + const [value, setValue] = createSignal("") + const [error, setError] = createSignal(null) + + function submit() { + const id = extractSessionId(value()) + if (!id) { + setError(language.t("session.cloud.import.invalid")) + return + } + props.onImport(id) + dialog.close() + } + + return ( + +
+ { + setValue(e.currentTarget.value) + setError(null) + }} + onKeyDown={(e) => { + if (e.key === "Enter") submit() + }} + autofocus + /> + {error() && {error()}} +
+ + +
+
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index fbbe63dadbd..e4737f95b16 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -6,11 +6,13 @@ import { Component, For, Show, createSignal, createEffect, createMemo, onCleanup, JSX } from "solid-js" import { Spinner } from "@kilocode/kilo-ui/spinner" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { useSession } from "../../context/session" import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" import { formatRelativeDate } from "../../utils/date" import { Message } from "./Message" +import { CloudImportDialog } from "./CloudImportDialog" /** Inline working/retry indicator shown below messages while the agent is active. */ const WorkingIndicator: Component = () => { @@ -73,6 +75,7 @@ export const MessageList: Component = (props) => { const session = useSession() const server = useServer() const language = useLanguage() + const dialog = useDialog() let containerRef: HTMLDivElement | undefined const [isAtBottom, setIsAtBottom] = createSignal(true) @@ -181,6 +184,20 @@ export const MessageList: Component = (props) => { + diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index ff683753fb4..2b76b059b39 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -689,6 +689,11 @@ export const dict = { "session.search.placeholder": "البحث في الجلسات...", "session.empty": "لا توجد جلسات بعد. انقر + لبدء محادثة جديدة.", "session.cloud.repoOnly": "هذا المستودع فقط", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "مساحة عمل جديدة", "workspace.type.local": "محلي", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 1664a39f5ee..b11ab55a5cf 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -696,6 +696,11 @@ export const dict = { "session.search.placeholder": "Buscar sessões...", "session.empty": "Nenhuma sessão ainda. Clique + para iniciar uma nova conversa.", "session.cloud.repoOnly": "Apenas este repositório", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Novo espaço de trabalho", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 34677b37935..4e1a676fe20 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -723,6 +723,11 @@ export const dict = { "session.search.placeholder": "Pretraži sesije...", "session.empty": "Još nema sesija. Kliknite + za početak novog razgovora.", "session.cloud.repoOnly": "Samo ovaj repozitorij", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Novi radni prostor", "workspace.type.local": "lokalno", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index ed9dd484826..a9b993a350c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -695,6 +695,11 @@ export const dict = { "session.search.placeholder": "Søg sessioner...", "session.empty": "Ingen sessioner endnu. Klik + for at starte en ny samtale.", "session.cloud.repoOnly": "Kun dette repository", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Nyt arbejdsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 6244c5f4111..b20aa38b994 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -701,6 +701,11 @@ export const dict = { "session.search.placeholder": "Sitzungen suchen...", "session.empty": "Noch keine Sitzungen. Klicke + um eine neue Unterhaltung zu starten.", "session.cloud.repoOnly": "Nur dieses Repository", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Neuer Arbeitsbereich", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 4ff2a1e44c4..8043bee3928 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -728,6 +728,11 @@ export const dict = { "session.search.placeholder": "Search sessions...", "session.empty": "No sessions yet. Click + to start a new conversation.", "session.cloud.repoOnly": "Only this repository", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "New workspace", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 08c7656dc06..50e6fc8fff4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -701,6 +701,11 @@ export const dict = { "session.search.placeholder": "Buscar sesiones...", "session.empty": "Aún no hay sesiones. Haz clic en + para iniciar una nueva conversación.", "session.cloud.repoOnly": "Solo este repositorio", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Nuevo espacio de trabajo", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 9e72b4b178d..3356e853232 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -705,6 +705,11 @@ export const dict = { "session.search.placeholder": "Rechercher des sessions...", "session.empty": "Aucune session pour l'instant. Cliquez + pour démarrer une nouvelle conversation.", "session.cloud.repoOnly": "Uniquement ce dépôt", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Nouvel espace de travail", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index eeb431616b0..bac79a491ac 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -689,6 +689,11 @@ export const dict = { "session.search.placeholder": "セッションを検索...", "session.empty": "セッションがありません。+ をクリックして新しい会話を始めましょう。", "session.cloud.repoOnly": "このリポジトリのみ", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "新しいワークスペース", "workspace.type.local": "ローカル", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 68806dfab65..4fac3938cf1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -694,6 +694,11 @@ export const dict = { "session.search.placeholder": "세션 검색...", "session.empty": "아직 세션이 없습니다. +를 클릭하여 새 대화를 시작하세요.", "session.cloud.repoOnly": "이 저장소만", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "새 작업 공간", "workspace.type.local": "로컬", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 88bf767af9c..772029395d6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -698,6 +698,11 @@ export const dict = { "session.search.placeholder": "Søk i sesjoner...", "session.empty": "Ingen sesjoner ennå. Klikk + for å starte en ny samtale.", "session.cloud.repoOnly": "Kun dette repositoriet", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Nytt arbeidsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index cf8ea38f7f7..89f46fef446 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -697,6 +697,11 @@ export const dict = { "session.search.placeholder": "Szukaj sesji...", "session.empty": "Brak sesji. Kliknij + aby rozpocząć nową rozmowę.", "session.cloud.repoOnly": "Tylko to repozytorium", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Nowa przestrzeń robocza", "workspace.type.local": "lokalna", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index e915f694445..b878ce79ff7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -701,6 +701,11 @@ export const dict = { "session.search.placeholder": "Поиск сессий...", "session.empty": "Сессий пока нет. Нажмите + чтобы начать новый разговор.", "session.cloud.repoOnly": "Только этот репозиторий", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "Новое рабочее пространство", "workspace.type.local": "локальное", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 57729a2e0b1..37154a4fe70 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -687,6 +687,11 @@ export const dict = { "session.search.placeholder": "ค้นหาเซสชัน...", "session.empty": "ยังไม่มีเซสชัน คลิก + เพื่อเริ่มการสนทนาใหม่", "session.cloud.repoOnly": "เฉพาะรีโพซิทอรีนี้", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "พื้นที่ทำงานใหม่", "workspace.type.local": "ในเครื่อง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index e8f7d086055..ecb28418669 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -690,6 +690,11 @@ export const dict = { "session.search.placeholder": "搜索会话...", "session.empty": "暂无会话。点击 + 开始新的对话。", "session.cloud.repoOnly": "仅此仓库", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "新建工作区", "workspace.type.local": "本地", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 68fe26f228c..8d3bdff5b05 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -687,6 +687,11 @@ export const dict = { "session.search.placeholder": "搜尋工作階段...", "session.empty": "尚無工作階段。點擊 + 開始新的對話。", "session.cloud.repoOnly": "僅此儲存庫", + "session.cloud.import": "Import from cloud", + "session.cloud.import.title": "Import from cloud", + "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", + "session.cloud.import.button": "Import", + "session.cloud.import.invalid": "Invalid session ID format", "workspace.new": "新增工作區", "workspace.type.local": "本地", diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index a800424337a..274334b8639 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -164,6 +164,72 @@ flex-shrink: 0; } +/* ============================================ + Import from Cloud Button (empty state) + ============================================ */ + +.import-cloud-button { + display: flex; + align-items: center; + justify-content: center; + padding: 6px 12px; + border: 1px solid var(--vscode-button-border, var(--vscode-contrastBorder, transparent)); + border-radius: 4px; + background: transparent; + color: var(--vscode-descriptionForeground); + cursor: pointer; + font-family: inherit; + font-size: 12px; + gap: 6px; + transition: + background-color 0.15s, + color 0.15s; +} + +.import-cloud-button:hover { + background: var(--vscode-list-hoverBackground); + color: var(--vscode-foreground); +} + +/* ============================================ + Cloud Import Dialog + ============================================ */ + +.cloud-import-dialog { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 320px; +} + +.cloud-import-input { + width: 100%; + padding: 6px 8px; + background: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, transparent); + border-radius: 4px; + font-family: inherit; + font-size: 13px; + box-sizing: border-box; +} + +.cloud-import-input:focus { + outline: none; + border-color: var(--vscode-focusBorder); +} + +.cloud-import-error { + font-size: 12px; + color: var(--vscode-errorForeground); +} + +.cloud-import-actions { + display: flex; + justify-content: flex-end; + gap: 8px; +} + .scroll-to-bottom-button { position: absolute; bottom: 12px; From ef6460b58fcbea3eec292a5b69e62a95a195697e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 19:44:56 +0100 Subject: [PATCH 35/71] refactor: use kilo-ui Button for import from cloud button --- .../src/components/chat/MessageList.tsx | 8 +++--- .../webview-ui/src/styles/chat.css | 27 ------------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index e4737f95b16..c02417286b0 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -6,6 +6,7 @@ import { Component, For, Show, createSignal, createEffect, createMemo, onCleanup, JSX } from "solid-js" import { Spinner } from "@kilocode/kilo-ui/spinner" +import { Button } from "@kilocode/kilo-ui/button" import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { useSession } from "../../context/session" import { useServer } from "../../context/server" @@ -184,8 +185,9 @@ export const MessageList: Component = (props) => { - + diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 274334b8639..d792b3c257e 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -164,33 +164,6 @@ flex-shrink: 0; } -/* ============================================ - Import from Cloud Button (empty state) - ============================================ */ - -.import-cloud-button { - display: flex; - align-items: center; - justify-content: center; - padding: 6px 12px; - border: 1px solid var(--vscode-button-border, var(--vscode-contrastBorder, transparent)); - border-radius: 4px; - background: transparent; - color: var(--vscode-descriptionForeground); - cursor: pointer; - font-family: inherit; - font-size: 12px; - gap: 6px; - transition: - background-color 0.15s, - color 0.15s; -} - -.import-cloud-button:hover { - background: var(--vscode-list-hoverBackground); - color: var(--vscode-foreground); -} - /* ============================================ Cloud Import Dialog ============================================ */ From dc038a28e3419107ad8f38ca37af90e510a66de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 19:46:32 +0100 Subject: [PATCH 36/71] fix: add padding to cloud import dialog content --- packages/kilo-vscode/webview-ui/src/styles/chat.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index d792b3c257e..19299c31b44 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -172,7 +172,7 @@ display: flex; flex-direction: column; gap: 12px; - min-width: 320px; + padding: 0 24px 12px 24px; } .cloud-import-input { From ea2aafcbc8a10094ec97091db4bada12ed7e45ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 19:51:46 +0100 Subject: [PATCH 37/71] fix: center fit dialogs vertically by removing min-height constraint --- packages/kilo-ui/src/components/dialog.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/kilo-ui/src/components/dialog.css b/packages/kilo-ui/src/components/dialog.css index a1306d92f77..cc2e29c7de3 100644 --- a/packages/kilo-ui/src/components/dialog.css +++ b/packages/kilo-ui/src/components/dialog.css @@ -20,4 +20,12 @@ [data-slot="dialog-title"] { font-weight: 600; } + + &[data-fit] [data-slot="dialog-container"] { + justify-content: center; + + [data-slot="dialog-content"] { + min-height: 0; + } + } } From a0f5b2aaf076317602148863541c37beb6a900de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:00:16 +0100 Subject: [PATCH 38/71] fix: force height:auto for fit dialogs with explicit unlayered CSS override --- packages/kilo-ui/src/components/dialog.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/kilo-ui/src/components/dialog.css b/packages/kilo-ui/src/components/dialog.css index cc2e29c7de3..d7825a86129 100644 --- a/packages/kilo-ui/src/components/dialog.css +++ b/packages/kilo-ui/src/components/dialog.css @@ -20,12 +20,12 @@ [data-slot="dialog-title"] { font-weight: 600; } +} - &[data-fit] [data-slot="dialog-container"] { - justify-content: center; +[data-component="dialog"][data-fit] [data-slot="dialog-container"] { + height: auto; +} - [data-slot="dialog-content"] { - min-height: 0; - } - } +[data-component="dialog"][data-fit] [data-slot="dialog-content"] { + min-height: 0; } From 9752eea6c9904f8c09cbcb4f2dad9320cae81a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:03:14 +0100 Subject: [PATCH 39/71] fix: rewrite CloudImportDialog to use standard Dialog pattern with TextField --- packages/kilo-ui/src/components/dialog.css | 8 --- .../src/components/chat/CloudImportDialog.tsx | 28 ++++----- .../webview-ui/src/styles/chat.css | 62 ------------------- 3 files changed, 12 insertions(+), 86 deletions(-) diff --git a/packages/kilo-ui/src/components/dialog.css b/packages/kilo-ui/src/components/dialog.css index d7825a86129..a1306d92f77 100644 --- a/packages/kilo-ui/src/components/dialog.css +++ b/packages/kilo-ui/src/components/dialog.css @@ -21,11 +21,3 @@ font-weight: 600; } } - -[data-component="dialog"][data-fit] [data-slot="dialog-container"] { - height: auto; -} - -[data-component="dialog"][data-fit] [data-slot="dialog-content"] { - min-height: 0; -} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx index 3d3dacaa15c..7ee3ce7a30a 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx @@ -1,14 +1,14 @@ import { Component, createSignal } from "solid-js" import { Dialog } from "@kilocode/kilo-ui/dialog" import { Button } from "@kilocode/kilo-ui/button" +import { TextField } from "@kilocode/kilo-ui/text-field" import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { useLanguage } from "../../context/language" const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i function extractSessionId(raw: string): string | null { - const trimmed = raw.trim() - const match = trimmed.match(UUID_RE) + const match = raw.trim().match(UUID_RE) return match ? match[0] : null } @@ -20,7 +20,7 @@ export const CloudImportDialog: Component = (props) => { const language = useLanguage() const dialog = useDialog() const [value, setValue] = createSignal("") - const [error, setError] = createSignal(null) + const [error, setError] = createSignal(undefined) function submit() { const id = extractSessionId(value()) @@ -33,24 +33,20 @@ export const CloudImportDialog: Component = (props) => { } return ( - -
- +
+ { - setValue(e.currentTarget.value) - setError(null) - }} - onKeyDown={(e) => { + onChange={setValue} + onKeyDown={(e: KeyboardEvent) => { if (e.key === "Enter") submit() }} - autofocus + validationState={error() ? "invalid" : undefined} + error={error()} /> - {error() && {error()}} -
+
diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 19299c31b44..0fb744a9bd6 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -164,68 +164,6 @@ flex-shrink: 0; } -/* ============================================ - Cloud Import Dialog - ============================================ */ - -.cloud-import-dialog { - display: flex; - flex-direction: column; - gap: 12px; - padding: 0 24px 12px 24px; -} - -.cloud-import-input { - width: 100%; - padding: 6px 8px; - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); - border: 1px solid var(--vscode-input-border, transparent); - border-radius: 4px; - font-family: inherit; - font-size: 13px; - box-sizing: border-box; -} - -.cloud-import-input:focus { - outline: none; - border-color: var(--vscode-focusBorder); -} - -.cloud-import-error { - font-size: 12px; - color: var(--vscode-errorForeground); -} - -.cloud-import-actions { - display: flex; - justify-content: flex-end; - gap: 8px; -} - -.scroll-to-bottom-button { - position: absolute; - bottom: 12px; - right: 12px; - width: 32px; - height: 32px; - border-radius: 50%; - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: none; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); - transition: background-color 0.2s; -} - -.scroll-to-bottom-button:hover { - background: var(--vscode-button-hoverBackground); -} - /* ============================================ Prompt Input ============================================ */ From 8a9d1dac49f8a636577d81726a508e6be3ea0114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:05:17 +0100 Subject: [PATCH 40/71] fix: flatten dialog CSS overrides and restore fit mode for import dialog --- packages/kilo-ui/src/components/dialog.css | 36 +++++++++++-------- .../src/components/chat/CloudImportDialog.tsx | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/kilo-ui/src/components/dialog.css b/packages/kilo-ui/src/components/dialog.css index a1306d92f77..1e25d1decc8 100644 --- a/packages/kilo-ui/src/components/dialog.css +++ b/packages/kilo-ui/src/components/dialog.css @@ -5,19 +5,25 @@ background-color: rgba(0, 0, 0, 0.5); } -[data-component="dialog"] { - [data-slot="dialog-content"] { - border-radius: 2px; - max-width: 512px; - background-color: var(--background-base); - border: 1px solid var(--border-weak-base); - } - - [data-slot="dialog-header"] { - font-size: 14px; - } - - [data-slot="dialog-title"] { - font-weight: 600; - } +[data-component="dialog"] [data-slot="dialog-content"] { + border-radius: 2px; + max-width: 512px; + background-color: var(--background-base); + border: 1px solid var(--border-weak-base); +} + +[data-component="dialog"] [data-slot="dialog-header"] { + font-size: 14px; +} + +[data-component="dialog"] [data-slot="dialog-title"] { + font-weight: 600; +} + +[data-component="dialog"][data-fit] [data-slot="dialog-container"] { + height: auto; +} + +[data-component="dialog"][data-fit] [data-slot="dialog-content"] { + min-height: 0; } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx index 7ee3ce7a30a..28f1d9dc169 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx @@ -33,7 +33,7 @@ export const CloudImportDialog: Component = (props) => { } return ( - +
Date: Mon, 23 Feb 2026 20:07:40 +0100 Subject: [PATCH 41/71] fix: use border-color for input focus instead of box-shadow --- packages/kilo-ui/src/components/text-field.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kilo-ui/src/components/text-field.css b/packages/kilo-ui/src/components/text-field.css index 4b9d18e4321..12be2757517 100644 --- a/packages/kilo-ui/src/components/text-field.css +++ b/packages/kilo-ui/src/components/text-field.css @@ -6,7 +6,8 @@ border-radius: 2px; &:focus-within:not(:has([data-readonly])) { - box-shadow: 0 0 0 1px var(--border-focus); + border-color: var(--border-focus); + box-shadow: none; } } From 528a68df4eb55719f27bff29bdf89f1abf8739d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:11:59 +0100 Subject: [PATCH 42/71] fix: center fit dialog content horizontally with align-self center --- packages/kilo-ui/src/components/dialog.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-ui/src/components/dialog.css b/packages/kilo-ui/src/components/dialog.css index 1e25d1decc8..a9b71631ea5 100644 --- a/packages/kilo-ui/src/components/dialog.css +++ b/packages/kilo-ui/src/components/dialog.css @@ -26,4 +26,5 @@ [data-component="dialog"][data-fit] [data-slot="dialog-content"] { min-height: 0; + align-self: center; } From 8d83b3c35697b37547cd32493aef0fef9002eea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:30:53 +0100 Subject: [PATCH 43/71] fix: address review feedback on cloud sessions feature - Use kilo-ui Checkbox instead of raw in CloudSessionList - Replace `any` types in KiloRoutesDeps with structural types - Inject Identifier dep instead of duplicating ID generation logic - Delete unused fetchCloudSessions() in api/sessions.ts - Add Array.isArray guard on data.messages in import endpoint - Avoid mutating data.info in-place during cloud session import - Translate session.cloud.import.* i18n keys across all 15 locales - Remove obsolete checkbox CSS (now handled by kilo-ui) --- packages/kilo-gateway/src/api/sessions.ts | 65 ---------------- packages/kilo-gateway/src/server/routes.ts | 78 ++++++++++--------- .../components/history/CloudSessionList.tsx | 8 +- .../kilo-vscode/webview-ui/src/i18n/ar.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/br.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/bs.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/da.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/de.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/es.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/fr.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/ja.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/ko.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/no.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/pl.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/ru.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/th.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/zh.ts | 10 +-- .../kilo-vscode/webview-ui/src/i18n/zht.ts | 10 +-- .../webview-ui/src/styles/chat.css | 13 ---- packages/opencode/src/server/server.ts | 2 + 20 files changed, 124 insertions(+), 192 deletions(-) delete mode 100644 packages/kilo-gateway/src/api/sessions.ts diff --git a/packages/kilo-gateway/src/api/sessions.ts b/packages/kilo-gateway/src/api/sessions.ts deleted file mode 100644 index c493d44b088..00000000000 --- a/packages/kilo-gateway/src/api/sessions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { KILO_API_BASE } from "./constants.js" -import { buildKiloHeaders } from "../headers.js" - -export interface CloudSession { - session_id: string - title: string | null - cloud_agent_session_id: string | null - created_at: string - updated_at: string - version: number -} - -export interface CloudSessionsResponse { - cliSessions: CloudSession[] - nextCursor: string | null -} - -export async function fetchCloudSessions( - token: string, - params?: { - cursor?: string - limit?: number - gitUrl?: string - }, -): Promise { - const input: Record = {} - if (params?.cursor !== undefined) input.cursor = params.cursor - if (params?.limit !== undefined) input.limit = params.limit - if (params?.gitUrl !== undefined) input.gitUrl = params.gitUrl - - const query = new URLSearchParams({ - batch: "1", - input: JSON.stringify({ "0": input }), - }) - - const response = await fetch(`${KILO_API_BASE}/api/trpc/cliSessionsV2.list?${query.toString()}`, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - ...buildKiloHeaders(), - }, - }) - - if (!response.ok) { - const text = await response.text() - throw new Error(`Failed to fetch cloud sessions: ${response.status} ${text}`) - } - - const json = (await response.json()) as unknown[] - const data = (json[0] as any)?.result?.data - const result = data?.json ?? data - - if (!result) return { cliSessions: [], nextCursor: null } - - const sessions = (result.cliSessions ?? []).map((s: any) => ({ - session_id: s.session_id, - title: s.title ?? null, - cloud_agent_session_id: s.cloud_agent_session_id ?? null, - created_at: typeof s.created_at === "string" ? s.created_at : new Date(s.created_at).toISOString(), - updated_at: typeof s.updated_at === "string" ? s.updated_at : new Date(s.updated_at).toISOString(), - version: s.version ?? 0, - })) - - return { cliSessions: sessions, nextCursor: result.nextCursor ?? null } -} diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index ac1f103e204..54c07d8cecf 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -6,7 +6,6 @@ * This factory function accepts OpenCode dependencies to create Kilo-specific routes */ -import { randomBytes } from "crypto" import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE @@ -21,6 +20,10 @@ type Errors = any type Auth = any type Z = any +interface DrizzleDb { + insert(table: object): { values(data: object): { onConflictDoNothing(): { run(): void } } } +} + interface KiloRoutesDeps { Hono: new () => Hono describeRoute: DescribeRoute @@ -29,14 +32,24 @@ interface KiloRoutesDeps { errors: Errors Auth: Auth z: Z - Database: any - Instance: any - SessionTable: any - MessageTable: any - PartTable: any - SessionToRow: (info: any) => any - Bus: { publish: (event: any, payload: any) => void } - SessionCreatedEvent: any + Database: { + transaction(callback: (db: DrizzleDb) => T): T + effect(fn: () => void | Promise): void + } + Instance: { + readonly directory: string + readonly project: { readonly id: string } + } + SessionTable: object + MessageTable: object + PartTable: object + SessionToRow: (info: Record) => Record + Bus: { publish(event: { type: string; properties: unknown }, payload: unknown): void | Promise } + SessionCreatedEvent: { type: string; properties: unknown } + Identifier: { + ascending(prefix: "session" | "message" | "part", given?: string): string + descending(prefix: "session" | "message" | "part", given?: string): string + } } /** @@ -62,19 +75,6 @@ interface KiloRoutesDeps { * }) * ``` */ -let counter = 0 -function generateId(prefix: string, descending: boolean): string { - const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - const bytes = randomBytes(14) - let rand = "" - for (let i = 0; i < 14; i++) rand += chars[bytes[i] % 62] - let now = BigInt(Date.now()) * BigInt(0x1000) + BigInt(counter++) - if (descending) now = ~now - const buf = Buffer.alloc(6) - for (let i = 0; i < 6; i++) buf[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) - return prefix + "_" + buf.toString("hex") + rand -} - export function createKiloRoutes(deps: KiloRoutesDeps) { const { Hono, @@ -92,6 +92,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { SessionToRow, Bus, SessionCreatedEvent, + Identifier, } = deps const Organization = z.object({ @@ -413,21 +414,28 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const data = (await response.json()) as any if (!data?.info?.id) return c.json({ error: "Invalid export data" }, 400) - const localSessionID = generateId("ses", true) + const localSessionID = Identifier.descending("session") const msgMap = new Map() const projectID = Instance.project.id - data.info.id = localSessionID - data.info.projectID = projectID - data.info.slug = data.info.slug || "imported" - data.info.directory = data.info.directory || Instance.directory || "." - data.info.version = data.info.version || "2" + const info = { + ...data.info, + id: localSessionID, + projectID, + slug: data.info.slug || "imported", + directory: data.info.directory || Instance.directory || ".", + version: data.info.version || "2", + } - Database.transaction((db: any) => { - db.insert(SessionTable).values(SessionToRow(data.info)).onConflictDoNothing().run() + Database.transaction((db) => { + db.insert(SessionTable) + .values(SessionToRow(info as Record)) + .onConflictDoNothing() + .run() - for (const msg of data.messages ?? []) { - const msgID = generateId("msg", false) + const messages = Array.isArray(data.messages) ? data.messages : [] + for (const msg of messages) { + const msgID = Identifier.ascending("message") msgMap.set(msg.info.id, msgID) msg.info.id = msgID msg.info.sessionID = localSessionID @@ -444,7 +452,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { .run() for (const part of msg.parts ?? []) { - const partID = generateId("prt", false) + const partID = Identifier.ascending("part") part.id = partID part.messageID = msgID part.sessionID = localSessionID @@ -461,10 +469,10 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { } } - Database.effect(() => Bus.publish(SessionCreatedEvent, { info: data.info })) + Database.effect(() => Bus.publish(SessionCreatedEvent, { info })) }) - return c.json(data.info) + return c.json(info) }, ) .get( diff --git a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx index f2e1a4030f8..c2c8bdfe117 100644 --- a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx @@ -7,6 +7,7 @@ import { Component, createSignal, createEffect, onMount, onCleanup, Show } from "solid-js" import { List } from "@kilocode/kilo-ui/list" import { Button } from "@kilocode/kilo-ui/button" +import { Checkbox } from "@kilocode/kilo-ui/checkbox" import { useVSCode } from "../../context/vscode" import { useLanguage } from "../../context/language" import { formatRelativeDate } from "../../utils/date" @@ -116,10 +117,9 @@ const CloudSessionList: Component = (props) => { return (
- + + {language.t("session.cloud.repoOnly") ?? "Only this repository"} +
items={sessions()} diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 2b76b059b39..91772e10360 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -689,11 +689,11 @@ export const dict = { "session.search.placeholder": "البحث في الجلسات...", "session.empty": "لا توجد جلسات بعد. انقر + لبدء محادثة جديدة.", "session.cloud.repoOnly": "هذا المستودع فقط", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "استيراد من السحابة", + "session.cloud.import.title": "استيراد من السحابة", + "session.cloud.import.placeholder": "معرّف الجلسة أو الرابط أو أمر kilo import", + "session.cloud.import.button": "استيراد", + "session.cloud.import.invalid": "تنسيق معرّف الجلسة غير صالح", "workspace.new": "مساحة عمل جديدة", "workspace.type.local": "محلي", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index b11ab55a5cf..209d1fb5896 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -696,11 +696,11 @@ export const dict = { "session.search.placeholder": "Buscar sessões...", "session.empty": "Nenhuma sessão ainda. Clique + para iniciar uma nova conversa.", "session.cloud.repoOnly": "Apenas este repositório", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Importar da nuvem", + "session.cloud.import.title": "Importar da nuvem", + "session.cloud.import.placeholder": "ID da sessão, URL ou comando kilo import", + "session.cloud.import.button": "Importar", + "session.cloud.import.invalid": "Formato de ID de sessão inválido", "workspace.new": "Novo espaço de trabalho", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 4e1a676fe20..326800f310a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -723,11 +723,11 @@ export const dict = { "session.search.placeholder": "Pretraži sesije...", "session.empty": "Još nema sesija. Kliknite + za početak novog razgovora.", "session.cloud.repoOnly": "Samo ovaj repozitorij", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Uvezi iz oblaka", + "session.cloud.import.title": "Uvezi iz oblaka", + "session.cloud.import.placeholder": "ID sesije, URL ili kilo import naredba", + "session.cloud.import.button": "Uvezi", + "session.cloud.import.invalid": "Neispravan format ID-a sesije", "workspace.new": "Novi radni prostor", "workspace.type.local": "lokalno", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index a9b993a350c..b2e50c148bf 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -695,11 +695,11 @@ export const dict = { "session.search.placeholder": "Søg sessioner...", "session.empty": "Ingen sessioner endnu. Klik + for at starte en ny samtale.", "session.cloud.repoOnly": "Kun dette repository", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Importér fra skyen", + "session.cloud.import.title": "Importér fra skyen", + "session.cloud.import.placeholder": "Sessions-ID, URL eller kilo import-kommando", + "session.cloud.import.button": "Importér", + "session.cloud.import.invalid": "Ugyldigt sessions-ID-format", "workspace.new": "Nyt arbejdsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index b20aa38b994..2cfd08410d8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -701,11 +701,11 @@ export const dict = { "session.search.placeholder": "Sitzungen suchen...", "session.empty": "Noch keine Sitzungen. Klicke + um eine neue Unterhaltung zu starten.", "session.cloud.repoOnly": "Nur dieses Repository", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Aus der Cloud importieren", + "session.cloud.import.title": "Aus der Cloud importieren", + "session.cloud.import.placeholder": "Sitzungs-ID, URL oder kilo import-Befehl", + "session.cloud.import.button": "Importieren", + "session.cloud.import.invalid": "Ungültiges Sitzungs-ID-Format", "workspace.new": "Neuer Arbeitsbereich", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 50e6fc8fff4..f576a3666b2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -701,11 +701,11 @@ export const dict = { "session.search.placeholder": "Buscar sesiones...", "session.empty": "Aún no hay sesiones. Haz clic en + para iniciar una nueva conversación.", "session.cloud.repoOnly": "Solo este repositorio", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Importar desde la nube", + "session.cloud.import.title": "Importar desde la nube", + "session.cloud.import.placeholder": "ID de sesión, URL o comando kilo import", + "session.cloud.import.button": "Importar", + "session.cloud.import.invalid": "Formato de ID de sesión no válido", "workspace.new": "Nuevo espacio de trabajo", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 3356e853232..e68f04cafe8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -705,11 +705,11 @@ export const dict = { "session.search.placeholder": "Rechercher des sessions...", "session.empty": "Aucune session pour l'instant. Cliquez + pour démarrer une nouvelle conversation.", "session.cloud.repoOnly": "Uniquement ce dépôt", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Importer depuis le cloud", + "session.cloud.import.title": "Importer depuis le cloud", + "session.cloud.import.placeholder": "ID de session, URL ou commande kilo import", + "session.cloud.import.button": "Importer", + "session.cloud.import.invalid": "Format d'ID de session invalide", "workspace.new": "Nouvel espace de travail", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index bac79a491ac..d82c9c20162 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -689,11 +689,11 @@ export const dict = { "session.search.placeholder": "セッションを検索...", "session.empty": "セッションがありません。+ をクリックして新しい会話を始めましょう。", "session.cloud.repoOnly": "このリポジトリのみ", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "クラウドからインポート", + "session.cloud.import.title": "クラウドからインポート", + "session.cloud.import.placeholder": "セッションID、URL、またはkilo importコマンド", + "session.cloud.import.button": "インポート", + "session.cloud.import.invalid": "セッションIDの形式が無効です", "workspace.new": "新しいワークスペース", "workspace.type.local": "ローカル", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 4fac3938cf1..1b994e1ee19 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -694,11 +694,11 @@ export const dict = { "session.search.placeholder": "세션 검색...", "session.empty": "아직 세션이 없습니다. +를 클릭하여 새 대화를 시작하세요.", "session.cloud.repoOnly": "이 저장소만", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "클라우드에서 가져오기", + "session.cloud.import.title": "클라우드에서 가져오기", + "session.cloud.import.placeholder": "세션 ID, URL 또는 kilo import 명령어", + "session.cloud.import.button": "가져오기", + "session.cloud.import.invalid": "잘못된 세션 ID 형식입니다", "workspace.new": "새 작업 공간", "workspace.type.local": "로컬", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 772029395d6..3d1c8e2e857 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -698,11 +698,11 @@ export const dict = { "session.search.placeholder": "Søk i sesjoner...", "session.empty": "Ingen sesjoner ennå. Klikk + for å starte en ny samtale.", "session.cloud.repoOnly": "Kun dette repositoriet", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Importer fra skyen", + "session.cloud.import.title": "Importer fra skyen", + "session.cloud.import.placeholder": "Økt-ID, URL eller kilo import-kommando", + "session.cloud.import.button": "Importer", + "session.cloud.import.invalid": "Ugyldig økt-ID-format", "workspace.new": "Nytt arbeidsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 89f46fef446..6c5a1d14a84 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -697,11 +697,11 @@ export const dict = { "session.search.placeholder": "Szukaj sesji...", "session.empty": "Brak sesji. Kliknij + aby rozpocząć nową rozmowę.", "session.cloud.repoOnly": "Tylko to repozytorium", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Importuj z chmury", + "session.cloud.import.title": "Importuj z chmury", + "session.cloud.import.placeholder": "ID sesji, URL lub polecenie kilo import", + "session.cloud.import.button": "Importuj", + "session.cloud.import.invalid": "Nieprawidłowy format ID sesji", "workspace.new": "Nowa przestrzeń robocza", "workspace.type.local": "lokalna", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index b878ce79ff7..25ef092602e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -701,11 +701,11 @@ export const dict = { "session.search.placeholder": "Поиск сессий...", "session.empty": "Сессий пока нет. Нажмите + чтобы начать новый разговор.", "session.cloud.repoOnly": "Только этот репозиторий", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "Импорт из облака", + "session.cloud.import.title": "Импорт из облака", + "session.cloud.import.placeholder": "ID сессии, URL или команда kilo import", + "session.cloud.import.button": "Импортировать", + "session.cloud.import.invalid": "Неверный формат ID сессии", "workspace.new": "Новое рабочее пространство", "workspace.type.local": "локальное", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 37154a4fe70..7ab38ee93b5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -687,11 +687,11 @@ export const dict = { "session.search.placeholder": "ค้นหาเซสชัน...", "session.empty": "ยังไม่มีเซสชัน คลิก + เพื่อเริ่มการสนทนาใหม่", "session.cloud.repoOnly": "เฉพาะรีโพซิทอรีนี้", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "นำเข้าจากคลาวด์", + "session.cloud.import.title": "นำเข้าจากคลาวด์", + "session.cloud.import.placeholder": "ID เซสชัน, URL หรือคำสั่ง kilo import", + "session.cloud.import.button": "นำเข้า", + "session.cloud.import.invalid": "รูปแบบ ID เซสชันไม่ถูกต้อง", "workspace.new": "พื้นที่ทำงานใหม่", "workspace.type.local": "ในเครื่อง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index ecb28418669..7266854eeb5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -690,11 +690,11 @@ export const dict = { "session.search.placeholder": "搜索会话...", "session.empty": "暂无会话。点击 + 开始新的对话。", "session.cloud.repoOnly": "仅此仓库", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "从云端导入", + "session.cloud.import.title": "从云端导入", + "session.cloud.import.placeholder": "会话 ID、URL 或 kilo import 命令", + "session.cloud.import.button": "导入", + "session.cloud.import.invalid": "会话 ID 格式无效", "workspace.new": "新建工作区", "workspace.type.local": "本地", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 8d3bdff5b05..22053eb7e2e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -687,11 +687,11 @@ export const dict = { "session.search.placeholder": "搜尋工作階段...", "session.empty": "尚無工作階段。點擊 + 開始新的對話。", "session.cloud.repoOnly": "僅此儲存庫", - "session.cloud.import": "Import from cloud", - "session.cloud.import.title": "Import from cloud", - "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", - "session.cloud.import.button": "Import", - "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import": "從雲端匯入", + "session.cloud.import.title": "從雲端匯入", + "session.cloud.import.placeholder": "工作階段 ID、URL 或 kilo import 指令", + "session.cloud.import.button": "匯入", + "session.cloud.import.invalid": "工作階段 ID 格式無效", "workspace.new": "新增工作區", "workspace.type.local": "本地", diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index 0fb744a9bd6..f30dfba1e21 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -755,19 +755,6 @@ border-bottom: 1px solid var(--vscode-panel-border); } -.cloud-session-checkbox { - display: flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: var(--vscode-foreground); - cursor: pointer; -} - -.cloud-session-checkbox input[type="checkbox"] { - accent-color: var(--vscode-focusBorder); -} - .cloud-session-load-more { flex-shrink: 0; display: flex; diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index c4ee7d38017..a14c7b2084c 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -33,6 +33,7 @@ import { ProviderRoutes } from "./routes/provider" import { createKiloRoutes } from "@kilocode/kilo-gateway" // kilocode_change import { Database } from "../storage/db" // kilocode_change import { Session } from "../session" // kilocode_change +import { Identifier } from "../id/id" // kilocode_change import { SessionTable, MessageTable, PartTable } from "../session/session.sql" // kilocode_change import { lazy } from "../util/lazy" import { InstanceBootstrap } from "../project/bootstrap" @@ -259,6 +260,7 @@ export namespace Server { SessionToRow: Session.toRow, // kilocode_change Bus, // kilocode_change SessionCreatedEvent: Session.Event.Created, // kilocode_change + Identifier, // kilocode_change }), ) // kilocode_change end From d31c3c0ed673134a29267182b4ceb816b557828b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:35:20 +0100 Subject: [PATCH 44/71] fix: address bot review comments on cloud sessions - Validate sessionId and id params as UUID to prevent SSRF (routes.ts) - Ensure info.time exists with fallback before SessionToRow (routes.ts) - Use ?? instead of || for slug/directory/version defaults (routes.ts) - Strip upstream error body from client-facing /cloud-sessions response - Add safe date fallback for null/invalid created_at/updated_at values - Fix pagination race condition with generation counter (CloudSessionList) - Show toast on cloud session import failure (session.tsx) - Add try/catch + .filter(m => m.info) in handleRequestCloudSessionData --- packages/kilo-gateway/src/server/routes.ts | 35 +++++++++--- packages/kilo-vscode/src/KiloProvider.ts | 53 +++++++++++-------- .../components/history/CloudSessionList.tsx | 12 ++++- .../webview-ui/src/context/session.tsx | 2 + 4 files changed, 71 insertions(+), 31 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 54c07d8cecf..bf117ebb240 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -338,7 +338,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { ...errors(401, 404), }, }), - validator("param", z.object({ id: z.string() })), + validator("param", z.object({ id: z.string().uuid() })), async (c: any) => { const auth = await Auth.get("kilo") if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) @@ -382,7 +382,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { validator( "json", z.object({ - sessionId: z.string(), + sessionId: z.string().uuid(), }), ), async (c: any) => { @@ -418,13 +418,22 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const msgMap = new Map() const projectID = Instance.project.id + const now = Date.now() + const time = { + created: data.info.time?.created ?? now, + updated: data.info.time?.updated ?? now, + ...(data.info.time?.compacting !== undefined && { compacting: data.info.time.compacting }), + ...(data.info.time?.archived !== undefined && { archived: data.info.time.archived }), + } + const info = { ...data.info, id: localSessionID, projectID, - slug: data.info.slug || "imported", - directory: data.info.directory || Instance.directory || ".", - version: data.info.version || "2", + slug: data.info.slug ?? "imported", + directory: data.info.directory ?? Instance.directory ?? ".", + version: data.info.version ?? 2, + time, } Database.transaction((db) => { @@ -545,7 +554,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { status: response.status, body: text.slice(0, 500), }) - return c.json({ error: `Cloud sessions fetch failed: ${response.status} ${text}` }, response.status as any) + return c.json({ error: `Cloud sessions fetch failed: ${response.status}` }, response.status as any) } const raw = await response.text() @@ -558,8 +567,18 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { session_id: s.session_id, title: s.title ?? null, cloud_agent_session_id: s.cloud_agent_session_id ?? null, - created_at: typeof s.created_at === "string" ? s.created_at : new Date(s.created_at).toISOString(), - updated_at: typeof s.updated_at === "string" ? s.updated_at : new Date(s.updated_at).toISOString(), + created_at: + typeof s.created_at === "string" + ? s.created_at + : s.created_at + ? new Date(s.created_at).toISOString() + : new Date().toISOString(), + updated_at: + typeof s.updated_at === "string" + ? s.updated_at + : s.updated_at + ? new Date(s.updated_at).toISOString() + : new Date().toISOString(), version: s.version ?? 0, })) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 64bf2bd72e1..7f30dda0de8 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1037,32 +1037,43 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return } - const data = await this.httpClient.getCloudSession(sessionId) - if (!data) { + try { + const data = await this.httpClient.getCloudSession(sessionId) + if (!data) { + this.postMessage({ + type: "cloudSessionImportFailed", + cloudSessionId: sessionId, + error: "Failed to fetch cloud session", + }) + return + } + + const messages = (data.messages ?? []) + .filter((m) => m.info) + .map((m) => ({ + id: m.info.id, + sessionID: m.info.sessionID, + role: m.info.role as "user" | "assistant", + parts: m.parts, + createdAt: m.info.time?.created ? new Date(m.info.time.created).toISOString() : new Date().toISOString(), + cost: m.info.cost, + tokens: m.info.tokens, + })) + + this.postMessage({ + type: "cloudSessionDataLoaded", + cloudSessionId: sessionId, + title: data.info.title ?? "Untitled", + messages, + }) + } catch (err) { + console.error("[Kilo New] Failed to load cloud session data:", err) this.postMessage({ type: "cloudSessionImportFailed", cloudSessionId: sessionId, - error: "Failed to fetch cloud session", + error: err instanceof Error ? err.message : "Failed to load cloud session", }) - return } - - const messages = (data.messages ?? []).map((m) => ({ - id: m.info.id, - sessionID: m.info.sessionID, - role: m.info.role as "user" | "assistant", - parts: m.parts, - createdAt: m.info.time?.created ? new Date(m.info.time.created).toISOString() : new Date().toISOString(), - cost: m.info.cost, - tokens: m.info.tokens, - })) - - this.postMessage({ - type: "cloudSessionDataLoaded", - cloudSessionId: sessionId, - title: data.info.title ?? "Untitled", - messages, - }) } /** diff --git a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx index c2c8bdfe117..3a236be5d54 100644 --- a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx @@ -62,15 +62,20 @@ const CloudSessionList: Component = (props) => { const [repoOnly, setRepoOnly] = createSignal(true) const [initialized, setInitialized] = createSignal(false) + let loadGen = 0 + let activeGen = 0 + const unsub = vscode.onMessage((message: ExtensionMessage) => { if (message.type === "cloudSessionsLoaded") { + if (activeGen !== loadGen) return const incoming = message.sessions.map(toDisplay) - if (nextCursor() && incoming.length > 0) { + const cursor = nextCursor() + if (cursor && incoming.length > 0) { setSessions((prev) => { const seen = new Set(prev.map((s) => s.id)) return [...prev, ...incoming.filter((s) => !seen.has(s.id))] }) - } else if (!nextCursor()) { + } else if (!cursor) { setSessions(incoming) } setNextCursor(message.nextCursor) @@ -91,6 +96,8 @@ const CloudSessionList: Component = (props) => { createEffect(() => { if (!initialized()) return const url = repoOnly() ? gitUrl() : undefined + loadGen++ + activeGen = loadGen setLoading(true) setSessions([]) setNextCursor(null) @@ -105,6 +112,7 @@ const CloudSessionList: Component = (props) => { const cursor = nextCursor() if (!cursor || loading()) return const url = repoOnly() ? gitUrl() : undefined + activeGen = loadGen setLoading(true) vscode.postMessage({ type: "requestCloudSessions", diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 53881671d65..97e0181d176 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -21,6 +21,7 @@ import { useVSCode } from "./vscode" import { useServer } from "./server" import { useProvider } from "./provider" import { useLanguage } from "./language" +import { showToast } from "@kilocode/kilo-ui/toast" import type { SessionInfo, Message, @@ -395,6 +396,7 @@ export const SessionProvider: ParentComponent = (props) => { case "cloudSessionImportFailed": setCloudPreviewId(null) setLoading(false) + showToast({ variant: "error", title: "Failed to import cloud session", description: message.error }) console.error("[Kilo New] Cloud session import failed:", message.error) break } From 6c9705ee10d4fa8f29557720bdba3be2b979c37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:43:13 +0100 Subject: [PATCH 45/71] fix: restore scroll-to-bottom-button CSS accidentally removed in cloud sessions changes --- .../webview-ui/src/styles/chat.css | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index f30dfba1e21..cbd3dad19ed 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -164,6 +164,29 @@ flex-shrink: 0; } +.scroll-to-bottom-button { + position: absolute; + bottom: 12px; + right: 12px; + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + transition: background-color 0.2s; +} + +.scroll-to-bottom-button:hover { + background: var(--vscode-button-hoverBackground); +} + /* ============================================ Prompt Input ============================================ */ From fc3c08aae43297c36b631bf912502c384087087c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 20:46:07 +0100 Subject: [PATCH 46/71] fix(kilo-ui): remove excessive padding between checkbox and label --- packages/kilo-ui/src/components/checkbox.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kilo-ui/src/components/checkbox.css b/packages/kilo-ui/src/components/checkbox.css index a39f431419d..45e7d1b21cf 100644 --- a/packages/kilo-ui/src/components/checkbox.css +++ b/packages/kilo-ui/src/components/checkbox.css @@ -23,7 +23,5 @@ [data-slot="checkbox-checkbox-label"] { font-size: 13px; line-height: normal; - padding-inline-start: 10px; - margin-inline-end: 10px; } } From 1ce31e1da32c42114ed3e786e29a36c79f592f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 21:04:58 +0100 Subject: [PATCH 47/71] fix(kilo-gateway): remove fallback defaults for imported session fields Remove hardcoded fallback values for `slug`, `directory`, and `version` when importing session data, allowing undefined values to propagate naturally instead of masking missing data with defaults. --- packages/kilo-gateway/src/server/routes.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index bf117ebb240..79a62b76dd7 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -430,9 +430,9 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { ...data.info, id: localSessionID, projectID, - slug: data.info.slug ?? "imported", - directory: data.info.directory ?? Instance.directory ?? ".", - version: data.info.version ?? 2, + slug: data.info.slug, + directory: data.info.directory ?? Instance.directory, + version: data.info.version, time, } From f8f150065d6892ccca4bc97703f47f0e02b15579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 21:15:07 +0100 Subject: [PATCH 48/71] fix: null-check msg.info in import transaction and revert unrelated UI changes --- packages/kilo-gateway/src/server/routes.ts | 2 +- packages/kilo-ui/src/components/checkbox.css | 2 ++ packages/kilo-ui/src/components/dialog.css | 24 ++++++++++--------- .../kilo-ui/src/components/text-field.css | 3 +-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 79a62b76dd7..c20c53d3ddd 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -443,7 +443,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { .run() const messages = Array.isArray(data.messages) ? data.messages : [] - for (const msg of messages) { + for (const msg of messages.filter((m: any) => m.info)) { const msgID = Identifier.ascending("message") msgMap.set(msg.info.id, msgID) msg.info.id = msgID diff --git a/packages/kilo-ui/src/components/checkbox.css b/packages/kilo-ui/src/components/checkbox.css index 45e7d1b21cf..a39f431419d 100644 --- a/packages/kilo-ui/src/components/checkbox.css +++ b/packages/kilo-ui/src/components/checkbox.css @@ -23,5 +23,7 @@ [data-slot="checkbox-checkbox-label"] { font-size: 13px; line-height: normal; + padding-inline-start: 10px; + margin-inline-end: 10px; } } diff --git a/packages/kilo-ui/src/components/dialog.css b/packages/kilo-ui/src/components/dialog.css index a9b71631ea5..e571f5cc7f8 100644 --- a/packages/kilo-ui/src/components/dialog.css +++ b/packages/kilo-ui/src/components/dialog.css @@ -5,19 +5,21 @@ background-color: rgba(0, 0, 0, 0.5); } -[data-component="dialog"] [data-slot="dialog-content"] { - border-radius: 2px; - max-width: 512px; - background-color: var(--background-base); - border: 1px solid var(--border-weak-base); -} +[data-component="dialog"] { + [data-slot="dialog-content"] { + border-radius: 2px; + max-width: 512px; + background-color: var(--background-base); + border: 1px solid var(--border-weak-base); + } -[data-component="dialog"] [data-slot="dialog-header"] { - font-size: 14px; -} + [data-slot="dialog-header"] { + font-size: 14px; + } -[data-component="dialog"] [data-slot="dialog-title"] { - font-weight: 600; + [data-slot="dialog-title"] { + font-weight: 600; + } } [data-component="dialog"][data-fit] [data-slot="dialog-container"] { diff --git a/packages/kilo-ui/src/components/text-field.css b/packages/kilo-ui/src/components/text-field.css index 12be2757517..4b9d18e4321 100644 --- a/packages/kilo-ui/src/components/text-field.css +++ b/packages/kilo-ui/src/components/text-field.css @@ -6,8 +6,7 @@ border-radius: 2px; &:focus-within:not(:has([data-readonly])) { - border-color: var(--border-focus); - box-shadow: none; + box-shadow: 0 0 0 1px var(--border-focus); } } From bd72dc17cd3f40c2f56e793ee269c90451c86d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 21:19:31 +0100 Subject: [PATCH 49/71] fix: add error handling and consistent ?? operator in cloud session routes --- packages/kilo-gateway/src/server/routes.ts | 192 +++++++++++---------- 1 file changed, 101 insertions(+), 91 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index c20c53d3ddd..63f493129c7 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -340,25 +340,30 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { }), validator("param", z.object({ id: z.string().uuid() })), async (c: any) => { - const auth = await Auth.get("kilo") - if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) - const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined - if (!token) return c.json({ error: "No valid token found" }, 401) + try { + const auth = await Auth.get("kilo") + if (!auth) return c.json({ error: "Not authenticated with Kilo Gateway" }, 401) + const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined + if (!token) return c.json({ error: "No valid token found" }, 401) - const { id } = c.req.valid("param") - const base = process.env.KILO_SESSION_INGEST_URL || "https://ingest.kilosessions.ai" - const response = await fetch(`${base}/api/session/${id}/export`, { - headers: { - Authorization: `Bearer ${token}`, - ...buildKiloHeaders(), - }, - }) + const { id } = c.req.valid("param") + const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" + const response = await fetch(`${base}/api/session/${id}/export`, { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, + }) - if (response.status === 404) return c.json({ error: "Session not found" }, 404) - if (!response.ok) return c.json({ error: "Failed to fetch session" }, response.status) + if (response.status === 404) return c.json({ error: "Session not found" }, 404) + if (!response.ok) return c.json({ error: "Failed to fetch session" }, response.status) - const data = await response.json() - return c.json(data) + const data = await response.json() + return c.json(data) + } catch (err: any) { + console.error("[Kilo Gateway] cloud/session/get: unhandled error", err?.message ?? err) + return c.json({ error: "Internal error" }, 500) + } }, ) .post( @@ -386,102 +391,107 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { }), ), async (c: any) => { - const { sessionId } = c.req.valid("json") + try { + const { sessionId } = c.req.valid("json") - const auth = await Auth.get("kilo") - if (!auth) return c.json({ error: "Not authenticated with Kilo" }, 401) - const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined - if (!token) return c.json({ error: "No valid token found" }, 401) + const auth = await Auth.get("kilo") + if (!auth) return c.json({ error: "Not authenticated with Kilo" }, 401) + const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined + if (!token) return c.json({ error: "No valid token found" }, 401) - const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" - const response = await fetch(`${base}/api/session/${sessionId}/export`, { - headers: { - Authorization: `Bearer ${token}`, - ...buildKiloHeaders(), - }, - }) - - if (response.status === 404) return c.json({ error: "Session not found in cloud" }, 404) - if (!response.ok) { - const text = await response.text() - console.error("[Kilo Gateway] cloud/session/import: export failed", { - status: response.status, - body: text.slice(0, 500), + const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" + const response = await fetch(`${base}/api/session/${sessionId}/export`, { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, }) - return c.json({ error: `Import failed: ${response.status}` }, response.status as any) - } - const data = (await response.json()) as any - if (!data?.info?.id) return c.json({ error: "Invalid export data" }, 400) + if (response.status === 404) return c.json({ error: "Session not found in cloud" }, 404) + if (!response.ok) { + const text = await response.text() + console.error("[Kilo Gateway] cloud/session/import: export failed", { + status: response.status, + body: text.slice(0, 500), + }) + return c.json({ error: `Import failed: ${response.status}` }, response.status as any) + } - const localSessionID = Identifier.descending("session") - const msgMap = new Map() - const projectID = Instance.project.id + const data = (await response.json()) as any + if (!data?.info?.id) return c.json({ error: "Invalid export data" }, 400) - const now = Date.now() - const time = { - created: data.info.time?.created ?? now, - updated: data.info.time?.updated ?? now, - ...(data.info.time?.compacting !== undefined && { compacting: data.info.time.compacting }), - ...(data.info.time?.archived !== undefined && { archived: data.info.time.archived }), - } + const localSessionID = Identifier.descending("session") + const msgMap = new Map() + const projectID = Instance.project.id - const info = { - ...data.info, - id: localSessionID, - projectID, - slug: data.info.slug, - directory: data.info.directory ?? Instance.directory, - version: data.info.version, - time, - } + const now = Date.now() + const time = { + created: data.info.time?.created ?? now, + updated: data.info.time?.updated ?? now, + ...(data.info.time?.compacting !== undefined && { compacting: data.info.time.compacting }), + ...(data.info.time?.archived !== undefined && { archived: data.info.time.archived }), + } - Database.transaction((db) => { - db.insert(SessionTable) - .values(SessionToRow(info as Record)) - .onConflictDoNothing() - .run() + const info = { + ...data.info, + id: localSessionID, + projectID, + slug: data.info.slug, + directory: data.info.directory ?? Instance.directory, + version: data.info.version, + time, + } - const messages = Array.isArray(data.messages) ? data.messages : [] - for (const msg of messages.filter((m: any) => m.info)) { - const msgID = Identifier.ascending("message") - msgMap.set(msg.info.id, msgID) - msg.info.id = msgID - msg.info.sessionID = localSessionID - if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID - - db.insert(MessageTable) - .values({ - id: msgID, - session_id: localSessionID, - time_created: msg.info.time?.created ?? Date.now(), - data: msg.info, - }) + Database.transaction((db) => { + db.insert(SessionTable) + .values(SessionToRow(info as Record)) .onConflictDoNothing() .run() - for (const part of msg.parts ?? []) { - const partID = Identifier.ascending("part") - part.id = partID - part.messageID = msgID - part.sessionID = localSessionID + const messages = Array.isArray(data.messages) ? data.messages : [] + for (const msg of messages.filter((m: any) => m.info)) { + const msgID = Identifier.ascending("message") + msgMap.set(msg.info.id, msgID) + msg.info.id = msgID + msg.info.sessionID = localSessionID + if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID - db.insert(PartTable) + db.insert(MessageTable) .values({ - id: partID, - message_id: msgID, + id: msgID, session_id: localSessionID, - data: part, + time_created: msg.info.time?.created ?? Date.now(), + data: msg.info, }) .onConflictDoNothing() .run() + + for (const part of msg.parts ?? []) { + const partID = Identifier.ascending("part") + part.id = partID + part.messageID = msgID + part.sessionID = localSessionID + + db.insert(PartTable) + .values({ + id: partID, + message_id: msgID, + session_id: localSessionID, + data: part, + }) + .onConflictDoNothing() + .run() + } } - } - Database.effect(() => Bus.publish(SessionCreatedEvent, { info })) - }) + Database.effect(() => Bus.publish(SessionCreatedEvent, { info })) + }) - return c.json(info) + return c.json(info) + } catch (err: any) { + console.error("[Kilo Gateway] cloud/session/import: unhandled error", err?.message ?? err) + return c.json({ error: "Internal error" }, 500) + } }, ) .get( From 375a5d8b47eb448a49cf05a3c0479e8f2989d48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 21:34:26 +0100 Subject: [PATCH 50/71] fix: relax UUID validation and reset session on import failure - Remove `.uuid()` constraint from route param and sessionId validators to allow non-UUID session identifiers - Reset current session ID to undefined when cloud session import fails to prevent stale session state --- packages/kilo-gateway/src/server/routes.ts | 4 ++-- packages/kilo-vscode/webview-ui/src/context/session.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 63f493129c7..aa22a2d070f 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -338,7 +338,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { ...errors(401, 404), }, }), - validator("param", z.object({ id: z.string().uuid() })), + validator("param", z.object({ id: z.string() })), async (c: any) => { try { const auth = await Auth.get("kilo") @@ -387,7 +387,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { validator( "json", z.object({ - sessionId: z.string().uuid(), + sessionId: z.string(), }), ), async (c: any) => { diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 97e0181d176..94e4ea06c96 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -395,6 +395,7 @@ export const SessionProvider: ParentComponent = (props) => { case "cloudSessionImportFailed": setCloudPreviewId(null) + setCurrentSessionID(undefined) setLoading(false) showToast({ variant: "error", title: "Failed to import cloud session", description: message.error }) console.error("[Kilo New] Cloud session import failed:", message.error) From 9908f010e2605b48879018eb02f4fdd612643dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 21:38:57 +0100 Subject: [PATCH 51/71] fix(kilo-gateway): add query validation and fix error leak in cloud-sessions route --- packages/kilo-gateway/src/server/routes.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index aa22a2d070f..4d10277ce17 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -43,7 +43,7 @@ interface KiloRoutesDeps { SessionTable: object MessageTable: object PartTable: object - SessionToRow: (info: Record) => Record + SessionToRow: (info: any) => Record Bus: { publish(event: { type: string; properties: unknown }, payload: unknown): void | Promise } SessionCreatedEvent: { type: string; properties: unknown } Identifier: { @@ -526,6 +526,14 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { ...errors(400, 401), }, }), + validator( + "query", + z.object({ + cursor: z.string().optional(), + limit: z.coerce.number().optional(), + gitUrl: z.string().optional(), + }), + ), async (c: any) => { try { const auth = await Auth.get("kilo") @@ -534,13 +542,11 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined if (!token) return c.json({ error: "No valid token found" }, 401) - const cursor = c.req.query("cursor") - const limit = c.req.query("limit") - const gitUrl = c.req.query("gitUrl") + const { cursor, limit, gitUrl } = c.req.valid("query") const input: Record = {} if (cursor) input.cursor = cursor - if (limit) input.limit = Number(limit) + if (limit) input.limit = limit if (gitUrl) input.gitUrl = gitUrl const params = new URLSearchParams({ @@ -595,7 +601,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { return c.json({ cliSessions: sessions, nextCursor: result.nextCursor ?? null }) } catch (err: any) { console.error("[Kilo Gateway] cloud-sessions: unhandled error", err?.message ?? err) - return c.json({ error: err?.message ?? "Unknown error" }, 500) + return c.json({ error: "Internal error" }, 500) } }, ) From eff94cba0d9651c969bad0548f98ebd4b296978f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 21:57:11 +0100 Subject: [PATCH 52/71] fix: clear cloudPreviewId when starting a new session --- packages/kilo-vscode/webview-ui/src/context/session.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 94e4ea06c96..30b33bbd54d 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -882,6 +882,7 @@ export const SessionProvider: ParentComponent = (props) => { function clearCurrentSession() { setCurrentSessionID(undefined) + setCloudPreviewId(null) setLoading(false) setPermissions([]) setQuestions([]) From 47352448d85e0735fd648d6581f1fc5c1f1fe135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:03:58 +0100 Subject: [PATCH 53/71] fix: support ses_ session IDs in cloud import dialog, remove cloud_agent_session_id --- packages/kilo-gateway/src/server/routes.ts | 4 +- .../src/services/cli-backend/types.ts | 1 - .../src/components/chat/CloudImportDialog.tsx | 9 +- packages/sdk/js/src/v2/gen/sdk.gen.ts | 19 ++- packages/sdk/js/src/v2/gen/types.gen.ts | 5 +- packages/sdk/openapi.json | 121 +++++++++++++++--- 6 files changed, 133 insertions(+), 26 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 4d10277ce17..745473fef14 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -437,7 +437,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { id: localSessionID, projectID, slug: data.info.slug, - directory: data.info.directory ?? Instance.directory, + directory: data.info.directory, version: data.info.version, time, } @@ -511,7 +511,6 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { z.object({ session_id: z.string(), title: z.string().nullable(), - cloud_agent_session_id: z.string().nullable(), created_at: z.string(), updated_at: z.string(), version: z.number(), @@ -582,7 +581,6 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const sessions = (result.cliSessions ?? []).map((s: any) => ({ session_id: s.session_id, title: s.title ?? null, - cloud_agent_session_id: s.cloud_agent_session_id ?? null, created_at: typeof s.created_at === "string" ? s.created_at diff --git a/packages/kilo-vscode/src/services/cli-backend/types.ts b/packages/kilo-vscode/src/services/cli-backend/types.ts index 44fe5d9adb6..401013b3997 100644 --- a/packages/kilo-vscode/src/services/cli-backend/types.ts +++ b/packages/kilo-vscode/src/services/cli-backend/types.ts @@ -341,7 +341,6 @@ export interface Config { export interface CloudSessionInfo { session_id: string title: string | null - cloud_agent_session_id: string | null created_at: string updated_at: string version: number diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx index 28f1d9dc169..94ccf7d4411 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/CloudImportDialog.tsx @@ -6,10 +6,15 @@ import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { useLanguage } from "../../context/language" const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i +const SES_RE = /ses_[A-Za-z0-9]+/ function extractSessionId(raw: string): string | null { - const match = raw.trim().match(UUID_RE) - return match ? match[0] : null + const trimmed = raw.trim() + const ses = trimmed.match(SES_RE) + if (ses) return ses[0] + const uuid = trimmed.match(UUID_RE) + if (uuid) return uuid[0] + return null } interface CloudImportDialogProps { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index a2008405b78..98b7840d74c 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -992,6 +992,7 @@ export class Session extends HeyApiClient { parentID?: string title?: string permission?: PermissionRuleset + platform?: string }, options?: Options, ) { @@ -1004,6 +1005,7 @@ export class Session extends HeyApiClient { { in: "body", key: "parentID" }, { in: "body", key: "title" }, { in: "body", key: "permission" }, + { in: "body", key: "platform" }, ], }, ], @@ -2493,10 +2495,25 @@ export class Kilo extends HeyApiClient { public cloudSessions( parameters?: { directory?: string + cursor?: string + limit?: number + gitUrl?: string }, options?: Options, ) { - const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }]) + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "cursor" }, + { in: "query", key: "limit" }, + { in: "query", key: "gitUrl" }, + ], + }, + ], + ) return (options?.client ?? this.client).get({ url: "/kilo/cloud-sessions", ...options, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a7238ddeae0..a7fce92571d 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2972,6 +2972,7 @@ export type SessionCreateData = { parentID?: string title?: string permission?: PermissionRuleset + platform?: string } path?: never query?: { @@ -4528,6 +4529,9 @@ export type KiloCloudSessionsData = { path?: never query?: { directory?: string + cursor?: string + limit?: number + gitUrl?: string } url: "/kilo/cloud-sessions" } @@ -4549,7 +4553,6 @@ export type KiloCloudSessionsResponses = { cliSessions: Array<{ session_id: string title: string | null - cloud_agent_session_id: string | null created_at: string updated_at: string version: number diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 248fd9bc81c..6dac5d7b220 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -1361,6 +1361,9 @@ }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" + }, + "platform": { + "type": "string" } } } @@ -2429,6 +2432,32 @@ "variant": { "type": "string" }, + "editorContext": { + "type": "object", + "properties": { + "visibleFiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "openTabs": { + "type": "array", + "items": { + "type": "string" + } + }, + "activeFile": { + "type": "string" + }, + "shell": { + "type": "string" + }, + "timezone": { + "type": "string" + } + } + }, "parts": { "type": "array", "items": { @@ -2808,6 +2837,32 @@ "variant": { "type": "string" }, + "editorContext": { + "type": "object", + "properties": { + "visibleFiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "openTabs": { + "type": "array", + "items": { + "type": "string" + } + }, + "activeFile": { + "type": "string" + }, + "shell": { + "type": "string" + }, + "timezone": { + "type": "string" + } + } + }, "parts": { "type": "array", "items": { @@ -4641,6 +4696,27 @@ "schema": { "type": "string" } + }, + { + "in": "query", + "name": "cursor", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "number" + } + }, + { + "in": "query", + "name": "gitUrl", + "schema": { + "type": "string" + } } ], "summary": "Get cloud sessions", @@ -4671,16 +4747,6 @@ } ] }, - "cloud_agent_session_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, "created_at": { "type": "string" }, @@ -4691,14 +4757,7 @@ "type": "number" } }, - "required": [ - "session_id", - "title", - "cloud_agent_session_id", - "created_at", - "updated_at", - "version" - ] + "required": ["session_id", "title", "created_at", "updated_at", "version"] } }, "nextCursor": { @@ -6909,6 +6968,32 @@ }, "variant": { "type": "string" + }, + "editorContext": { + "type": "object", + "properties": { + "visibleFiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "openTabs": { + "type": "array", + "items": { + "type": "string" + } + }, + "activeFile": { + "type": "string" + }, + "shell": { + "type": "string" + }, + "timezone": { + "type": "string" + } + } } }, "required": ["id", "sessionID", "role", "time", "agent", "model"] From 76c96afe9b609e10b23b614fed33549d9a57cf14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:22:50 +0100 Subject: [PATCH 54/71] style(i18n): reformat long string values onto single lines --- packages/app/src/i18n/ar.ts | 3 +-- packages/app/src/i18n/fr.ts | 6 ++---- packages/app/src/i18n/th.ts | 3 +-- packages/app/src/i18n/zht.ts | 3 +-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index a147e0a7ca3..d9130992ca8 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -132,8 +132,7 @@ export const dict = { "provider.connect.oauth.code.invalid": "رمز التفويض غير صالح", "provider.connect.oauth.auto.visit.prefix": "قم بزيارة ", "provider.connect.oauth.auto.visit.link": "هذا الرابط", - "provider.connect.oauth.auto.visit.suffix": - " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.", + "provider.connect.oauth.auto.visit.suffix": " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.", "provider.connect.oauth.auto.confirmationCode": "رمز التأكيد", "provider.connect.toast.connected.title": "تم توصيل {{provider}}", "provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 3ea4a19a9be..1f3dd6b04d2 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -385,8 +385,7 @@ export const dict = { "toast.session.unshare.failed.description": "Une erreur s'est produite lors de l'annulation du partage de la session", "toast.session.listFailed.title": "Échec du chargement des sessions pour {{project}}", "toast.update.title": "Mise à jour disponible", - "toast.update.description": - "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.", + "toast.update.description": "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.", "toast.update.action.installRestart": "Installer et redémarrer", "toast.update.action.notYet": "Pas encore", "error.page.title": "Quelque chose s'est mal passé", @@ -517,8 +516,7 @@ export const dict = { "sidebar.workspaces.enable": "Activer les espaces de travail", "sidebar.workspaces.disable": "Désactiver les espaces de travail", "sidebar.gettingStarted.title": "Commencer", - "sidebar.gettingStarted.line1": - "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.", + "sidebar.gettingStarted.line1": "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.", "sidebar.gettingStarted.line2": "Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.", "sidebar.project.recentSessions": "Sessions récentes", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 5072733aade..f1d695f0ee4 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -144,8 +144,7 @@ export const dict = { "provider.connect.oauth.code.invalid": "รหัสการอนุญาตไม่ถูกต้อง", "provider.connect.oauth.auto.visit.prefix": "เยี่ยมชม ", "provider.connect.oauth.auto.visit.link": "ลิงก์นี้", - "provider.connect.oauth.auto.visit.suffix": - " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo", + "provider.connect.oauth.auto.visit.suffix": " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo", "provider.connect.oauth.auto.confirmationCode": "รหัสยืนยัน", "provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว", "provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 2e624f2071b..69577b3d234 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -145,8 +145,7 @@ export const dict = { "provider.connect.oauth.code.invalid": "授權碼無效", "provider.connect.oauth.auto.visit.prefix": "造訪 ", "provider.connect.oauth.auto.visit.link": "此連結", - "provider.connect.oauth.auto.visit.suffix": - " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。", + "provider.connect.oauth.auto.visit.suffix": " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。", "provider.connect.oauth.auto.confirmationCode": "確認碼", "provider.connect.toast.connected.title": "{{provider}} 已連線", "provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。", From 92ce9b3a5d5f08c775d1b207c9a9c4e7d8e116a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:23:28 +0100 Subject: [PATCH 55/71] style: reformat multiline expressions for readability --- packages/app/src/pages/layout/sidebar-items.tsx | 4 +--- packages/opencode/src/session/prompt.ts | 5 ++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/app/src/pages/layout/sidebar-items.tsx b/packages/app/src/pages/layout/sidebar-items.tsx index 9bbb58f3fbe..3b8f0c3c8d3 100644 --- a/packages/app/src/pages/layout/sidebar-items.tsx +++ b/packages/app/src/pages/layout/sidebar-items.tsx @@ -32,9 +32,7 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
0 && props.notify }} diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index c5fa4de1e09..365e9c9585b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -676,7 +676,10 @@ export namespace SessionPrompt { await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: sessionMessages }) // Build system prompt, adding structured output instruction if needed - const system = [...(await SystemPrompt.environment(model, lastUser.editorContext)), ...(await InstructionPrompt.system())] // kilocode_change + const system = [ + ...(await SystemPrompt.environment(model, lastUser.editorContext)), + ...(await InstructionPrompt.system()), + ] // kilocode_change const format = lastUser.format ?? { type: "text" } if (format.type === "json_schema") { system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT) From 2fffe68897b69a9c61609b5bc02af5e86fed032f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:23:40 +0100 Subject: [PATCH 56/71] fix(kilo-gateway): route session fetch based on ID format Use a direct session URL for UUID-format IDs and fall back to the legacy export endpoint for non-UUID session IDs (e.g. `ses_` prefixed). This ensures compatibility with both ID formats when fetching session data from the ingest service. --- packages/kilo-gateway/src/server/routes.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 745473fef14..ac24ad20b11 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -348,7 +348,9 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const { id } = c.req.valid("param") const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" - const response = await fetch(`${base}/api/session/${id}/export`, { + const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id) + const url = isUuid ? `${base}/session/${id}` : `${base}/api/session/${id}/export` + const response = await fetch(url, { headers: { Authorization: `Bearer ${token}`, ...buildKiloHeaders(), @@ -400,7 +402,9 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { if (!token) return c.json({ error: "No valid token found" }, 401) const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" - const response = await fetch(`${base}/api/session/${sessionId}/export`, { + const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId) + const exportUrl = isUuid ? `${base}/session/${sessionId}` : `${base}/api/session/${sessionId}/export` + const response = await fetch(exportUrl, { headers: { Authorization: `Bearer ${token}`, ...buildKiloHeaders(), From f31ca9104d93f511b789929a92a9b672a4206338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:29:18 +0100 Subject: [PATCH 57/71] build(deps): sort and organize package.json dependencies Reorder `@kilocode/*` packages alphabetically at the top of the dependencies block and remove duplicate entries that were scattered throughout the file. No new dependencies are added or removed. --- packages/opencode/package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e5d2b09d8f5..622db2eea6f 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -78,13 +78,15 @@ "@gitlab/opencode-gitlab-auth": "1.3.3", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", + "@kilocode/kilo-gateway": "workspace:*", + "@kilocode/kilo-telemetry": "workspace:*", + "@kilocode/plugin": "workspace:*", + "@kilocode/sdk": "workspace:*", "@modelcontextprotocol/sdk": "1.25.2", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@openauthjs/openauth": "catalog:", - "@kilocode/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", - "@kilocode/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", "@opentui/core": "0.1.79", @@ -115,6 +117,7 @@ "opentui-spinner": "0.0.6", "partial-json": "0.1.7", "remeda": "catalog:", + "simple-git": "3.31.1", "solid-js": "catalog:", "strip-ansi": "7.1.2", "tree-sitter-bash": "0.25.0", @@ -125,10 +128,7 @@ "xdg-basedir": "5.1.0", "yargs": "18.0.0", "zod": "catalog:", - "zod-to-json-schema": "3.24.5", - "@kilocode/kilo-gateway": "workspace:*", - "@kilocode/kilo-telemetry": "workspace:*", - "simple-git": "3.31.1" + "zod-to-json-schema": "3.24.5" }, "overrides": { "drizzle-orm": "1.0.0-beta.12-a5629fb" From dac3e80818070a35a1891a067ce57a2309dc5678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:29:52 +0100 Subject: [PATCH 58/71] fix(kilo-gateway): use Instance.directory instead of data.info.directory --- packages/kilo-gateway/src/server/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index ac24ad20b11..44626a684d9 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -441,7 +441,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { id: localSessionID, projectID, slug: data.info.slug, - directory: data.info.directory, + directory: Instance.directory, version: data.info.version, time, } From ff727da6118024e25e596f9e0cac9d515c432428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:37:01 +0100 Subject: [PATCH 59/71] fix(kilo-gateway): always use current timestamp for session updated time --- packages/kilo-gateway/src/server/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 44626a684d9..83339d16a65 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -431,7 +431,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const now = Date.now() const time = { created: data.info.time?.created ?? now, - updated: data.info.time?.updated ?? now, + updated: now, ...(data.info.time?.compacting !== undefined && { compacting: data.info.time.compacting }), ...(data.info.time?.archived !== undefined && { archived: data.info.time.archived }), } From a4b8a189728bf73b1f880f58c17aba6e862e9a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:52:07 +0100 Subject: [PATCH 60/71] fix(session): prevent message flash on cloud session import race condition Guard against two race conditions that caused visible UI flashes when importing cloud sessions: 1. In `handleSessionCreated`, avoid wiping existing messages to `[]` when the SSE `session.created` event races with `cloudSessionImported`. Messages are now only initialized if none exist yet for that session ID. 2. In `handleCloudSessionImported`, stop deleting cloud parts when cleaning up synthetic `cloud:` store entries. Carried-over messages still reference cloud message IDs, so deleting their parts caused every `` to render empty until `loadMessages` completed. Stale cloud parts are harmlessly overwritten once `messagesLoaded` arrives. --- .../webview-ui/src/context/session.tsx | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 30b33bbd54d..57aeb3a0bae 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -410,7 +410,15 @@ export const SessionProvider: ParentComponent = (props) => { function handleSessionCreated(session: SessionInfo) { batch(() => { setStore("sessions", session.id, session) - setStore("messages", session.id, []) + + // Only initialize messages if none exist yet — a cloud session import + // (handleCloudSessionImported) may have already populated messages for + // this session ID. The SSE session.created event can race with the + // cloudSessionImported message, and wiping to [] causes a flash of + // the empty/welcome screen. + if (!store.messages[session.id]?.length) { + setStore("messages", session.id, []) + } // If there's a pending model selection, assign it to this new session. // Guard against duplicate sessionCreated events (HTTP response + SSE) @@ -705,15 +713,12 @@ export const SessionProvider: ParentComponent = (props) => { setCloudPreviewId(null) setCurrentSessionID(session.id) - const msgIds = cloudMessages.map((m) => m.id) - setStore( - "parts", - produce((parts) => { - for (const id of msgIds) { - delete parts[id] - } - }), - ) + // Clean up synthetic cloud: entries from sessions/messages stores. + // NOTE: Do NOT delete cloud parts here — the carried-over messages still + // reference cloud message IDs. Deleting their parts causes every + // to render empty (parts().length === 0), creating a visible flash where + // only the WorkingIndicator spinner is shown until loadMessages completes. + // Stale cloud parts are harmlessly overwritten once messagesLoaded arrives. setStore( "sessions", produce((sessions) => { From 5616a0241cbd580d4975ec6561a408d575a4f5e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 22:56:51 +0100 Subject: [PATCH 61/71] fix(kilo-vscode): swap navigation order of history and cloud history buttons Reorder the sidebar navigation buttons so that cloudHistoryButtonClicked is at group navigation@3 and historyButtonClicked is at navigation@4, correcting the display order in the sidebar view. --- packages/kilo-vscode/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index ba5d47bc32c..2f2da3059f8 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -222,13 +222,13 @@ "when": "view == kilo-code.new.sidebarView" }, { - "command": "kilo-code.new.historyButtonClicked", - "group": "navigation@4", + "command": "kilo-code.new.cloudHistoryButtonClicked", + "group": "navigation@3", "when": "view == kilo-code.new.sidebarView" }, { - "command": "kilo-code.new.cloudHistoryButtonClicked", - "group": "navigation@3", + "command": "kilo-code.new.historyButtonClicked", + "group": "navigation@4", "when": "view == kilo-code.new.sidebarView" }, { From a3c980826ea0a0c22f6d11446814f00d1d5085c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Mon, 23 Feb 2026 23:10:03 +0100 Subject: [PATCH 62/71] fix(kilo-sessions): reverse messages before fullSync we need to send them in asc order --- packages/opencode/src/kilo-sessions/kilo-sessions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/kilo-sessions/kilo-sessions.ts b/packages/opencode/src/kilo-sessions/kilo-sessions.ts index d3d916fd62f..479bffed51e 100644 --- a/packages/opencode/src/kilo-sessions/kilo-sessions.ts +++ b/packages/opencode/src/kilo-sessions/kilo-sessions.ts @@ -343,6 +343,7 @@ export namespace KiloSessions { const session = await Session.get(sessionId) const diffs = await Session.diff(sessionId) const messages = await Array.fromAsync(MessageV2.stream(sessionId)) + messages.reverse() const models = await Promise.all( messages .filter((m) => m.info.role === "user") From 59568960205e3003709af38af6e61799e5c22084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 12:53:30 +0100 Subject: [PATCH 63/71] feat(vscode): include editor context when sending message after cloud import --- packages/kilo-vscode/src/KiloProvider.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index e921ee124c7..d7a5ddcd47e 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1151,11 +1151,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper parts.push({ type: "text", text }) try { + const editorContext = await this.gatherEditorContext() + await this.httpClient.sendMessage(session.id, parts, workspaceDir, { providerID, modelID, agent, variant, + editorContext, }) } catch (err) { console.error("[Kilo New] Failed to send message after cloud import:", err) From 131599c6ad866b3288d26ca18bf65b75ede1ad3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 12:53:53 +0100 Subject: [PATCH 64/71] test(worktree-state-manager): flush before save in subdirectory test --- packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts b/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts index d905f9d6e49..af36c3c7b5b 100644 --- a/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts +++ b/packages/kilo-vscode/tests/unit/worktree-state-manager.test.ts @@ -168,6 +168,7 @@ describe("WorktreeStateManager", () => { const fresh = path.join(root, "subdir") const mgr = new WorktreeStateManager(fresh, () => {}) mgr.addWorktree({ branch: "test", path: "/tmp/test", parentBranch: "main" }) + await mgr.flush() await mgr.save() expect(fs.existsSync(path.join(fresh, ".kilocode", "agent-manager.json"))).toBe(true) From 506ca123bc0e704e8b83c158208b9f126b82654f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 14:11:48 +0100 Subject: [PATCH 65/71] revert(vscode): skip manually adding files to session it's handled by gatherEditorContext below --- packages/kilo-vscode/src/KiloProvider.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index d7a5ddcd47e..47f5665ad51 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1133,15 +1133,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper // Step 2: Send the user's message on the new local session const parts: Array<{ type: "text"; text: string } | { type: "file"; mime: string; url: string }> = [] - const editor = vscode.window.activeTextEditor - if (editor && editor.document.uri.scheme === "file") { - const url = editor.document.uri.toString() - const already = files?.some((f) => f.url === url) - if (!already) { - parts.push({ type: "file", mime: "text/plain", url }) - } - } - if (files) { for (const f of files) { parts.push({ type: "file", mime: f.mime, url: f.url }) From 731c326f05f2933c48e9e709904df201e5b72f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 14:50:25 +0100 Subject: [PATCH 66/71] refactor: replace magic number with DAY_MS constant in CloudSessionList --- .../src/components/history/CloudSessionList.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx index 3a236be5d54..e6c1c0a119c 100644 --- a/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/history/CloudSessionList.tsx @@ -19,10 +19,12 @@ function dateGroupKey(iso: string): (typeof DATE_GROUP_KEYS)[number] { const now = new Date() const then = new Date(iso) + const DAY_MS = 24 * 60 * 60 * 1000 + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) - const yesterday = new Date(today.getTime() - 86400000) - const weekAgo = new Date(today.getTime() - 7 * 86400000) - const monthAgo = new Date(today.getTime() - 30 * 86400000) + const yesterday = new Date(today.getTime() - DAY_MS) + const weekAgo = new Date(today.getTime() - 7 * DAY_MS) + const monthAgo = new Date(today.getTime() - 30 * DAY_MS) if (then >= today) return DATE_GROUP_KEYS[0] if (then >= yesterday) return DATE_GROUP_KEYS[1] From 7da15d60a2eb05d4bc1b31d548d43e387fe29383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 14:50:31 +0100 Subject: [PATCH 67/71] docs: clarify cloud parts cleanup flow in handleCloudSessionImported --- .../webview-ui/src/context/session.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 57aeb3a0bae..97f13ff7244 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -714,11 +714,29 @@ export const SessionProvider: ParentComponent = (props) => { setCurrentSessionID(session.id) // Clean up synthetic cloud: entries from sessions/messages stores. - // NOTE: Do NOT delete cloud parts here — the carried-over messages still - // reference cloud message IDs. Deleting their parts causes every - // to render empty (parts().length === 0), creating a visible flash where - // only the WorkingIndicator spinner is shown until loadMessages completes. - // Stale cloud parts are harmlessly overwritten once messagesLoaded arrives. + // + // Why we do NOT delete cloud parts here: + // + // During preview, parts are stored keyed by the original cloud message IDs + // (e.g. store.parts[""] = [...]). When the import completes + // we carry cloudMessages into the new local session (above) so the UI + // renders immediately without a loading flash. Those carried-over message + // objects still hold their original cloud IDs, so every component + // calls getParts("") — which means the parts must remain in + // the store for now. + // + // If we deleted them here, every message would temporarily render with no + // parts (parts().length === 0), showing only the WorkingIndicator spinner + // until the real data arrives. + // + // Instead, right after this batch we dispatch a "loadMessages" request + // (below). When the extension responds with the "messagesLoaded" event, + // handleMessagesLoaded() replaces the messages array with server-assigned + // IDs and writes new parts keyed by those IDs. The old cloud-keyed part + // entries become orphans — no message in the store references them anymore. + // They remain in store.parts until the webview reloads or the store is + // reset, which is a bounded, one-session-worth amount of data that does + // not accumulate over time. setStore( "sessions", produce((sessions) => { From 98763a09a54ac75ed08fb4af6bae7b9c8e2107e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 14:50:36 +0100 Subject: [PATCH 68/71] refactor: extract cloud session fetch/import logic into cloud-sessions.ts --- packages/kilo-gateway/src/cloud-sessions.ts | 154 ++++++++++++++++++++ packages/kilo-gateway/src/server/routes.ts | 117 +++------------ 2 files changed, 171 insertions(+), 100 deletions(-) create mode 100644 packages/kilo-gateway/src/cloud-sessions.ts diff --git a/packages/kilo-gateway/src/cloud-sessions.ts b/packages/kilo-gateway/src/cloud-sessions.ts new file mode 100644 index 00000000000..a530cbbec3b --- /dev/null +++ b/packages/kilo-gateway/src/cloud-sessions.ts @@ -0,0 +1,154 @@ +import { buildKiloHeaders } from "./headers.js" + +export const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + +const INGEST_BASE = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" + +function exportUrl(sessionId: string) { + return UUID_RE.test(sessionId) + ? `${INGEST_BASE}/session/${sessionId}` + : `${INGEST_BASE}/api/session/${sessionId}/export` +} + +export type FetchResult = { ok: true; data: any } | { ok: false; status: number; error: string } + +export async function fetchCloudSession(token: string, sessionId: string): Promise { + const response = await fetch(exportUrl(sessionId), { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, + }) + + if (response.status === 404) return { ok: false, status: 404, error: "Session not found" } + if (!response.ok) return { ok: false, status: response.status, error: "Failed to fetch session" } + + const data = await response.json() + return { ok: true, data } +} + +export async function fetchCloudSessionForImport(token: string, sessionId: string): Promise { + const response = await fetch(exportUrl(sessionId), { + headers: { + Authorization: `Bearer ${token}`, + ...buildKiloHeaders(), + }, + }) + + if (response.status === 404) return { ok: false, status: 404, error: "Session not found in cloud" } + if (!response.ok) { + const text = await response.text() + console.error("[Kilo Gateway] cloud/session/import: export failed", { + status: response.status, + body: text.slice(0, 500), + }) + return { ok: false, status: response.status, error: `Import failed: ${response.status}` } + } + + const data = await response.json() + return { ok: true, data } +} + +interface ImportDeps { + Database: { + transaction(callback: (db: any) => T): T + effect(fn: () => void | Promise): void + } + Instance: { + readonly directory: string + readonly project: { readonly id: string } + } + SessionTable: object + MessageTable: object + PartTable: object + SessionToRow: (info: any) => Record + Bus: { publish(event: { type: string; properties: unknown }, payload: unknown): void | Promise } + SessionCreatedEvent: { type: string; properties: unknown } + Identifier: { + ascending(prefix: "session" | "message" | "part", given?: string): string + descending(prefix: "session" | "message" | "part", given?: string): string + } +} + +export function importSessionToDb(data: any, deps: ImportDeps) { + const { + Database, + Instance, + SessionTable, + MessageTable, + PartTable, + SessionToRow, + Bus, + SessionCreatedEvent, + Identifier, + } = deps + + const localSessionID = Identifier.descending("session") + const msgMap = new Map() + const projectID = Instance.project.id + + const now = Date.now() + const time = { + created: data.info.time?.created ?? now, + updated: now, + ...(data.info.time?.compacting !== undefined && { compacting: data.info.time.compacting }), + ...(data.info.time?.archived !== undefined && { archived: data.info.time.archived }), + } + + const info = { + ...data.info, + id: localSessionID, + projectID, + slug: data.info.slug, + directory: Instance.directory, + version: data.info.version, + time, + } + + Database.transaction((db) => { + db.insert(SessionTable) + .values(SessionToRow(info as Record)) + .onConflictDoNothing() + .run() + + const messages = Array.isArray(data.messages) ? data.messages : [] + for (const msg of messages.filter((m: any) => m.info)) { + const msgID = Identifier.ascending("message") + msgMap.set(msg.info.id, msgID) + msg.info.id = msgID + msg.info.sessionID = localSessionID + if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID + + db.insert(MessageTable) + .values({ + id: msgID, + session_id: localSessionID, + time_created: msg.info.time?.created ?? Date.now(), + data: msg.info, + }) + .onConflictDoNothing() + .run() + + for (const part of msg.parts ?? []) { + const partID = Identifier.ascending("part") + part.id = partID + part.messageID = msgID + part.sessionID = localSessionID + + db.insert(PartTable) + .values({ + id: partID, + message_id: msgID, + session_id: localSessionID, + data: part, + }) + .onConflictDoNothing() + .run() + } + } + + Database.effect(() => Bus.publish(SessionCreatedEvent, { info })) + }) + + return info +} diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 83339d16a65..306fb70508a 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -10,6 +10,7 @@ import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE import { buildKiloHeaders } from "../headers.js" // kilocode_change +import { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "../cloud-sessions.js" // kilocode_change // Type definitions for OpenCode dependencies (injected at runtime) type Hono = any @@ -347,21 +348,9 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { if (!token) return c.json({ error: "No valid token found" }, 401) const { id } = c.req.valid("param") - const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" - const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id) - const url = isUuid ? `${base}/session/${id}` : `${base}/api/session/${id}/export` - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - ...buildKiloHeaders(), - }, - }) - - if (response.status === 404) return c.json({ error: "Session not found" }, 404) - if (!response.ok) return c.json({ error: "Failed to fetch session" }, response.status) - - const data = await response.json() - return c.json(data) + const result = await fetchCloudSession(token, id) + if (!result.ok) return c.json({ error: result.error }, result.status) + return c.json(result.data) } catch (err: any) { console.error("[Kilo Gateway] cloud/session/get: unhandled error", err?.message ?? err) return c.json({ error: "Internal error" }, 500) @@ -401,94 +390,22 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { const token = auth.type === "api" ? auth.key : auth.type === "oauth" ? auth.access : undefined if (!token) return c.json({ error: "No valid token found" }, 401) - const base = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" - const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId) - const exportUrl = isUuid ? `${base}/session/${sessionId}` : `${base}/api/session/${sessionId}/export` - const response = await fetch(exportUrl, { - headers: { - Authorization: `Bearer ${token}`, - ...buildKiloHeaders(), - }, - }) + const fetched = await fetchCloudSessionForImport(token, sessionId) + if (!fetched.ok) return c.json({ error: fetched.error }, fetched.status as any) - if (response.status === 404) return c.json({ error: "Session not found in cloud" }, 404) - if (!response.ok) { - const text = await response.text() - console.error("[Kilo Gateway] cloud/session/import: export failed", { - status: response.status, - body: text.slice(0, 500), - }) - return c.json({ error: `Import failed: ${response.status}` }, response.status as any) - } - - const data = (await response.json()) as any + const data = fetched.data if (!data?.info?.id) return c.json({ error: "Invalid export data" }, 400) - const localSessionID = Identifier.descending("session") - const msgMap = new Map() - const projectID = Instance.project.id - - const now = Date.now() - const time = { - created: data.info.time?.created ?? now, - updated: now, - ...(data.info.time?.compacting !== undefined && { compacting: data.info.time.compacting }), - ...(data.info.time?.archived !== undefined && { archived: data.info.time.archived }), - } - - const info = { - ...data.info, - id: localSessionID, - projectID, - slug: data.info.slug, - directory: Instance.directory, - version: data.info.version, - time, - } - - Database.transaction((db) => { - db.insert(SessionTable) - .values(SessionToRow(info as Record)) - .onConflictDoNothing() - .run() - - const messages = Array.isArray(data.messages) ? data.messages : [] - for (const msg of messages.filter((m: any) => m.info)) { - const msgID = Identifier.ascending("message") - msgMap.set(msg.info.id, msgID) - msg.info.id = msgID - msg.info.sessionID = localSessionID - if (msg.info.parentID) msg.info.parentID = msgMap.get(msg.info.parentID) ?? msg.info.parentID - - db.insert(MessageTable) - .values({ - id: msgID, - session_id: localSessionID, - time_created: msg.info.time?.created ?? Date.now(), - data: msg.info, - }) - .onConflictDoNothing() - .run() - - for (const part of msg.parts ?? []) { - const partID = Identifier.ascending("part") - part.id = partID - part.messageID = msgID - part.sessionID = localSessionID - - db.insert(PartTable) - .values({ - id: partID, - message_id: msgID, - session_id: localSessionID, - data: part, - }) - .onConflictDoNothing() - .run() - } - } - - Database.effect(() => Bus.publish(SessionCreatedEvent, { info })) + const info = importSessionToDb(data, { + Database, + Instance, + SessionTable, + MessageTable, + PartTable, + SessionToRow, + Bus, + SessionCreatedEvent, + Identifier, }) return c.json(info) From ef19677163907999094ea5c8b61d62fe2075b23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 14:56:20 +0100 Subject: [PATCH 69/71] refactor: eliminate duplicate ImportDeps by exporting it from cloud-sessions.ts --- packages/kilo-gateway/src/cloud-sessions.ts | 8 ++++-- packages/kilo-gateway/src/server/routes.ts | 32 ++++++--------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/packages/kilo-gateway/src/cloud-sessions.ts b/packages/kilo-gateway/src/cloud-sessions.ts index a530cbbec3b..fd5e9d4c991 100644 --- a/packages/kilo-gateway/src/cloud-sessions.ts +++ b/packages/kilo-gateway/src/cloud-sessions.ts @@ -2,6 +2,10 @@ import { buildKiloHeaders } from "./headers.js" export const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i +export interface DrizzleDb { + insert(table: object): { values(data: object): { onConflictDoNothing(): { run(): void } } } +} + const INGEST_BASE = process.env.KILO_SESSION_INGEST_URL ?? "https://ingest.kilosessions.ai" function exportUrl(sessionId: string) { @@ -49,9 +53,9 @@ export async function fetchCloudSessionForImport(token: string, sessionId: strin return { ok: true, data } } -interface ImportDeps { +export interface ImportDeps { Database: { - transaction(callback: (db: any) => T): T + transaction(callback: (db: DrizzleDb) => T): T effect(fn: () => void | Promise): void } Instance: { diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 306fb70508a..5a4598d7235 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -10,7 +10,13 @@ import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE import { buildKiloHeaders } from "../headers.js" // kilocode_change -import { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "../cloud-sessions.js" // kilocode_change +import { + fetchCloudSession, + fetchCloudSessionForImport, + importSessionToDb, + ImportDeps, + DrizzleDb, +} from "../cloud-sessions.js" // kilocode_change // Type definitions for OpenCode dependencies (injected at runtime) type Hono = any @@ -21,11 +27,7 @@ type Errors = any type Auth = any type Z = any -interface DrizzleDb { - insert(table: object): { values(data: object): { onConflictDoNothing(): { run(): void } } } -} - -interface KiloRoutesDeps { +interface KiloRoutesDeps extends ImportDeps { Hono: new () => Hono describeRoute: DescribeRoute validator: Validator @@ -33,24 +35,6 @@ interface KiloRoutesDeps { errors: Errors Auth: Auth z: Z - Database: { - transaction(callback: (db: DrizzleDb) => T): T - effect(fn: () => void | Promise): void - } - Instance: { - readonly directory: string - readonly project: { readonly id: string } - } - SessionTable: object - MessageTable: object - PartTable: object - SessionToRow: (info: any) => Record - Bus: { publish(event: { type: string; properties: unknown }, payload: unknown): void | Promise } - SessionCreatedEvent: { type: string; properties: unknown } - Identifier: { - ascending(prefix: "session" | "message" | "part", given?: string): string - descending(prefix: "session" | "message" | "part", given?: string): string - } } /** From aef1f961456e164a205ecbea34ee9721cf341cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 15:02:33 +0100 Subject: [PATCH 70/71] fix: use type-only imports for ImportDeps and DrizzleDb --- packages/kilo-gateway/src/server/routes.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 5a4598d7235..bf37784fc81 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -10,13 +10,8 @@ import { fetchProfile, fetchBalance } from "../api/profile.js" import { fetchKilocodeNotifications, KilocodeNotificationSchema } from "../api/notifications.js" import { KILO_API_BASE, HEADER_FEATURE } from "../api/constants.js" // kilocode_change - added HEADER_FEATURE import { buildKiloHeaders } from "../headers.js" // kilocode_change -import { - fetchCloudSession, - fetchCloudSessionForImport, - importSessionToDb, - ImportDeps, - DrizzleDb, -} from "../cloud-sessions.js" // kilocode_change +import type { ImportDeps, DrizzleDb } from "../cloud-sessions.js" // kilocode_change +import { fetchCloudSession, fetchCloudSessionForImport, importSessionToDb } from "../cloud-sessions.js" // kilocode_change // Type definitions for OpenCode dependencies (injected at runtime) type Hono = any From d7043ebb528907e6367e03451f245a0d8efc3627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20=C5=A0=C4=87eki=C4=87?= Date: Tue, 24 Feb 2026 15:08:01 +0100 Subject: [PATCH 71/71] fix: add i18n key for cloud session import failure toast --- packages/kilo-vscode/webview-ui/src/context/session.tsx | 6 +++++- packages/kilo-vscode/webview-ui/src/i18n/ar.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/br.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/bs.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/da.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/de.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/en.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/es.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/fr.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ja.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ko.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/no.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/pl.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/ru.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/th.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zh.ts | 1 + packages/kilo-vscode/webview-ui/src/i18n/zht.ts | 1 + 17 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 97f13ff7244..524b1901a88 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -397,7 +397,11 @@ export const SessionProvider: ParentComponent = (props) => { setCloudPreviewId(null) setCurrentSessionID(undefined) setLoading(false) - showToast({ variant: "error", title: "Failed to import cloud session", description: message.error }) + showToast({ + variant: "error", + title: language.t("session.cloud.import.failed") ?? "Failed to import cloud session", + description: message.error, + }) console.error("[Kilo New] Cloud session import failed:", message.error) break } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 91772e10360..3d06b0b587d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -694,6 +694,7 @@ export const dict = { "session.cloud.import.placeholder": "معرّف الجلسة أو الرابط أو أمر kilo import", "session.cloud.import.button": "استيراد", "session.cloud.import.invalid": "تنسيق معرّف الجلسة غير صالح", + "session.cloud.import.failed": "فشل استيراد جلسة السحابة", "workspace.new": "مساحة عمل جديدة", "workspace.type.local": "محلي", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 209d1fb5896..180d23a9f29 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -701,6 +701,7 @@ export const dict = { "session.cloud.import.placeholder": "ID da sessão, URL ou comando kilo import", "session.cloud.import.button": "Importar", "session.cloud.import.invalid": "Formato de ID de sessão inválido", + "session.cloud.import.failed": "Falha ao importar sessão da nuvem", "workspace.new": "Novo espaço de trabalho", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 326800f310a..0c8e4334cf5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -728,6 +728,7 @@ export const dict = { "session.cloud.import.placeholder": "ID sesije, URL ili kilo import naredba", "session.cloud.import.button": "Uvezi", "session.cloud.import.invalid": "Neispravan format ID-a sesije", + "session.cloud.import.failed": "Uvoz sesije iz oblaka nije uspio", "workspace.new": "Novi radni prostor", "workspace.type.local": "lokalno", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index b2e50c148bf..642b82ab743 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -700,6 +700,7 @@ export const dict = { "session.cloud.import.placeholder": "Sessions-ID, URL eller kilo import-kommando", "session.cloud.import.button": "Importér", "session.cloud.import.invalid": "Ugyldigt sessions-ID-format", + "session.cloud.import.failed": "Kunne ikke importere cloud-session", "workspace.new": "Nyt arbejdsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 2cfd08410d8..a197a97607c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -706,6 +706,7 @@ export const dict = { "session.cloud.import.placeholder": "Sitzungs-ID, URL oder kilo import-Befehl", "session.cloud.import.button": "Importieren", "session.cloud.import.invalid": "Ungültiges Sitzungs-ID-Format", + "session.cloud.import.failed": "Fehler beim Importieren der Cloud-Sitzung", "workspace.new": "Neuer Arbeitsbereich", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 8043bee3928..2d45ba027c2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -733,6 +733,7 @@ export const dict = { "session.cloud.import.placeholder": "Session ID, URL, or kilo import command", "session.cloud.import.button": "Import", "session.cloud.import.invalid": "Invalid session ID format", + "session.cloud.import.failed": "Failed to import cloud session", "workspace.new": "New workspace", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index f576a3666b2..c87fb1306fa 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -706,6 +706,7 @@ export const dict = { "session.cloud.import.placeholder": "ID de sesión, URL o comando kilo import", "session.cloud.import.button": "Importar", "session.cloud.import.invalid": "Formato de ID de sesión no válido", + "session.cloud.import.failed": "Error al importar la sesión de la nube", "workspace.new": "Nuevo espacio de trabajo", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index e68f04cafe8..5e67bc59664 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -710,6 +710,7 @@ export const dict = { "session.cloud.import.placeholder": "ID de session, URL ou commande kilo import", "session.cloud.import.button": "Importer", "session.cloud.import.invalid": "Format d'ID de session invalide", + "session.cloud.import.failed": "Échec de l'importation de la session cloud", "workspace.new": "Nouvel espace de travail", "workspace.type.local": "local", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index d82c9c20162..4c8fd2d88ba 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -694,6 +694,7 @@ export const dict = { "session.cloud.import.placeholder": "セッションID、URL、またはkilo importコマンド", "session.cloud.import.button": "インポート", "session.cloud.import.invalid": "セッションIDの形式が無効です", + "session.cloud.import.failed": "クラウドセッションのインポートに失敗しました", "workspace.new": "新しいワークスペース", "workspace.type.local": "ローカル", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 1b994e1ee19..775d7361328 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -699,6 +699,7 @@ export const dict = { "session.cloud.import.placeholder": "세션 ID, URL 또는 kilo import 명령어", "session.cloud.import.button": "가져오기", "session.cloud.import.invalid": "잘못된 세션 ID 형식입니다", + "session.cloud.import.failed": "클라우드 세션 가져오기 실패", "workspace.new": "새 작업 공간", "workspace.type.local": "로컬", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 3d1c8e2e857..e9397228918 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -703,6 +703,7 @@ export const dict = { "session.cloud.import.placeholder": "Økt-ID, URL eller kilo import-kommando", "session.cloud.import.button": "Importer", "session.cloud.import.invalid": "Ugyldig økt-ID-format", + "session.cloud.import.failed": "Kunne ikke importere skyøkt", "workspace.new": "Nytt arbeidsområde", "workspace.type.local": "lokal", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 6c5a1d14a84..7e3e654f4d0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -702,6 +702,7 @@ export const dict = { "session.cloud.import.placeholder": "ID sesji, URL lub polecenie kilo import", "session.cloud.import.button": "Importuj", "session.cloud.import.invalid": "Nieprawidłowy format ID sesji", + "session.cloud.import.failed": "Nie udało się zaimportować sesji chmurowej", "workspace.new": "Nowa przestrzeń robocza", "workspace.type.local": "lokalna", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 25ef092602e..534345749e8 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -706,6 +706,7 @@ export const dict = { "session.cloud.import.placeholder": "ID сессии, URL или команда kilo import", "session.cloud.import.button": "Импортировать", "session.cloud.import.invalid": "Неверный формат ID сессии", + "session.cloud.import.failed": "Не удалось импортировать облачную сессию", "workspace.new": "Новое рабочее пространство", "workspace.type.local": "локальное", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 7ab38ee93b5..afc72bf08d6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -692,6 +692,7 @@ export const dict = { "session.cloud.import.placeholder": "ID เซสชัน, URL หรือคำสั่ง kilo import", "session.cloud.import.button": "นำเข้า", "session.cloud.import.invalid": "รูปแบบ ID เซสชันไม่ถูกต้อง", + "session.cloud.import.failed": "ไม่สามารถนำเข้าเซสชันคลาวด์ได้", "workspace.new": "พื้นที่ทำงานใหม่", "workspace.type.local": "ในเครื่อง", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 7266854eeb5..790267b9f9f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -695,6 +695,7 @@ export const dict = { "session.cloud.import.placeholder": "会话 ID、URL 或 kilo import 命令", "session.cloud.import.button": "导入", "session.cloud.import.invalid": "会话 ID 格式无效", + "session.cloud.import.failed": "导入云会话失败", "workspace.new": "新建工作区", "workspace.type.local": "本地", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 22053eb7e2e..679fe4fba02 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -692,6 +692,7 @@ export const dict = { "session.cloud.import.placeholder": "工作階段 ID、URL 或 kilo import 指令", "session.cloud.import.button": "匯入", "session.cloud.import.invalid": "工作階段 ID 格式無效", + "session.cloud.import.failed": "無法匯入雲端工作階段", "workspace.new": "新增工作區", "workspace.type.local": "本地",