Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## [v5.1.0] - 2026-01-12

### Added

- Line counter tracking for file edit reviews (lines added, updated, deleted)
- Server integration to report line change statistics
- Automatic language detection from edited files for analytics
- `MatterProgressIndicator` component with animated loading dots
- "View Diff" button in file edit review to view pending changes in VS Code editor
- Elapsed time tracking for reasoning blocks (shows "Thinking for Xs" during streaming)
- "Thought" translation key for completed reasoning blocks

### Changed

- Enhanced file edit review accept all functionality with detailed metrics
- Improved webview message handling for file edit operations
- Updated "API Request..." label to "Generating..." for better UX
- Fixed sendingDisabled behavior when canceling tasks or aborting commands
- Enhanced reasoning block UI with time display and collapse functionality

### Fixed

- Removed debug console.log statement from kilo config loading
- Fixed unused parameter warning in ReasoningBlock component

---

## [v5.0.3] - 2026-01-11

### Added
Expand Down
1 change: 0 additions & 1 deletion src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3027,7 +3027,6 @@ ${prompt}
if (this._kiloConfig === null) {
const { repositoryUrl } = await this.getGitProperties()
this._kiloConfig = await getKilocodeConfig(this.cwd, repositoryUrl)
console.log("getKiloConfig", this._kiloConfig)
}
return this._kiloConfig
}
Expand Down
132 changes: 130 additions & 2 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
checkoutDiffPayloadSchema,
checkoutRestorePayloadSchema,
} from "../../shared/WebviewMessage"
import type { WebviewMessage as WebviewMessageType } from "../../shared/WebviewMessage"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { experimentDefault } from "../../shared/experiments"
import { Terminal } from "../../integrations/terminal/Terminal"
Expand Down Expand Up @@ -976,6 +977,48 @@ export const webviewMessageHandler = async (
} as any)
break
}
// kilocode_change start: View pending file diffs in VS Code diff view
case "viewPendingFileDiffs" as any: {
const currentTask = provider.getCurrentTask()

if (!currentTask) {
break
}

const pendingEdits = currentTask.fileEditReviewController.getPendingEdits()

if (pendingEdits.size === 0) {
break
}

// Open each file with pending edits in diff view
for (const [readablePath, edit] of pendingEdits.entries()) {
try {
const fileName = path.basename(edit.absolutePath)
const fileUri = vscode.Uri.file(edit.absolutePath)

// Create a URI for the original content using the diff view scheme
const originalUri = vscode.Uri.parse(`cline-diff:${fileName}`).with({
query: Buffer.from(edit.originalContent).toString("base64"),
})
Comment on lines +1001 to +1003
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Code Quality

Issue: Using vscode.Uri.parse with a constructed string cline-diff:${fileName} is unsafe if fileName contains spaces or special characters that are not URL-encoded.

Fix: Use vscode.Uri.from which handles path encoding correctly.

Impact: Ensures reliable URI creation for files with special characters.

Suggested change
const originalUri = vscode.Uri.parse(`cline-diff:${fileName}`).with({
query: Buffer.from(edit.originalContent).toString("base64"),
})
// Create a URI for the original content using the diff view scheme
const originalUri = vscode.Uri.from({
scheme: "cline-diff",
path: fileName,
query: Buffer.from(edit.originalContent).toString("base64"),
})


// Open diff view between original and current content
await vscode.commands.executeCommand(
"vscode.diff",
originalUri,
fileUri,
`${fileName}: Original ↔ Axon Code's Changes`,
{ preserveFocus: true },
)
} catch (error) {
provider.log(
`Failed to open diff for ${readablePath}: ${error instanceof Error ? error.message : "Unknown error"}`,
)
}
}
break
}
// kilocode_change end
// kilocode_change start: Get Git changes for AI Code Review (separate from pending file edits)
case "getGitChangesForReview": {
try {
Expand Down Expand Up @@ -1453,9 +1496,94 @@ ${comment.suggestion}
await seeNewChanges(task, (message.payload as SeeNewChangesPayload).commitRange)
}
break
case "fileEditReviewAcceptAll":
await vscode.commands.executeCommand("axon-code.fileEdit.acceptAll")
case "fileEditReviewAcceptAll": {
const currentTask = provider.getCurrentTask()
if (!currentTask) {
break
}

// Execute the accept all command and get line counters
const lineCounters = await vscode.commands.executeCommand<
{ linesAdded: number; linesUpdated: number; linesDeleted: number } | undefined
>("axon-code.fileEdit.acceptAll")

// Send line counters to server if we have data
if (
lineCounters &&
(lineCounters.linesAdded > 0 || lineCounters.linesUpdated > 0 || lineCounters.linesDeleted > 0)
) {
try {
// Get state to retrieve kilocodeToken
const state = await provider.getState()
const kilocodeToken = state.apiConfiguration?.kilocodeToken

if (!kilocodeToken) {
provider.log("KiloCode token not found, skipping line counter update")
break
}

// Get git repository info for x-axon-repo header
const { getGitRepositoryInfo } = await import("../../utils/git")
const gitInfo = await getGitRepositoryInfo(provider.cwd)
const repo = gitInfo.repositoryUrl || path.basename(provider.cwd)

// Get the primary language from the first edited file
const pendingEdits = currentTask.fileEditReviewController.getPendingEdits()
let language = "ts" // default
if (pendingEdits.size > 0) {
const firstFile = Array.from(pendingEdits.keys())[0]
const ext = path.extname(firstFile).toLowerCase()
const languageMap: Record<string, string> = {
".ts": "ts",
".tsx": "tsx",
".js": "js",
".jsx": "jsx",
".py": "py",
".java": "java",
".go": "go",
".rs": "rs",
".cpp": "cpp",
".c": "c",
".cs": "cs",
".php": "php",
".rb": "rb",
".swift": "swift",
".kt": "kt",
".dart": "dart",
".vue": "vue",
".svelte": "svelte",
}
language = languageMap[ext] || "ts"
}
Comment on lines +1505 to +1557
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Logic Error

Issue: pendingEdits are retrieved after axon-code.fileEdit.acceptAll command execution. Since the accept command clears the pending edits in FileEditReviewController, pendingEdits will always be empty here, causing the language detection to always default to "ts".

Fix: Retrieve the pending edits and determine the language before executing the accept command.

Impact: Incorrect language reporting in metrics.

Suggested change
// Execute the accept all command and get line counters
const lineCounters = await vscode.commands.executeCommand<
{ linesAdded: number; linesUpdated: number; linesDeleted: number } | undefined
>("axon-code.fileEdit.acceptAll")
// Send line counters to server if we have data
if (
lineCounters &&
(lineCounters.linesAdded > 0 || lineCounters.linesUpdated > 0 || lineCounters.linesDeleted > 0)
) {
try {
// Get state to retrieve kilocodeToken
const state = await provider.getState()
const kilocodeToken = state.apiConfiguration?.kilocodeToken
if (!kilocodeToken) {
provider.log("KiloCode token not found, skipping line counter update")
break
}
// Get git repository info for x-axon-repo header
const { getGitRepositoryInfo } = await import("../../utils/git")
const gitInfo = await getGitRepositoryInfo(provider.cwd)
const repo = gitInfo.repositoryUrl || path.basename(provider.cwd)
// Get the primary language from the first edited file
const pendingEdits = currentTask.fileEditReviewController.getPendingEdits()
let language = "ts" // default
if (pendingEdits.size > 0) {
const firstFile = Array.from(pendingEdits.keys())[0]
const ext = path.extname(firstFile).toLowerCase()
const languageMap: Record<string, string> = {
".ts": "ts",
".tsx": "tsx",
".js": "js",
".jsx": "jsx",
".py": "py",
".java": "java",
".go": "go",
".rs": "rs",
".cpp": "cpp",
".c": "c",
".cs": "cs",
".php": "php",
".rb": "rb",
".swift": "swift",
".kt": "kt",
".dart": "dart",
".vue": "vue",
".svelte": "svelte",
}
language = languageMap[ext] || "ts"
}
// Get the primary language from the first edited file before they are cleared
const pendingEdits = currentTask.fileEditReviewController.getPendingEdits()
let language = "ts" // default
if (pendingEdits.size > 0) {
const firstFile = Array.from(pendingEdits.keys())[0]
const ext = path.extname(firstFile).toLowerCase()
const languageMap: Record<string, string> = {
".ts": "ts",
".tsx": "tsx",
".js": "js",
".jsx": "jsx",
".py": "py",
".java": "java",
".go": "go",
".rs": "rs",
".cpp": "cpp",
".c": "c",
".cs": "cs",
".php": "php",
".rb": "rb",
".swift": "swift",
".kt": "kt",
".dart": "dart",
".vue": "vue",
".svelte": "svelte",
}
language = languageMap[ext] || "ts"
}
// Execute the accept all command and get line counters
const lineCounters = await vscode.commands.executeCommand<
{ linesAdded: number; linesUpdated: number; linesDeleted: number } | undefined
>("axon-code.fileEdit.acceptAll")
// Send line counters to server if we have data
if (
lineCounters &&
(lineCounters.linesAdded > 0 || lineCounters.linesUpdated > 0 || lineCounters.linesDeleted > 0)
) {
try {
// Get state to retrieve kilocodeToken
const state = await provider.getState()
const kilocodeToken = state.apiConfiguration?.kilocodeToken
if (!kilocodeToken) {
provider.log("KiloCode token not found, skipping line counter update")
break
}
// Get git repository info for x-axon-repo header
const { getGitRepositoryInfo } = await import("../../utils/git")
const gitInfo = await getGitRepositoryInfo(provider.cwd)
const repo = gitInfo.repositoryUrl || path.basename(provider.cwd)


// Send POST request to update line counters
const url = `https://api.matterai.so/axoncode/meta/${currentTask.taskId}/lines`
const response = await fetch(url, {
method: "POST",
Comment on lines +1560 to +1562
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Robustness

Issue: The fetch request lacks a timeout configuration. If the API endpoint hangs, this operation could remain pending indefinitely.

Fix: Add an AbortSignal with a reasonable timeout (e.g., 10 seconds).

Impact: Prevents potential resource leaks and hanging operations.

Suggested change
const url = `https://api.matterai.so/axoncode/meta/${currentTask.taskId}/lines`
const response = await fetch(url, {
method: "POST",
// Send POST request to update line counters
const url = `https://api.matterai.so/axoncode/meta/${currentTask.taskId}/lines`
const response = await fetch(url, {
signal: AbortSignal.timeout(10000),
method: "POST",

headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${kilocodeToken}`,
"X-AXON-REPO": repo,
},
body: JSON.stringify({
language,
linesAdded: lineCounters.linesAdded,
linesUpdated: lineCounters.linesUpdated,
linesDeleted: lineCounters.linesDeleted,
}),
})

if (!response.ok) {
provider.log(`Failed to update line counters: ${response.statusText}`)
}
} catch (error) {
provider.log(
`Error updating line counters: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
break
}
case "fileEditReviewRejectAll":
await vscode.commands.executeCommand("axon-code.fileEdit.rejectAll")
break
Expand Down
44 changes: 42 additions & 2 deletions src/integrations/editor/FileEditReviewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,53 @@ export class FileEditReviewController implements vscode.Disposable {
}
}

async handleAcceptAll() {
if (this.pendingEdits.size === 0) return
async handleAcceptAll(): Promise<{ linesAdded: number; linesUpdated: number; linesDeleted: number } | undefined> {
if (this.pendingEdits.size === 0) return undefined

// Calculate line counters before clearing
let linesAdded = 0
let linesUpdated = 0
let linesDeleted = 0

for (const edit of this.pendingEdits.values()) {
for (const editEntry of edit.edits) {
const beforeLines = editEntry.originalContent ? editEntry.originalContent.split("\n") : []
const afterLines = editEntry.newContent ? editEntry.newContent.split("\n") : []

if (beforeLines.length === 0) {
// New content added
linesAdded += afterLines.length
} else if (afterLines.length === 0) {
// Content deleted
linesDeleted += beforeLines.length
} else {
// Modified content - count changed lines
const commonLength = Math.min(beforeLines.length, afterLines.length)
let changedInCommon = 0
for (let i = 0; i < commonLength; i++) {
if (beforeLines[i] !== afterLines[i]) {
changedInCommon++
}
}
linesUpdated += changedInCommon

// Account for added/deleted lines beyond common length
const diff = afterLines.length - beforeLines.length
if (diff > 0) {
linesAdded += diff
} else if (diff < 0) {
linesDeleted += Math.abs(diff)
}
}
}
}

this.pendingEdits.clear()
this.reviewQueue = []
this.refreshDecorations()
this.codeLensEmitter.fire()

return { linesAdded, linesUpdated, linesDeleted }
}

async handleRejectAll() {
Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "matterai",
"version": "5.0.3",
"version": "5.1.0",
"icon": "assets/icons/matterai-ic.png",
"galleryBanner": {
"color": "#FFFFFF",
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export interface WebviewMessage {
| "commitChanges"
| "getPendingFileEdits"
| "pendingFileEdits"
| "viewPendingFileDiffs"
| "requestCodeReview"
| "codeReviewResults"
| "applyCodeReviewFix"
Expand Down
7 changes: 4 additions & 3 deletions webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { CommandExecution } from "./CommandExecution"
import { CommandExecutionError } from "./CommandExecutionError"
import { FollowUpSuggest } from "./FollowUpSuggest"
import { Markdown } from "./Markdown"
import { ProgressIndicator } from "./ProgressIndicator"
import { MatterProgressIndicator, ProgressIndicator } from "./ProgressIndicator"
import ReportBugPreview from "./ReportBugPreview"
import { ReadOnlyChatText } from "./ReadOnlyChatText"
import { PlanFileIndicator } from "./PlanFileIndicator"
Expand Down Expand Up @@ -329,7 +329,7 @@ export const ChatRowContent = ({
) : apiRequestFailedMessage ? (
getIconSpan("error", errorColor)
) : (
<ProgressIndicator />
<MatterProgressIndicator />
),
apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
apiReqCancelReason === "user_cancelled" ? (
Expand Down Expand Up @@ -1197,7 +1197,8 @@ export const ChatRowContent = ({
content={message.text || ""}
ts={message.ts}
isStreaming={isStreaming}
isLast={isLast}
_isLast={isLast}
partial={message.partial}
metadata={message.metadata as any}
/>
)
Expand Down
9 changes: 8 additions & 1 deletion webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
if (isStreaming) {
vscode.postMessage({ type: "cancelTask" })
setDidClickCancel(true)
// Reset sendingDisabled so subsequent messages are sent directly instead of queued
setSendingDisabled(false)
return
}

Expand Down Expand Up @@ -859,9 +861,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
break
case "command_output":
vscode.postMessage({ type: "terminalOperation", terminalOperation: "abort" })
// Reset sendingDisabled so subsequent messages are sent directly instead of queued
setSendingDisabled(false)
break
}
setSendingDisabled(true)
// Only set sendingDisabled to true for cases that need it (not for command_output abort)
if (clineAsk !== "command_output") {
setSendingDisabled(true)
}
setClineAsk(undefined)
setEnableButtons(false)
},
Expand Down
35 changes: 35 additions & 0 deletions webview-ui/src/components/chat/ProgressIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
import { useEffect, useState } from "react"

export const ProgressIndicator = () => (
<div
Expand All @@ -14,3 +15,37 @@ export const ProgressIndicator = () => (
</div>
</div>
)

export const MatterProgressIndicator = () => {
const [activeIndex, setActiveIndex] = useState(0)

useEffect(() => {
const interval = setInterval(() => {
setActiveIndex((prev) => (prev + 1) % 3)
}, 200)

return () => clearInterval(interval)
}, [])

return (
<div
style={{
display: "flex",
alignItems: "center",
gap: "3px",
}}>
{[0, 1, 2].map((index) => (
<div
key={index}
style={{
width: "6px",
height: "6px",
backgroundColor: index === activeIndex ? "#c4fdff" : "rgba(196, 253, 255, 0.5)",
cursor: "pointer",
transition: "background-color 0.3s ease",
}}
/>
))}
</div>
)
}
2 changes: 1 addition & 1 deletion webview-ui/src/components/chat/QueuedMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const QueuedMessages = ({ queue, onRemove, onUpdate }: QueuedMessagesProp
return (
<div
key={message.id}
className="bg-vscode-editor-background border rounded-xs p-1 overflow-hidden whitespace-pre-wrap flex-shrink-0">
className="bg-vscode-editor-background border rounded-md p-1 overflow-hidden whitespace-pre-wrap flex-shrink-0">
<div className="flex justify-between items-center">
<div className="flex-grow px-2 py-1 wrap-anywhere">
{editState.isEditing ? (
Expand Down
Loading
Loading