Merge pull request #11223 from maphew/fix/cli-cloud-fork-session-import

fix(cli): import cloud sessions before validation
This commit is contained in:
Catriel Müller
2026-07-06 14:44:53 -03:00
committed by GitHub
4 changed files with 89 additions and 22 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---
Fix cloud session fork commands so they import cloud sessions before validating the local session.
+14 -14
View File
@@ -320,20 +320,6 @@ export const TuiThreadCommand = cmd({
events: createEventSource(client),
}
try {
await validateSession({
url: transport.url, // kilocode_change
sessionID: localSessionID(args), // kilocode_change
directory: cwd,
fetch: transport.fetch,
headers: transport.headers, // kilocode_change
})
} catch (error) {
UI.error(errorMessage(error))
process.exitCode = 1
return
}
setTimeout(() => {
client.call("checkUpgrade", { directory: cwd }).catch(() => {})
}, 1000).unref?.()
@@ -359,6 +345,20 @@ export const TuiThreadCommand = cmd({
}
// kilocode_change end
try {
await validateSession({
url: transport.url, // kilocode_change
sessionID: localSessionID(args), // kilocode_change
directory: cwd,
fetch: transport.fetch,
headers: transport.headers, // kilocode_change
})
} catch (error) {
UI.error(errorMessage(error))
process.exitCode = 1
return
}
// kilocode_change start
await start({
// kilocode_change - shared lazy loader also supports daemon attach
@@ -6,7 +6,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
import { errorMessage } from "@/util/error"
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
import { validateSession } from "@/cli/cmd/tui/validate-session"
import { importCloudSession, localSessionID } from "@/kilocode/cloud-session"
import { importCloudSession } from "@/kilocode/cloud-session"
import { DaemonClient } from "@/kilocode/daemon/client"
import { createKiloClient } from "@kilocode/sdk/v2"
@@ -67,10 +67,13 @@ export namespace KiloTuiThreadDaemon {
const prompt = await input.input()
const config = await TuiConfig.get()
const fork = await session(input, daemon)
if (!fork.ok) return true
try {
await validateSession({
url: daemon.url,
sessionID: localSessionID(input.args),
sessionID: fork.id,
directory: input.cwd,
headers: daemon.headers,
})
@@ -80,9 +83,6 @@ export namespace KiloTuiThreadDaemon {
return true
}
const fork = await session(input, daemon)
if (!fork.ok) return true
await input.start({
url: daemon.url,
config,
@@ -1,4 +1,4 @@
import { describe, expect, spyOn, test } from "bun:test"
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { tmpdir } from "../../../fixture/fixture"
@@ -6,6 +6,10 @@ import { resolveThreadDirectory } from "../../../../src/cli/cmd/tui/thread"
import { KiloTuiThreadDaemon } from "../../../../src/kilocode/cli/cmd/tui/thread"
import { DaemonClient } from "../../../../src/kilocode/daemon/client"
afterEach(() => {
mock.restore()
})
describe("kilo tui thread", () => {
test("ignores stale PWD after cwd is changed by a process wrapper", async () => {
await using root = await tmpdir()
@@ -31,7 +35,7 @@ describe("kilo tui thread", () => {
}
})
test("skips local validation before importing cloud sessions", async () => {
test("validates imported daemon session over HTTP after importing from cloud", async () => {
await using root = await tmpdir()
const cloud = "ses_cloud"
const local = "ses_local"
@@ -43,6 +47,7 @@ describe("kilo tui thread", () => {
const route = `${request.method} ${new URL(request.url).pathname}`
calls.push(route)
if (route === "POST /kilo/cloud/session/import") return Response.json({ id: local })
if (route === `GET /session/${local}`) return Response.json({ id: local })
return new Response(null, { status: 404 })
},
})
@@ -76,10 +81,67 @@ describe("kilo tui thread", () => {
start,
})
expect(calls).toEqual(["POST /kilo/cloud/session/import"])
expect(calls).toEqual(["POST /kilo/cloud/session/import", `GET /session/${local}`])
expect(opened).toEqual([local])
} finally {
daemon.mockRestore()
}
})
test("imports cloud fork before validating daemon session", async () => {
const seen: string[] = []
const started: string[] = []
mock.module("@kilocode/sdk/v2", () => ({
createKiloClient: () => ({
kilo: {
cloud: {
session: {
import: async (input: { sessionId: string }) => {
expect(input.sessionId).toBe("ses_cloud")
return { data: { id: "ses_local" } }
},
},
},
},
}),
}))
mock.module("@/cli/cmd/tui/validate-session", () => ({
validateSession: async (input: { sessionID?: string }) => {
seen.push(input.sessionID ?? "")
},
}))
mock.module("@/cli/cmd/tui/config/tui", () => ({
TuiConfig: {
get: async () => ({}),
},
}))
mock.module("@/kilocode/daemon/client", () => ({
DaemonClient: {
maybe: async () => ({ url: "http://127.0.0.1:4096", headers: {} }),
},
}))
mock.module("@/cli/ui", () => ({
UI: {
println: () => {},
error: () => {},
},
}))
const key = JSON.stringify({ time: Date.now(), rand: Math.random() })
const mod = await import(`../../../../src/kilocode/cli/cmd/tui/thread?${key}`)
const handled = await mod.KiloTuiThreadDaemon.attach({
args: { session: "ses_cloud", cloudFork: true },
cwd: "/tmp/project",
input: async () => undefined,
start: async (input: { args: { sessionID?: string } }) => {
started.push(input.args.sessionID ?? "")
},
})
expect(handled).toBe(true)
expect(seen).toEqual(["ses_local"])
expect(started).toEqual(["ses_local"])
})
})