fix: preserve Kilo identity after upstream merge

This commit is contained in:
marius-kilocode
2026-06-01 16:58:26 +02:00
parent 6016248c42
commit b5213921c1
16 changed files with 128 additions and 33 deletions
+6 -6
View File
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
version = "1.14.42"
version = "7.3.18"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/Kilo-Org/kilocode"
@@ -11,26 +11,26 @@ name = "Kilo"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.42/opencode-darwin-arm64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.18/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.42/opencode-darwin-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.18/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.42/opencode-linux-arm64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.18/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.42/opencode-linux-x64.tar.gz"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.18/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.42/opencode-windows-x64.zip"
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.18/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+2
View File
@@ -102,6 +102,7 @@
<!-- packages/opencode/src/session/network.ts -->
- <https://kilo.ai/>
<!-- packages/opencode/src/cli/cmd/generate.ts -->
<!-- packages/opencode/src/kilocode/server/httpapi/public.ts -->
<!-- packages/opencode/src/provider/provider.ts -->
- <https://kilo.ai/discord>
<!-- packages/kilo-vscode/webview-ui/src/components/chat/FeedbackDialog.tsx -->
@@ -131,6 +132,7 @@
<!-- packages/opencode/src/config/model-id.ts -->
- <https://opencode.ai/>
<!-- packages/opencode/src/cli/cmd/generate.ts -->
<!-- packages/opencode/src/kilocode/server/httpapi/public.ts -->
- <https://opencode.ai/auth>
<!-- packages/opencode/src/cli/cmd/providers.ts -->
- <https://opencode.ai/docs/agents>
+4 -2
View File
@@ -16,7 +16,8 @@ function configureTreeSitterResources(target) {
}
// kilocode_change end
function run(target, fallback) { // kilocode_change - preserve cached binary fallback
function run(target, fallback) {
// kilocode_change - preserve cached binary fallback
configureTreeSitterResources(target) // kilocode_change
// kilocode_change start - fall through if the cached binary cannot be spawned
const child = (() => {
@@ -37,7 +38,8 @@ function run(target, fallback) { // kilocode_change - preserve cached binary fal
// kilocode_change end
const forwarders = {}
const clear = () => { // kilocode_change - remove listeners before cached binary fallback
const clear = () => {
// kilocode_change - remove listeners before cached binary fallback
for (const signal of forwardedSignals) {
process.removeListener(signal, forwarders[signal])
}
@@ -99,7 +99,7 @@ function RejectField(props: {
minHeight={1}
maxHeight={3}
wrapMode="word"
placeholder="Tell OpenCode what to do differently"
placeholder={"Tell Kilo what to do differently" /* kilocode_change */}
placeholderColor={props.theme.muted}
textColor={props.theme.text}
focusedTextColor={props.theme.text}
@@ -288,7 +288,7 @@ export function RunPermissionBody(props: {
</Match>
<Match when={state().stage === "reject"}>
<box paddingLeft={1}>
<text fg={props.theme.muted}>Tell OpenCode what to do differently</text>
<text fg={props.theme.muted}>{"Tell Kilo what to do differently" /* kilocode_change */}</text>
</box>
</Match>
</Switch>
@@ -125,11 +125,11 @@ export function permissionInfo(request: PermissionRequest): PermissionInfo {
export function permissionAlwaysLines(request: PermissionRequest): string[] {
if (request.always.length === 1 && request.always[0] === "*") {
return [`This will allow ${request.permission} until OpenCode is restarted.`]
return [`This will allow ${request.permission} until Kilo is restarted.`] // kilocode_change
}
return [
"This will allow the following patterns until OpenCode is restarted.",
"This will allow the following patterns until Kilo is restarted.", // kilocode_change
...request.always.map((item) => `- ${item}`),
]
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Per-tool display rules shared across `opencode run` output paths.
// Per-tool display rules shared across `kilo run` output paths. // kilocode_change
//
// Each known tool (bash, edit, write, task, etc.) has a ToolRule that controls
// five display hooks:
+1 -1
View File
@@ -1,7 +1,7 @@
// Dev-only JSONL event trace for direct interactive mode.
//
// Enable with KILO_DIRECT_TRACE=1. Writes one JSON line per event to
// ~/.local/share/opencode/log/direct/<timestamp>-<pid>.jsonl. Also writes
// ~/.local/share/kilo/log/direct/<timestamp>-<pid>.jsonl. Also writes // kilocode_change
// a latest.json pointer so you can quickly find the most recent trace.
//
// The trace captures the full closed loop: outbound prompts, inbound SDK
@@ -3,7 +3,7 @@
// Variants are provider-specific reasoning effort levels (e.g., "high", "max").
// Resolution priority: CLI --variant flag > saved preference > session history.
//
// The saved variant persists across sessions in ~/.local/state/opencode/model.json
// The saved variant persists across sessions in ~/.local/state/kilo/model.json // kilocode_change
// so your last-used variant sticks. Cycling (ctrl+t) updates both the active
// variant and the persisted file.
import path from "path"
@@ -30,6 +30,7 @@ type Spec = {
}
export function matchLegacyKiloOpenApi(input: Record<string, unknown>) {
rebrand(input)
const spec = input as Spec
const body = spec.paths?.["/kilo/organization"]?.post?.requestBody?.content?.["application/json"]?.schema
const ref = body?.$ref?.replace("#/components/schemas/", "")
@@ -79,6 +80,25 @@ export function matchLegacyKiloOpenApi(input: Record<string, unknown>) {
}
}
function rebrand(value: unknown): void {
if (Array.isArray(value)) {
for (const item of value) rebrand(item)
return
}
if (!value || typeof value !== "object") return
for (const [key, item] of Object.entries(value)) {
if (typeof item !== "string") {
rebrand(item)
continue
}
;(value as Record<string, unknown>)[key] = item
.replaceAll("OpenCode", "Kilo")
.replaceAll("opencode.local", "kilo.local")
.replaceAll("opencode serve", "kilo serve")
.replaceAll("https://opencode.ai/", "https://kilo.ai/")
}
}
function nullable(schema: Schema): Schema {
if (schema.anyOf?.some((item) => item.type === "null")) return schema
return { anyOf: [schema, { type: "null" }] }
+1 -1
View File
@@ -1,4 +1,4 @@
- Clone or refresh a repository into OpenCode's managed cache under the data directory
- Clone or refresh a repository into Kilo's managed cache under the data directory
- Accepts git URLs, forge host/path references, or GitHub owner/repo shorthand
- Returns the cached absolute local path so other tools can explore the cloned source
- Use this before Read, Glob, or Grep when the code you need lives outside the current workspace
@@ -132,11 +132,11 @@ describe("run permission shared", () => {
test("formats always-allow copy for wildcard and explicit patterns", () => {
expect(permissionAlwaysLines(req({ permission: "bash", always: ["*"] }))).toEqual([
"This will allow bash until OpenCode is restarted.",
"This will allow bash until Kilo is restarted.",
])
expect(permissionAlwaysLines(req({ always: ["src/**/*.ts", "src/**/*.tsx"] }))).toEqual([
"This will allow the following patterns until OpenCode is restarted.",
"This will allow the following patterns until Kilo is restarted.",
"- src/**/*.ts",
"- src/**/*.tsx",
])
@@ -4,14 +4,16 @@ import { tmpdir } from "os"
import { join } from "path"
const script = join(import.meta.dir, "..", "..", "bin", "kilo")
const platform = process.platform === "win32" ? "windows" : process.platform
const binary = platform === "windows" ? "kilo.exe" : "kilo"
describe("bin/kilo tree-sitter resources", () => {
async function setup(root: string, nested: boolean) {
const dir = nested
? join(root, "node_modules", "@kilocode", "cli-darwin-arm64", "bin")
? join(root, "node_modules", "@kilocode", `cli-${platform}-${process.arch}`, "bin")
: join(root, "node_modules", "@kilocode", "cli", "bin")
const wasm = join(dir, "tree-sitter")
const bin = join(dir, nested ? "kilo" : ".kilo")
const bin = join(dir, nested ? binary : ".kilo")
const log = join(root, nested ? "nested-env.txt" : "cached-env.txt")
await mkdir(wasm, { recursive: true })
@@ -60,7 +60,8 @@ describe("instance HttpApi", () => {
expect(response.status).toBe(200)
expect(response.headers["content-type"]).toContain("application/json")
expect(yield* response.json).toMatchObject({
const spec = yield* response.json
expect(spec).toMatchObject({
openapi: expect.any(String),
info: expect.any(Object),
paths: expect.objectContaining({
@@ -68,6 +69,7 @@ describe("instance HttpApi", () => {
"/session": expect.any(Object),
}),
})
expect(JSON.stringify(spec)).not.toContain("OpenCode")
}),
)
+3
View File
@@ -41,6 +41,9 @@ const forbidden: { pattern: string; reason: string; allow?: string[] }[] = [
},
{ pattern: `"HTTP-Referer": "https://opencode.ai/"`, reason: "attributes outbound LLM traffic to upstream" },
{ pattern: `"http-referer": "https://opencode.ai/"`, reason: "attributes outbound LLM traffic to upstream" },
{ pattern: "Tell OpenCode what to do differently", reason: "direct-mode permission UI uses upstream branding" },
{ pattern: "until OpenCode is restarted", reason: "permission copy uses upstream branding" },
{ pattern: "OpenCode's managed cache", reason: "Scout tool description uses upstream branding" },
// Candidates -- enable once the underlying call sites have been rebranded.
// Each one currently fires on real leaks; uncomment after fixing the listed
@@ -0,0 +1,37 @@
import { describe, expect, test } from "bun:test"
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { preserveZedVersion } from "./preserve-versions"
describe("preserveZedVersion", () => {
test("preserves the Kilo extension version and release archives", async () => {
const dir = await mkdtemp(join(tmpdir(), "kilo-zed-version-"))
const file = join(dir, "extension.toml")
try {
await writeFile(
file,
[
'version = "1.14.42"',
'archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.14.42/opencode-linux-x64.tar.gz"',
"",
].join("\n"),
)
expect(await preserveZedVersion(file, { targetVersion: "7.3.18" })).toMatchObject({
file,
originalVersion: "1.14.42",
preserved: true,
})
expect(await readFile(file, "utf8")).toBe(
[
'version = "7.3.18"',
'archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.18/opencode-linux-x64.tar.gz"',
"",
].join("\n"),
)
} finally {
await rm(dir, { recursive: true, force: true })
}
})
})
+39 -12
View File
@@ -36,7 +36,7 @@ export async function getCurrentVersion(): Promise<string> {
*/
export async function preserveVersion(filePath: string, options: PreserveOptions = {}): Promise<VersionPreserveResult> {
const file = Bun.file(filePath)
let content = await file.text()
const content = await file.text()
// Extract current version
const versionMatch = content.match(/"version":\s*"([^"]+)"/)
@@ -61,6 +61,30 @@ export async function preserveVersion(filePath: string, options: PreserveOptions
}
}
export async function preserveZedVersion(
filePath: string,
options: PreserveOptions = {},
): Promise<VersionPreserveResult> {
const file = Bun.file(filePath)
const content = await file.text()
const versionMatch = content.match(/^version = "([^"]+)"/m)
const originalVersion = versionMatch ? versionMatch[1] : "unknown"
const targetVersion = options.targetVersion || (await getCurrentVersion())
const next = content
.replace(/^version = "[^"]+"/m, `version = "${targetVersion}"`)
.replace(/\/releases\/download\/v[^/]+\//g, `/releases/download/v${targetVersion}/`)
const changed = next !== content
if (changed && !options.dryRun) await Bun.write(filePath, next)
return {
file: filePath,
originalVersion,
preserved: changed,
dryRun: options.dryRun ?? false,
}
}
/**
* Preserve versions in all package.json files
*/
@@ -75,25 +99,28 @@ export async function preserveAllVersions(options: PreserveOptions = {}): Promis
info(`Target version: ${targetVersion}`)
const track = (result: VersionPreserveResult) => {
if (!result.preserved) return
results.push(result)
if (options.dryRun) {
info(`[DRY-RUN] Would preserve ${result.file}: ${result.originalVersion} -> ${targetVersion}`)
return
}
success(`Preserved ${result.file}: ${result.originalVersion} -> ${targetVersion}`)
}
for await (const path of glob.scan({ absolute: true })) {
// Skip excluded paths
if (excludes.some((ex) => path.includes(ex.replace(/\*\*/g, "")))) {
continue
}
const result = await preserveVersion(path, { ...options, targetVersion })
if (result.preserved) {
results.push(result)
if (options.dryRun) {
info(`[DRY-RUN] Would preserve ${result.file}: ${result.originalVersion} -> ${targetVersion}`)
} else {
success(`Preserved ${result.file}: ${result.originalVersion} -> ${targetVersion}`)
}
}
track(await preserveVersion(path, { ...options, targetVersion }))
}
const zed = "packages/extensions/zed/extension.toml"
if (await Bun.file(zed).exists()) track(await preserveZedVersion(zed, { ...options, targetVersion }))
return results
}