From 4104ab59d9cc4bcf4643afbe1f71174d754c4e0e Mon Sep 17 00:00:00 2001 From: maphew Date: Sun, 14 Jun 2026 15:34:18 -0700 Subject: [PATCH] fix(cli): import cloud sessions before validation --- .changeset/cloud-fork-session-import.md | 5 ++ packages/opencode/src/cli/cmd/tui/thread.ts | 26 ++++---- .../src/kilocode/cli/cmd/tui/thread.ts | 8 +-- .../test/kilocode/cli/tui/thread.test.ts | 63 ++++++++++++++++++- 4 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 .changeset/cloud-fork-session-import.md diff --git a/.changeset/cloud-fork-session-import.md b/.changeset/cloud-fork-session-import.md new file mode 100644 index 0000000000..a8fd70504f --- /dev/null +++ b/.changeset/cloud-fork-session-import.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Fix cloud session fork commands so they import cloud sessions before validating the local session. diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 0a8c34865c..e4fa4263e3 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -310,19 +310,6 @@ export const TuiThreadCommand = cmd({ events: createEventSource(client), } - try { - await validateSession({ - url: transport.url, // kilocode_change - sessionID: args.session, - directory: cwd, - fetch: transport.fetch, - }) - } catch (error) { - UI.error(errorMessage(error)) - process.exitCode = 1 - return - } - setTimeout(() => { client.call("checkUpgrade", { directory: cwd }).catch(() => {}) }, 1000).unref?.() @@ -347,6 +334,19 @@ export const TuiThreadCommand = cmd({ } // kilocode_change end + try { + await validateSession({ + url: transport.url, // kilocode_change + sessionID: args.session, + directory: cwd, + fetch: transport.fetch, + }) + } catch (error) { + UI.error(errorMessage(error)) + process.exitCode = 1 + return + } + await start({ // kilocode_change - shared lazy loader also supports daemon attach url: transport.url, diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/thread.ts b/packages/opencode/src/kilocode/cli/cmd/tui/thread.ts index 053ed5f742..a6e84f1a78 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/kilocode/cli/cmd/tui/thread.ts @@ -51,10 +51,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: input.args.session, + sessionID: fork.id, directory: input.cwd, headers: daemon.headers, }) @@ -64,9 +67,6 @@ export namespace KiloTuiThreadDaemon { return true } - const fork = await session(input, daemon) - if (!fork.ok) return true - await input.start({ url: daemon.url, config, diff --git a/packages/opencode/test/kilocode/cli/tui/thread.test.ts b/packages/opencode/test/kilocode/cli/tui/thread.test.ts index 5cb6e89fe3..cd093b5d71 100644 --- a/packages/opencode/test/kilocode/cli/tui/thread.test.ts +++ b/packages/opencode/test/kilocode/cli/tui/thread.test.ts @@ -1,9 +1,13 @@ -import { describe, expect, test } from "bun:test" +import { afterEach, describe, expect, mock, test } from "bun:test" import fs from "fs/promises" import path from "path" import { tmpdir } from "../../../fixture/fixture" import { resolveThreadDirectory } from "../../../../src/cli/cmd/tui/thread" +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() @@ -12,4 +16,61 @@ describe("kilo tui thread", () => { expect(resolveThreadDirectory(".", root.path, pkg)).toBe(pkg) }) + + 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"]) + }) })