Skip to content
Open
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
11 changes: 11 additions & 0 deletions packages/app/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,14 @@ async function seedStorage(page: Page, input: { directory: string; extra?: strin
}

export { expect }

/**
* Check if KILO credentials are available for tests requiring connected providers.
* Model picker tests require connected providers which need these credentials.
* @returns true if credentials are missing
*/
export function missingKiloCredentials() {
const hasKey = !!process.env.KILO_API_KEY
const hasOrg = !!process.env.KILO_ORG_ID
return !hasKey || !hasOrg
}
3 changes: 2 additions & 1 deletion packages/app/e2e/models/model-picker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { test, expect } from "../fixtures"
import { test, expect, missingKiloCredentials } from "../fixtures"
import { promptSelector } from "../selectors"
import { clickListItem } from "../actions"

test("smoke model selection updates prompt footer", async ({ page, gotoSession }) => {
test.skip(missingKiloCredentials(), "KILO_API_KEY or KILO_ORG_ID not set")
await gotoSession()

await page.locator(promptSelector).click()
Expand Down
3 changes: 2 additions & 1 deletion packages/app/e2e/models/models-visibility.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { test, expect } from "../fixtures"
import { test, expect, missingKiloCredentials } from "../fixtures"
import { promptSelector } from "../selectors"
import { closeDialog, openSettings, clickListItem } from "../actions"

test("hiding a model removes it from the model picker", async ({ page, gotoSession }) => {
test.skip(missingKiloCredentials(), "KILO_API_KEY or KILO_ORG_ID not set")
await gotoSession()

await page.locator(promptSelector).click()
Expand Down
4 changes: 3 additions & 1 deletion packages/app/e2e/settings/settings-models.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { test, expect } from "../fixtures"
import { test, expect, missingKiloCredentials } from "../fixtures"
import { promptSelector } from "../selectors"
import { closeDialog, openSettings } from "../actions"

test("hiding a model removes it from the model picker", async ({ page, gotoSession }) => {
test.skip(missingKiloCredentials(), "KILO_API_KEY or KILO_ORG_ID not set")
await gotoSession()

await page.locator(promptSelector).click()
Expand Down Expand Up @@ -61,6 +62,7 @@ test("hiding a model removes it from the model picker", async ({ page, gotoSessi
})

test("showing a hidden model restores it to the model picker", async ({ page, gotoSession }) => {
test.skip(missingKiloCredentials(), "KILO_API_KEY or KILO_ORG_ID not set")
await gotoSession()

await page.locator(promptSelector).click()
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/custom-elements.d.ts

This file was deleted.

18 changes: 18 additions & 0 deletions packages/app/src/custom-elements.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// kilocode_change - copy content instead of symlink for Windows compatibility
import { DIFFS_TAG_NAME } from "@pierre/diffs"

/**
* TypeScript declaration for the <diffs-container> custom element.
* This tells TypeScript that <diffs-container> is a valid JSX element in SolidJS.
* Required for using the @pierre/diffs web component in .tsx files.
*/

declare module "solid-js" {
namespace JSX {
interface IntrinsicElements {
[DIFFS_TAG_NAME]: HTMLAttributes<HTMLElement>
}
}
}

export {}
18 changes: 15 additions & 3 deletions packages/desktop/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
multiple: opts?.multiple ?? false,
title: opts?.title ?? t("desktop.dialog.chooseFolder"),
})
return result
// kilocode_change - normalize paths to use forward slashes
if (result === null) return null
if (Array.isArray(result)) {
return result.map((p) => p.replace(/\\/g, "/"))
}
return result.replace(/\\/g, "/")
},

async openFilePickerDialog(opts) {
Expand All @@ -83,15 +88,22 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({
multiple: opts?.multiple ?? false,
title: opts?.title ?? t("desktop.dialog.chooseFile"),
})
return result
// kilocode_change - normalize paths to use forward slashes
if (result === null) return null
if (Array.isArray(result)) {
return result.map((p) => p.replace(/\\/g, "/"))
}
return result.replace(/\\/g, "/")
},

async saveFilePickerDialog(opts) {
const result = await save({
title: opts?.title ?? t("desktop.dialog.saveFile"),
defaultPath: opts?.defaultPath,
})
return result
// kilocode_change - normalize paths to use forward slashes
if (result === null) return null
return result.replace(/\\/g, "/")
},

openLink(url: string) {
Expand Down
1 change: 0 additions & 1 deletion packages/enterprise/src/custom-elements.d.ts

This file was deleted.

18 changes: 18 additions & 0 deletions packages/enterprise/src/custom-elements.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// kilocode_change - copy content instead of symlink for Windows compatibility
import { DIFFS_TAG_NAME } from "@pierre/diffs"

/**
* TypeScript declaration for the <diffs-container> custom element.
* This tells TypeScript that <diffs-container> is a valid JSX element in SolidJS.
* Required for using the @pierre/diffs web component in .tsx files.
*/

declare module "solid-js" {
namespace JSX {
interface IntrinsicElements {
[DIFFS_TAG_NAME]: HTMLAttributes<HTMLElement>
}
}
}

export {}
7 changes: 4 additions & 3 deletions packages/opencode/src/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Plugin } from "@/plugin"
import { Skill } from "../skill"

import { Telemetry } from "@kilocode/kilo-telemetry" // kilocode_change
import { Filesystem } from "@/util/filesystem"

export namespace Agent {
export const Info = z
Expand Down Expand Up @@ -103,12 +104,12 @@ export namespace Agent {
question: "allow",
plan_exit: "allow",
external_directory: {
[path.join(Global.Path.data, "plans", "*")]: "allow",
[Filesystem.join(Global.Path.data, "plans", "*")]: "allow",
},
edit: {
"*": "deny",
[path.join(".opencode", "plans", "*.md")]: "allow",
[path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
[Filesystem.join(".opencode", "plans", "*.md")]: "allow",
[Filesystem.relative(Instance.worktree, Filesystem.join(Global.Path.data, "plans", "*.md"))]: "allow",
},
}),
user,
Expand Down
10 changes: 5 additions & 5 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ export function Session() {
} else {
const exportDir = process.cwd()
const filename = options.filename.trim()
const filepath = path.join(exportDir, filename)
const filepath = Filesystem.join(exportDir, filename)

await Bun.write(filepath, transcript)

Expand Down Expand Up @@ -1647,7 +1647,7 @@ function Bash(props: ToolProps<typeof BashTool>) {
const base = sync.data.path.directory
if (!base) return undefined

const absolute = path.resolve(base, workdir)
const absolute = Filesystem.resolve(base, workdir)
if (absolute === base) return undefined

const home = Global.Path.home
Expand Down Expand Up @@ -1701,7 +1701,7 @@ function Write(props: ToolProps<typeof WriteTool>) {
})

const diagnostics = createMemo(() => {
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
const filePath = Filesystem.realpath(props.input.filePath ?? "")
return props.metadata.diagnostics?.[filePath] ?? []
})

Expand Down Expand Up @@ -1911,7 +1911,7 @@ function Edit(props: ToolProps<typeof EditTool>) {
const diffContent = createMemo(() => props.metadata.diff)

const diagnostics = createMemo(() => {
const filePath = Filesystem.normalizePath(props.input.filePath ?? "")
const filePath = Filesystem.realpath(props.input.filePath ?? "")
const arr = props.metadata.diagnostics?.[filePath] ?? []
return arr.filter((x) => x.severity === 1).slice(0, 3)
})
Expand Down Expand Up @@ -2104,7 +2104,7 @@ function Skill(props: ToolProps<typeof SkillTool>) {
function normalizePath(input?: string) {
if (!input) return ""
if (path.isAbsolute(input)) {
return path.relative(process.cwd(), input) || "."
return Filesystem.relative(process.cwd(), input) || "."
}
return input
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Keybind } from "@/util/keybind"
import { Locale } from "@/util/locale"
import { Global } from "@/global"
import { useDialog } from "../../ui/dialog"
import { Filesystem } from "@/util/filesystem"

type PermissionStage = "permission" | "always" | "reject"

Expand All @@ -23,8 +24,8 @@ function normalizePath(input?: string) {

const cwd = process.cwd()
const home = Global.Path.home
const absolute = path.isAbsolute(input) ? input : path.resolve(cwd, input)
const relative = path.relative(cwd, absolute)
const absolute = path.isAbsolute(input) ? input : Filesystem.resolve(cwd, input)
const relative = Filesystem.relative(cwd, absolute)

if (!relative) return "."
if (!relative.startsWith("..")) return relative
Expand Down Expand Up @@ -248,7 +249,7 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
const derived =
typeof pattern === "string"
? pattern.includes("*")
? path.dirname(pattern)
? Filesystem.dirname(pattern)
: pattern
: undefined

Expand Down
7 changes: 5 additions & 2 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,13 @@ export namespace Config {
}

function rel(item: string, patterns: string[]) {
// Normalize the item path first
const normalizedItem = Filesystem.normalize(item)
for (const pattern of patterns) {
const index = item.indexOf(pattern)
const normalizedPattern = Filesystem.normalize(pattern)
const index = normalizedItem.indexOf(normalizedPattern)
if (index === -1) continue
return item.slice(index + pattern.length)
return normalizedItem.slice(index + normalizedPattern.length)
}
}

Expand Down
11 changes: 7 additions & 4 deletions packages/opencode/src/file/ignore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sep } from "node:path"
import { Filesystem } from "../util/filesystem"

export namespace FileIgnore {
const FOLDERS = new Set([
Expand Down Expand Up @@ -64,18 +64,21 @@ export namespace FileIgnore {
whitelist?: Bun.Glob[]
},
) {
// Normalize path to use forward slashes
const normalizedPath = Filesystem.normalize(filepath)

for (const glob of opts?.whitelist || []) {
if (glob.match(filepath)) return false
if (glob.match(normalizedPath)) return false
}

const parts = filepath.split(sep)
const parts = normalizedPath.split("/")
for (let i = 0; i < parts.length; i++) {
if (FOLDERS.has(parts[i])) return true
}

const extra = opts?.extra || []
for (const glob of [...FILE_GLOBS, ...extra]) {
if (glob.match(filepath)) return true
if (glob.match(normalizedPath)) return true
}

return false
Expand Down
6 changes: 3 additions & 3 deletions packages/opencode/src/kilocode/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export namespace KilocodePaths {
}),
)
for (const dir of projectDirs) {
const skillsDir = path.join(dir, "skills")
const skillsDir = Filesystem.join(dir, "skills")
if (await Filesystem.isDir(skillsDir)) {
directories.push(dir) // Return parent (.kilocode/), not skills/
}
Expand All @@ -71,14 +71,14 @@ export namespace KilocodePaths {
if (!opts.skipGlobalPaths) {
// 2. Global ~/.kilocode/
const global = globalDir()
const globalSkills = path.join(global, "skills")
const globalSkills = Filesystem.join(global, "skills")
if (await Filesystem.isDir(globalSkills)) {
directories.push(global) // Return parent, not skills/
}

// 3. VSCode extension global storage (marketplace-installed skills)
const vscode = vscodeGlobalStorage()
const vscodeSkills = path.join(vscode, "skills")
const vscodeSkills = Filesystem.join(vscode, "skills")
if (await Filesystem.isDir(vscodeSkills)) {
directories.push(vscode) // Return parent, not skills/
}
Expand Down
4 changes: 2 additions & 2 deletions packages/opencode/src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export namespace LSPClient {

const diagnostics = new Map<string, Diagnostic[]>()
connection.onNotification("textDocument/publishDiagnostics", (params) => {
const filePath = Filesystem.normalizePath(fileURLToPath(params.uri))
const filePath = Filesystem.realpath(fileURLToPath(params.uri))
l.info("textDocument/publishDiagnostics", {
path: filePath,
count: params.diagnostics.length,
Expand Down Expand Up @@ -208,7 +208,7 @@ export namespace LSPClient {
return diagnostics
},
async waitForDiagnostics(input: { path: string }) {
const normalizedPath = Filesystem.normalizePath(
const normalizedPath = Filesystem.realpath(
path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path),
)
log.info("waiting for diagnostics", { path: normalizedPath })
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/src/lsp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Config } from "../config/config"
import { spawn } from "child_process"
import { Instance } from "../project/instance"
import { Flag } from "@/flag/flag"
import { Filesystem } from "@/util/filesystem"

export namespace LSP {
const log = Log.create({ service: "lsp" })
Expand Down Expand Up @@ -111,9 +112,10 @@ export namespace LSP {
root: existing?.root ?? (async () => Instance.directory),
extensions: item.extensions ?? existing?.extensions ?? [],
spawn: async (root) => {
const normalizedRoot = Filesystem.normalize(root)
return {
process: spawn(item.command[0], item.command.slice(1), {
cwd: root,
cwd: normalizedRoot,
env: {
...process.env,
...item.env,
Expand Down
8 changes: 4 additions & 4 deletions packages/opencode/src/patch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,23 @@ export namespace Patch {
const line = lines[startIdx]

if (line.startsWith("*** Add File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.substring("*** Add File:".length).trim()
return filePath ? { filePath, nextIdx: startIdx + 1 } : null
}

if (line.startsWith("*** Delete File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.substring("*** Delete File:".length).trim()
return filePath ? { filePath, nextIdx: startIdx + 1 } : null
}

if (line.startsWith("*** Update File:")) {
const filePath = line.split(":", 2)[1]?.trim()
const filePath = line.substring("*** Update File:".length).trim()
let movePath: string | undefined
let nextIdx = startIdx + 1

// Check for move directive
if (nextIdx < lines.length && lines[nextIdx].startsWith("*** Move to:")) {
movePath = lines[nextIdx].split(":", 2)[1]?.trim()
movePath = lines[nextIdx].substring("*** Move to:".length).trim()
nextIdx++
}

Expand Down
Loading
Loading