Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
5862235
add kilo token
iscekic Nov 18, 2025
9aaa3e8
emit events on file save
iscekic Nov 18, 2025
9601e5e
correctly forward events
iscekic Nov 18, 2025
2841163
pipe event from extension to cli
iscekic Nov 18, 2025
a0eca0a
remove debug logging
iscekic Nov 18, 2025
28f54b6
revert lefotvers from experimenting
iscekic Nov 18, 2025
ce1c145
add trpcClient service
iscekic Nov 18, 2025
c185474
add session service
iscekic Nov 18, 2025
3ba8272
init trpc service
iscekic Nov 18, 2025
239ddd7
rename sessionService -> sessionClient
iscekic Nov 18, 2025
fb68b58
add session service
iscekic Nov 18, 2025
6eb51b6
improve SessionService
iscekic Nov 18, 2025
db23cb8
update tests
iscekic Nov 18, 2025
0e15822
improve logging
iscekic Nov 18, 2025
3c8b126
check session existence before restoring
iscekic Nov 19, 2025
bf0e6e6
correctly revive session files
iscekic Nov 19, 2025
c27b595
restore extension state wip
iscekic Nov 19, 2025
dc185aa
correctly mock history item
iscekic Nov 19, 2025
6fe5588
fix ordering when initializing services
iscekic Nov 19, 2025
46eae2b
skip reviving checkpoint messages
iscekic Nov 19, 2025
bc5eace
revive session automatically
iscekic Nov 19, 2025
22f6eff
display user-facing message
iscekic Nov 19, 2025
9061d4c
update tests
iscekic Nov 19, 2025
8a16865
add sessionId atom
iscekic Nov 19, 2025
c6afc27
add session command
iscekic Nov 19, 2025
132877e
kiloToken fallback
iscekic Nov 19, 2025
d1fb485
add /session commands
iscekic Nov 19, 2025
4488278
support fetching blobs instead of getting them directly via backend
iscekic Nov 19, 2025
10c02ae
fix race condition
iscekic Nov 19, 2025
e01269b
fix compat errors after server update
iscekic Nov 19, 2025
dc00ab9
fix failing tests
iscekic Nov 19, 2025
6be81e9
more failing tests
iscekic Nov 19, 2025
c777bdc
adjust for backend field rename
iscekic Nov 20, 2025
cf6ed3e
add changeset
iscekic Nov 20, 2025
2e5eda4
fix tests
iscekic Nov 20, 2025
006e58a
add // kilocode_change markers
iscekic Nov 20, 2025
3980b22
improve kiloToken fallback
iscekic Nov 20, 2025
a3dfc16
add autocomplete support
iscekic Nov 20, 2025
da62328
use search endpoint
iscekic Nov 20, 2025
c92182b
add new endpoints
iscekic Nov 21, 2025
61d0f9d
add /session share command
iscekic Nov 21, 2025
c70363f
update message
iscekic Nov 21, 2025
5c626ad
set correct git dir
iscekic Nov 21, 2025
b572865
resolve kiloToken before deep merge
iscekic Nov 21, 2025
0505016
streamline id -> session_id
iscekic Nov 21, 2025
ea05fde
Merge branch 'main' into add-session-support
iscekic Nov 21, 2025
bd59428
reset session on /new
iscekic Nov 21, 2025
91412cf
improve /session autocomplete
iscekic Nov 21, 2025
efbc845
adjust to api changes
iscekic Nov 24, 2025
0acdc6d
adjust for api changes
iscekic Nov 24, 2025
669a52c
e2e flow wip
iscekic Nov 24, 2025
7a6e473
remove atom
iscekic Nov 24, 2025
07d5cfc
restore git state wip
iscekic Nov 24, 2025
140cf1b
handle first commit edge case
iscekic Nov 24, 2025
85be7c6
fix tests
iscekic Nov 24, 2025
a2ebe05
Merge branch 'main' into add-session-support
iscekic Nov 24, 2025
72f2442
fix copilot remarks
iscekic Nov 24, 2025
2e11a76
use --3way for patch applying
iscekic Nov 24, 2025
db8d11d
safer stash operations
iscekic Nov 24, 2025
3e36866
unstage files after applying patch
iscekic Nov 24, 2025
90357d7
try to avoid detached HEAD
iscekic Nov 24, 2025
3e5a5a2
use applyPatch
iscekic Nov 24, 2025
69e8ed6
log session_created message in json mode
iscekic Nov 25, 2025
84e0317
show untracked files in git diff
iscekic Nov 25, 2025
9338487
set session title based on first ui message
iscekic Nov 25, 2025
45f6618
add /session rename
iscekic Nov 25, 2025
22cdf48
session select improvements
iscekic Nov 25, 2025
5bbefbc
expose singleCompletionHandler
iscekic Nov 26, 2025
208314c
get session title from llm
iscekic Nov 26, 2025
03d2bf6
don't block on generating session title
iscekic Nov 26, 2025
b54b5b6
typo
iscekic Nov 26, 2025
8a720da
Merge branch 'main' into add-session-support
iscekic Nov 26, 2025
9c345fb
fix failing tests
iscekic Nov 26, 2025
957544a
merge test files
iscekic Nov 26, 2025
d6696e8
extract utility fn
iscekic Nov 26, 2025
ec9e76a
replace emoji with asterisk
iscekic Nov 26, 2025
6857634
extract sync interval variable
iscekic Nov 26, 2025
75e23ba
remove redundant getter/setter pair
iscekic Nov 26, 2025
46cfd0d
several small improvements
iscekic Nov 26, 2025
08fbd48
move item for easier merges
iscekic Nov 26, 2025
8d94ec2
utilize separate blob upload endpoint
iscekic Nov 26, 2025
13d6d03
conditionally update session
iscekic Nov 26, 2025
30fb900
selectively upload changed blobs instead of all
iscekic Nov 26, 2025
2728436
use actual hash for git blob
iscekic Nov 26, 2025
2af296b
update test
iscekic Nov 26, 2025
76f3ed0
reorder for readability
iscekic Nov 26, 2025
499902d
small edge case fixes
iscekic Nov 26, 2025
e20e764
Merge branch 'main' into add-session-support
iscekic Nov 26, 2025
88c9ffa
have --continue restore latest session
iscekic Nov 26, 2025
efa0192
reduce sync interval
iscekic Nov 26, 2025
0fe9efe
fix test
iscekic Nov 26, 2025
4e3a14c
consistent behavior between --continue and --session
iscekic Nov 26, 2025
812172f
address PR remarks
iscekic Nov 27, 2025
2d75466
update tests
iscekic Nov 27, 2025
23c571b
Merge branch 'main' into add-session-support
iscekic Nov 27, 2025
6c623ee
get kilo token from kilo gateway provider
iscekic Nov 27, 2025
2bbaa64
add created_on_platform
iscekic Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/deep-peaches-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@kilocode/cli": minor
"kilo-code": minor
---

add sessions support
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ Note that available settings vary by provider and model. Each provider offers di
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-2.png" alt="Provider selection dropdown" width="550" />
- Enter API key

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API key entry field" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API key entry field" width="550" />

- Choose a model

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="Model selection interface" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="Model selection interface" width="550" />

- Adjust model parameters

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="Model parameter adjustment controls" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="Model parameter adjustment controls" width="550" />

### Switching Profiles

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ API 配置配置文件允许您创建和切换不同的 AI 设置集。每个配

- 选择您的 API 提供商

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-2.png" alt="提供商选择下拉菜单" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-2.png" alt="提供商选择下拉菜单" width="550" />

- 输入 API 密钥

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API 密钥输入字段" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API 密钥输入字段" width="550" />

- 选择模型

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="模型选择界面" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="模型选择界面" width="550" />

- 调整模型参数

<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="模型参数调整控件" width="550" />
<img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="模型参数调整控件" width="550" />

### 切换配置文件

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Kilo Code 使用一个影子 Git 仓库(独立于您的主版本控制系统

- **恢复文件和任务** - 恢复工作区文件并删除所有后续对话消息。当您希望将代码和对话完全重置回检查点的时间点时使用。此选项需要在对话框中进行确认,因为它无法撤消。

<img src="/docs/img/checkpoints/checkpoints-9.png" alt="恢复文件和任务检查点的确认对话框" width="300" />
<img src="/docs/img/checkpoints/checkpoints-9.png" alt="恢复文件和任务检查点的确认对话框" width="300" />

### 限制和注意事项

Expand Down
52 changes: 47 additions & 5 deletions cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { requestRouterModelsAtom } from "./state/atoms/actions.js"
import { loadHistoryAtom } from "./state/atoms/history.js"
import { taskHistoryDataAtom, updateTaskHistoryFiltersAtom } from "./state/atoms/taskHistory.js"
import { sendWebviewMessageAtom } from "./state/atoms/actions.js"
import { taskResumedViaContinueAtom } from "./state/atoms/extension.js"
import { taskResumedViaContinueOrSessionAtom } from "./state/atoms/extension.js"
import { getTelemetryService, getIdentityManager } from "./services/telemetry/index.js"
import { notificationsAtom, notificationsErrorAtom, notificationsLoadingAtom } from "./state/atoms/notifications.js"
import { fetchKilocodeNotifications } from "./utils/notifications.js"
Expand All @@ -24,6 +24,9 @@ import type { CLIOptions } from "./types/cli.js"
import type { CLIConfig, ProviderConfig } from "./config/types.js"
import { getModelIdKey } from "./constants/providers/models.js"
import type { ProviderName } from "./types/messages.js"
import { TrpcClient } from "./services/trpcClient.js"
import { SessionService } from "./services/session.js"
import { getKiloToken } from "./config/persistence.js"

/**
* Main application class that orchestrates the CLI lifecycle
Expand All @@ -34,6 +37,7 @@ export class CLI {
private ui: Instance | null = null
private options: CLIOptions
private isInitialized = false
private sessionService: SessionService | null = null

constructor(options: CLIOptions = {}) {
this.options = options
Expand Down Expand Up @@ -126,6 +130,31 @@ export class CLI {
// Track successful extension initialization
telemetryService.trackExtensionInitialized(true)

// Initialize services and restore session if kiloToken is available
// This must happen AFTER ExtensionService initialization to allow webview messages
const kiloToken = getKiloToken(config)

if (kiloToken) {
TrpcClient.init(kiloToken)
logs.debug("TrpcClient initialized with kiloToken", "CLI")

this.sessionService = SessionService.init(this.service, this.store, this.options.json)
logs.debug("SessionService initialized with ExtensionService", "CLI")

// Set workspace directory for git operations (important for parallel mode/worktrees)
const workspace = this.options.workspace || process.cwd()
this.sessionService.setWorkspaceDirectory(workspace)
logs.debug("SessionService workspace directory set", "CLI", { workspace })

if (this.options.session) {
await this.sessionService.restoreSession(this.options.session)
} else if (this.options.fork) {
logs.info("Forking session from share ID", "CLI", { shareId: this.options.fork })

await this.sessionService.forkSession(this.options.fork)
}
}

// Load command history
await this.store.set(loadHistoryAtom)
logs.debug("Command history loaded", "CLI")
Expand Down Expand Up @@ -273,6 +302,8 @@ export class CLI {
try {
logs.info("Disposing Kilo Code CLI...", "CLI")

await this.sessionService?.destroy()

// Signal codes take precedence over CI logic
if (signal === "SIGINT") {
exitCode = 130
Expand Down Expand Up @@ -432,8 +463,21 @@ export class CLI {
try {
logs.info("Attempting to resume last conversation", "CLI", { workspace })

// First, try to restore from persisted session ID if kiloToken is available
if (this.sessionService) {
const restored = await this.sessionService.restoreLastSession()
if (restored) {
return
}

logs.debug("Falling back to task history", "CLI")
}

// Fallback: Use task history approach
logs.debug("Using task history fallback to resume conversation", "CLI")

// Update filters to current workspace and newest sort
await this.store.set(updateTaskHistoryFiltersAtom, {
this.store.set(updateTaskHistoryFiltersAtom, {
workspace: "current",
sort: "newest",
favoritesOnly: false,
Expand Down Expand Up @@ -461,7 +505,6 @@ export class CLI {
logs.warn("No previous tasks found for workspace", "CLI", { workspace })
console.error("\nNo previous tasks found for this workspace. Please start a new conversation.\n")
process.exit(1)
return // TypeScript doesn't know process.exit stops execution
}

// Find the most recent task (first in the list since we sorted by newest)
Expand All @@ -471,7 +514,6 @@ export class CLI {
logs.warn("No valid task found in history", "CLI", { workspace })
console.error("\nNo valid task found to resume. Please start a new conversation.\n")
process.exit(1)
return
}

logs.debug("Found last task", "CLI", { taskId: lastTask.id, task: lastTask.task })
Expand All @@ -483,7 +525,7 @@ export class CLI {
})

// Mark that the task was resumed via --continue to prevent showing "Task ready to resume" message
this.store.set(taskResumedViaContinueAtom, true)
this.store.set(taskResumedViaContinueOrSessionAtom, true)

logs.info("Task resume initiated", "CLI", { taskId: lastTask.id, task: lastTask.task })
} catch (error) {
Expand Down
46 changes: 44 additions & 2 deletions cli/src/commands/__tests__/new.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@
* Tests for /new command
*/

import { describe, it, expect, vi, beforeEach } from "vitest"
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { newCommand } from "../new.js"
import type { CommandContext } from "../core/types.js"
import { createMockContext } from "./helpers/mockContext.js"
import { SessionService } from "../../services/session.js"

describe("/new command", () => {
let mockContext: CommandContext
let mockSessionService: Partial<SessionService> & { destroy: ReturnType<typeof vi.fn> }

beforeEach(() => {
// Mock process.stdout.write to capture terminal clearing
vi.spyOn(process.stdout, "write").mockImplementation(() => true)

mockContext = createMockContext({
input: "/new",
})

// Mock SessionService
mockSessionService = {
destroy: vi.fn().mockResolvedValue(undefined),
sessionId: "test-session-id",
}

// Mock SessionService.init to return our mock
vi.spyOn(SessionService, "init").mockReturnValue(mockSessionService as unknown as SessionService)
})

afterEach(() => {
vi.restoreAllMocks()
})

describe("Command metadata", () => {
Expand Down Expand Up @@ -55,6 +71,27 @@ describe("/new command", () => {
expect(mockContext.clearTask).toHaveBeenCalledTimes(1)
})

it("should clear the session", async () => {
await newCommand.handler(mockContext)

expect(SessionService.init).toHaveBeenCalled()
expect(mockSessionService.destroy).toHaveBeenCalledTimes(1)
})

it("should continue execution even if session clearing fails", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
mockSessionService.destroy.mockRejectedValue(new Error("Session error"))

await newCommand.handler(mockContext)

// Should still clear task and replace messages despite session error
expect(mockContext.clearTask).toHaveBeenCalled()
expect(mockContext.replaceMessages).toHaveBeenCalled()
expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to clear session:", expect.any(Error))

consoleErrorSpy.mockRestore()
})

it("should replace CLI messages with welcome message", async () => {
await newCommand.handler(mockContext)

Expand Down Expand Up @@ -84,14 +121,18 @@ describe("/new command", () => {
callOrder.push("clearTask")
})

mockSessionService.destroy = vi.fn().mockImplementation(async () => {
callOrder.push("sessionDestroy")
})

mockContext.replaceMessages = vi.fn().mockImplementation(() => {
callOrder.push("replaceMessages")
})

await newCommand.handler(mockContext)

// Operations should execute in this order
expect(callOrder).toEqual(["clearTask", "replaceMessages"])
expect(callOrder).toEqual(["clearTask", "sessionDestroy", "replaceMessages"])
})

it("should handle clearTask errors gracefully", async () => {
Expand Down Expand Up @@ -123,6 +164,7 @@ describe("/new command", () => {

// Verify all cleanup operations were performed
expect(mockContext.clearTask).toHaveBeenCalled()
expect(mockSessionService.destroy).toHaveBeenCalled()
expect(mockContext.replaceMessages).toHaveBeenCalled()

// Verify welcome message was replaced
Expand Down
Loading