Merge pull request #6550 from Kilo-Org/eshurakov/ingest-import

IngestImport - Bootstrap ingest for imported sessions
This commit is contained in:
Evgeny Shurakov
2026-03-03 13:01:06 +01:00
committed by GitHub
3 changed files with 109 additions and 5 deletions
+45 -2
View File
@@ -1,7 +1,6 @@
import type { Argv } from "yargs"
import type { Session as SDKSession, Message, Part } from "@kilocode/sdk/v2"
import { Session } from "../../session"
import { Bus } from "../../bus"
import { cmd } from "./cmd"
import { bootstrap } from "../bootstrap"
import { Database } from "../../storage/db"
@@ -10,6 +9,9 @@ import { Instance } from "../../project/instance"
import { ShareNext } from "../../share/share-next"
import { EOL } from "os"
import { Filesystem } from "../../util/filesystem"
import { Log } from "../../util/log"
const log = Log.create({ service: "import-command" })
/** Discriminated union returned by the ShareNext API (GET /api/share/:id/data) */
export type ShareData =
@@ -65,6 +67,44 @@ export function transformShareData(shareData: ShareData[]): {
}
}
// kilocode_change start
export function ingestBootstrapWarning(sessionId: string, error: unknown) {
const details = error instanceof Error ? error.message : String(error)
return `Warning: imported session ${sessionId} locally, but ingest bootstrap failed: ${details}`
}
async function ingestBootstrap(sessionId: string) {
const { KiloSessions } = await import("../../kilo-sessions/kilo-sessions")
return KiloSessions.bootstrap(sessionId)
}
export async function bootstrapImportedSessionIngest(
sessionId: string,
input?: {
bootstrap?: (sessionId: string) => Promise<unknown>
warn?: (message: string) => void
},
) {
const run = input?.bootstrap ?? ingestBootstrap
const warn =
input?.warn ??
((message: string) => {
process.stderr.write(message)
process.stderr.write(EOL)
})
log.info("ingest bootstrap started", { sessionId })
await run(sessionId)
.then(() => {
log.info("ingest bootstrap completed", { sessionId })
})
.catch((error) => {
log.error("ingest bootstrap failed", { sessionId, error })
warn(ingestBootstrapWarning(sessionId, error))
})
}
// kilocode_change end
export const ImportCommand = cmd({
command: "import <file>",
describe: "import session data from JSON file or URL",
@@ -134,7 +174,6 @@ export const ImportCommand = cmd({
Database.use((db) => {
db.insert(SessionTable).values(Session.toRow(exportData.info)).onConflictDoNothing().run()
Database.effect(() => Bus.publish(Session.Event.Created, { info: exportData.info }))
})
for (const msg of exportData.messages) {
@@ -167,6 +206,10 @@ export const ImportCommand = cmd({
}
}
// kilocode_change start
await bootstrapImportedSessionIngest(exportData.info.id)
// kilocode_change end
process.stdout.write(`Imported session: ${exportData.info.id}`)
process.stdout.write(EOL)
})
@@ -198,8 +198,25 @@ export namespace KiloSessions {
}
export async function create(sessionId: string) {
const result = await bootstrap(sessionId)
if (!result) return { id: "", ingestPath: "" }
void fullSync(sessionId).catch((error) => log.error("share full sync failed", { sessionId, error }))
return result
}
export async function bootstrap(sessionId: string) {
if (ingestDisabled) {
log.info("session bootstrap skipped: ingest disabled", { sessionId })
return
}
const client = await getClient()
if (!client) return { id: "", ingestPath: "" }
if (!client) {
log.info("session bootstrap skipped: no client", { sessionId })
return
}
log.info("creating session", { sessionId })
@@ -216,7 +233,7 @@ export namespace KiloSessions {
await Storage.write(["session_share", sessionId], result)
void fullSync(sessionId).catch((error) => log.error("share full sync failed", { sessionId, error }))
log.info("session bootstrap completed", { sessionId })
return result
}
+45 -1
View File
@@ -1,5 +1,11 @@
import { test, expect } from "bun:test"
import { parseShareUrl, transformShareData, type ShareData } from "../../src/cli/cmd/import"
import {
parseShareUrl,
transformShareData,
bootstrapImportedSessionIngest,
ingestBootstrapWarning,
type ShareData,
} from "../../src/cli/cmd/import"
// parseShareUrl tests
test("parses valid share URLs", () => {
@@ -36,3 +42,41 @@ test("returns null for invalid share data", () => {
expect(transformShareData([{ type: "message", data: {} as any }])).toBeNull()
expect(transformShareData([{ type: "session", data: { id: "s" } as any }])).toBeNull() // no messages
})
test("formats ingest bootstrap warning", () => {
expect(ingestBootstrapWarning("session-123", new Error("network failed"))).toContain("session-123")
expect(ingestBootstrapWarning("session-123", new Error("network failed"))).toContain("network failed")
expect(ingestBootstrapWarning("session-123", "oops")).toContain("oops")
})
test("bootstrapImportedSessionIngest runs bootstrap and does not warn on success", async () => {
const calls: string[] = []
const warnings: string[] = []
await bootstrapImportedSessionIngest("session-success", {
bootstrap: async (sessionId) => {
calls.push(sessionId)
},
warn: (message) => warnings.push(message),
})
expect(calls).toEqual(["session-success"])
expect(warnings).toHaveLength(0)
})
test("bootstrapImportedSessionIngest warns and continues on failure", async () => {
const warnings: string[] = []
await expect(
bootstrapImportedSessionIngest("session-fail", {
bootstrap: async () => {
throw new Error("boom")
},
warn: (message) => warnings.push(message),
}),
).resolves.toBeUndefined()
expect(warnings).toHaveLength(1)
expect(warnings[0]).toContain("session-fail")
expect(warnings[0]).toContain("boom")
})