diff --git a/bun.lock b/bun.lock
index a95c498394..c1a64cf5a8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -499,6 +499,7 @@
"@solid-primitives/resize-observer": "2.1.3",
"@solidjs/meta": "catalog:",
"@solidjs/router": "catalog:",
+ "@typescript/native-preview": "catalog:",
"dompurify": "3.3.1",
"fuzzysort": "catalog:",
"katex": "0.16.27",
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index edf3360731..b1b8335069 100644
--- a/packages/app/src/i18n/ar.ts
+++ b/packages/app/src/i18n/ar.ts
@@ -136,7 +136,8 @@ export const dict = {
"provider.connect.oauth.code.invalid": "رمز التفويض غير صالح",
"provider.connect.oauth.auto.visit.prefix": "قم بزيارة ",
"provider.connect.oauth.auto.visit.link": "هذا الرابط",
- "provider.connect.oauth.auto.visit.suffix": " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.",
+ "provider.connect.oauth.auto.visit.suffix":
+ " وأدخل الرمز أدناه لتوصيل حسابك واستخدام نماذج {{provider}} في Kilo.",
"provider.connect.oauth.auto.confirmationCode": "رمز التأكيد",
"provider.connect.toast.connected.title": "تم توصيل {{provider}}",
"provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.",
diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts
index 5ca652a639..4a2bca5f29 100644
--- a/packages/app/src/i18n/fr.ts
+++ b/packages/app/src/i18n/fr.ts
@@ -389,7 +389,8 @@ export const dict = {
"toast.session.unshare.failed.description": "Une erreur s'est produite lors de l'annulation du partage de la session",
"toast.session.listFailed.title": "Échec du chargement des sessions pour {{project}}",
"toast.update.title": "Mise à jour disponible",
- "toast.update.description": "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.",
+ "toast.update.description":
+ "Une nouvelle version d'Kilo ({{version}}) est maintenant disponible pour installation.",
"toast.update.action.installRestart": "Installer et redémarrer",
"toast.update.action.notYet": "Pas encore",
"error.page.title": "Quelque chose s'est mal passé",
@@ -523,7 +524,8 @@ export const dict = {
"sidebar.workspaces.enable": "Activer les espaces de travail",
"sidebar.workspaces.disable": "Désactiver les espaces de travail",
"sidebar.gettingStarted.title": "Commencer",
- "sidebar.gettingStarted.line1": "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.",
+ "sidebar.gettingStarted.line1":
+ "Kilo inclut des modèles gratuits pour que vous puissiez commencer immédiatement.",
"sidebar.gettingStarted.line2":
"Connectez n'importe quel fournisseur pour utiliser des modèles, y compris Claude, GPT, Gemini etc.",
"sidebar.project.recentSessions": "Sessions récentes",
diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts
index f07df4a2f2..b52bd58067 100644
--- a/packages/app/src/i18n/th.ts
+++ b/packages/app/src/i18n/th.ts
@@ -148,7 +148,8 @@ export const dict = {
"provider.connect.oauth.code.invalid": "รหัสการอนุญาตไม่ถูกต้อง",
"provider.connect.oauth.auto.visit.prefix": "เยี่ยมชม ",
"provider.connect.oauth.auto.visit.link": "ลิงก์นี้",
- "provider.connect.oauth.auto.visit.suffix": " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo",
+ "provider.connect.oauth.auto.visit.suffix":
+ " และป้อนรหัสด้านล่างเพื่อเชื่อมต่อบัญชีและใช้โมเดล {{provider}} ใน Kilo",
"provider.connect.oauth.auto.confirmationCode": "รหัสยืนยัน",
"provider.connect.toast.connected.title": "{{provider}} ที่เชื่อมต่อแล้ว",
"provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว",
diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts
index 242ce21921..10e72dc985 100644
--- a/packages/app/src/i18n/tr.ts
+++ b/packages/app/src/i18n/tr.ts
@@ -143,7 +143,8 @@ export const dict = {
"provider.connect.opencodeZen.visit.prefix": "",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
- "provider.connect.oauth.code.visit.prefix": "Hesabınızı bağlamak ve Kilo'da {{provider}} modellerini kullanmak için ",
+ "provider.connect.oauth.code.visit.prefix":
+ "Hesabınızı bağlamak ve Kilo'da {{provider}} modellerini kullanmak için ",
"provider.connect.oauth.code.visit.link": "bu bağlantıya",
"provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.",
"provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts
index c71e66268b..505cf2a530 100644
--- a/packages/app/src/i18n/zht.ts
+++ b/packages/app/src/i18n/zht.ts
@@ -149,7 +149,8 @@ export const dict = {
"provider.connect.oauth.code.invalid": "授權碼無效",
"provider.connect.oauth.auto.visit.prefix": "造訪 ",
"provider.connect.oauth.auto.visit.link": "此連結",
- "provider.connect.oauth.auto.visit.suffix": " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。",
+ "provider.connect.oauth.auto.visit.suffix":
+ " 並輸入以下程式碼,以連線你的帳戶並在 Kilo 中使用 {{provider}} 模型。",
"provider.connect.oauth.auto.confirmationCode": "確認碼",
"provider.connect.toast.connected.title": "{{provider}} 已連線",
"provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。",
diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx
index b9c8a31c9d..e042cdaca6 100644
--- a/packages/app/src/pages/layout.tsx
+++ b/packages/app/src/pages/layout.tsx
@@ -22,7 +22,7 @@ import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
-import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
+import { Tooltip } from "@opencode-ai/ui/tooltip"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
import { Dialog } from "@opencode-ai/ui/dialog"
import { getFilename } from "@opencode-ai/util/path"
@@ -1942,20 +1942,14 @@ export default function Layout(props: ParentProps) {
fallback={
<>
- navigateWithSidebarReset(`/${base64Encode(p.worktree)}/session`)}
>
-
-
+ {language.t("command.session.new")}
+
<>
-
-
-
+
{
return `file://${path}`
},
openTab: (tab) => calls.push(`open:${tab}`),
+ setActive: (tab) => calls.push(`active:${tab}`),
loadFile: (path) => calls.push(`load:${path}`),
})
openReviewFile("src/a.ts")
- expect(calls).toEqual(["show", "load:src/a.ts", "tab:src/a.ts", "open:file://src/a.ts"])
+ expect(calls).toEqual(["show", "load:src/a.ts", "tab:src/a.ts", "open:file://src/a.ts", "active:file://src/a.ts"])
})
})
diff --git a/packages/app/src/pages/session/helpers.ts b/packages/app/src/pages/session/helpers.ts
index 20f1d99a8b..60b26cdf47 100644
--- a/packages/app/src/pages/session/helpers.ts
+++ b/packages/app/src/pages/session/helpers.ts
@@ -24,15 +24,20 @@ export const createOpenReviewFile = (input: {
showAllFiles: () => void
tabForPath: (path: string) => string
openTab: (tab: string) => void
+ setActive: (tab: string) => void
loadFile: (path: string) => any | Promise
}) => {
return (path: string) => {
batch(() => {
input.showAllFiles()
const maybePromise = input.loadFile(path)
- const openTab = () => input.openTab(input.tabForPath(path))
- if (maybePromise instanceof Promise) maybePromise.then(openTab)
- else openTab()
+ const open = () => {
+ const tab = input.tabForPath(path)
+ input.openTab(tab)
+ input.setActive(tab)
+ }
+ if (maybePromise instanceof Promise) maybePromise.then(open)
+ else open()
})
}
}
diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx
index 192c7667d9..1f756fc245 100644
--- a/packages/app/src/pages/session/message-timeline.tsx
+++ b/packages/app/src/pages/session/message-timeline.tsx
@@ -1,4 +1,4 @@
-import { For, createEffect, createMemo, on, onCleanup, Show, startTransition, Index, type JSX } from "solid-js"
+import { For, createEffect, createMemo, on, onCleanup, Show, Index, type JSX } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { useNavigate, useParams } from "@solidjs/router"
import { Button } from "@opencode-ai/ui/button"
@@ -160,7 +160,7 @@ function createTimelineStaging(input: TimelineStageInput) {
}
const currentTotal = input.messages().length
count = Math.min(currentTotal, count + input.config.batch)
- startTransition(() => setState("count", count))
+ setState("count", count)
if (count >= currentTotal) {
setState({ completedSession: sessionKey, activeSession: "" })
frame = undefined
diff --git a/packages/desktop/src/i18n/de.ts b/packages/desktop/src/i18n/de.ts
index 224e0b62d2..6ef1167a36 100644
--- a/packages/desktop/src/i18n/de.ts
+++ b/packages/desktop/src/i18n/de.ts
@@ -45,7 +45,8 @@ export const dict = {
"desktop.menu.help.shareFeedback": "Feedback teilen",
"desktop.menu.help.reportBug": "Einen Fehler melden",
"desktop.cli.error.unsupportedPlatform": "Die CLI-Installation wird nur unter macOS und Linux unterstützt.",
- "desktop.cli.error.sidecarMissing": "Das Kilo CLI-Binary fehlt. Versuchen Sie, die Desktop-App neu zu installieren.",
+ "desktop.cli.error.sidecarMissing":
+ "Das Kilo CLI-Binary fehlt. Versuchen Sie, die Desktop-App neu zu installieren.",
"desktop.cli.error.scriptWriteFailed": "Das CLI-Installationsskript konnte nicht vorbereitet werden.",
"desktop.cli.error.scriptPermissionFailed": "Das CLI-Installationsskript konnte nicht ausführbar gemacht werden.",
"desktop.cli.error.scriptRunFailed": "Das CLI-Installationsskript konnte nicht ausgeführt werden.",
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index 075b9ca5bb..62690a6ff1 100644
--- a/packages/opencode/src/acp/agent.ts
+++ b/packages/opencode/src/acp/agent.ts
@@ -31,6 +31,7 @@ import {
import { Log } from "../util/log"
import { pathToFileURL } from "bun"
import { Filesystem } from "../util/filesystem"
+import { Hash } from "../util/hash"
import { ACPSessionManager } from "./session"
import type { ACPConfig } from "./types"
import { Provider } from "../provider/provider"
@@ -283,7 +284,7 @@ export namespace ACP {
const output = this.bashOutput(part)
const content: ToolCallContent[] = []
if (output) {
- const hash = String(Bun.hash(output))
+ const hash = Hash.fast(output)
if (part.tool === "bash") {
if (this.bashSnapshots.get(part.callID) === hash) {
await this.connection
diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts
index cce7f2dd95..53a5f4bdaf 100644
--- a/packages/opencode/src/cli/cmd/auth.ts
+++ b/packages/opencode/src/cli/cmd/auth.ts
@@ -13,6 +13,7 @@ import { Instance } from "../../project/instance"
import type { Hooks } from "@kilocode/plugin"
import { Process } from "../../util/process"
import { text } from "node:stream/consumers"
+import { setTimeout as sleep } from "node:timers/promises"
type PluginAuth = NonNullable
@@ -47,7 +48,7 @@ async function handlePluginAuth(plugin: { auth: PluginAuth }, provider: string,
const method = plugin.auth.methods[index]
// Handle prompts for all auth types
- await Bun.sleep(10)
+ await sleep(10)
const inputs: Record = {}
if (method.prompts) {
for (const prompt of method.prompts) {
diff --git a/packages/opencode/src/cli/cmd/debug/lsp.ts b/packages/opencode/src/cli/cmd/debug/lsp.ts
index d83c4ed8a4..4b8a3e7d45 100644
--- a/packages/opencode/src/cli/cmd/debug/lsp.ts
+++ b/packages/opencode/src/cli/cmd/debug/lsp.ts
@@ -3,6 +3,7 @@ import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
import { Log } from "../../../util/log"
import { EOL } from "os"
+import { setTimeout as sleep } from "node:timers/promises"
export const LSPCommand = cmd({
command: "lsp",
@@ -19,7 +20,7 @@ const DiagnosticsCommand = cmd({
async handler(args) {
await bootstrap(process.cwd(), async () => {
await LSP.touchFile(args.file, true)
- await Bun.sleep(1000)
+ await sleep(1000)
process.stdout.write(JSON.stringify(await LSP.diagnostics(), null, 2) + EOL)
})
},
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index e7891f2ab2..cc7ac4c189 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -28,6 +28,7 @@ import { Bus } from "../../bus"
import { MessageV2 } from "../../session/message-v2"
import { SessionPrompt } from "@/session/prompt"
import { $ } from "bun"
+import { setTimeout as sleep } from "node:timers/promises"
type GitHubAuthor = {
login: string
@@ -353,7 +354,7 @@ export const GithubInstallCommand = cmd({
}
retries++
- await Bun.sleep(1000)
+ await sleep(1000)
} while (true)
s.stop("Installed GitHub app")
@@ -1384,7 +1385,7 @@ Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
} catch (e) {
if (retries > 0) {
console.log(`Retrying after ${delayMs}ms...`)
- await Bun.sleep(delayMs)
+ await sleep(delayMs)
return withRetry(fn, retries - 1, delayMs)
}
throw e
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 55e3177895..8a0df9a569 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -6,6 +6,7 @@ import { cmd } from "./cmd"
import { Flag } from "../../flag/flag"
import { bootstrap } from "../bootstrap"
import { EOL } from "os"
+import { text as streamText } from "node:stream/consumers"
import { Filesystem } from "../../util/filesystem"
import { createKiloClient, type Message, type KiloClient, type ToolPart } from "@kilocode/sdk/v2"
import { Server } from "../../server/server"
@@ -351,7 +352,7 @@ export const RunCommand = cmd({
}
}
- if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
+ if (!process.stdin.isTTY) message += "\n" + (await streamText(process.stdin))
if (message.trim().length === 0 && !args.command) {
UI.error("You must provide a message or a command")
diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts
index fdadc1be9b..007c5fe5f2 100644
--- a/packages/opencode/src/cli/cmd/tui/thread.ts
+++ b/packages/opencode/src/cli/cmd/tui/thread.ts
@@ -3,6 +3,7 @@ import { tui } from "./app"
import { Rpc } from "@/util/rpc"
import { type rpc } from "./worker"
import path from "path"
+import { text as streamText } from "node:stream/consumers"
import { fileURLToPath } from "url"
import { UI } from "@/cli/ui"
import { Log } from "@/util/log"
@@ -55,7 +56,7 @@ async function target() {
}
async function input(value?: string) {
- const piped = process.stdin.isTTY ? undefined : await Bun.stdin.text()
+ const piped = process.stdin.isTTY ? undefined : await streamText(process.stdin)
if (!value) return piped
if (!piped) return value
return piped + "\n" + value
diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts
index 21997e881a..f7560b7491 100644
--- a/packages/opencode/src/cli/cmd/tui/worker.ts
+++ b/packages/opencode/src/cli/cmd/tui/worker.ts
@@ -10,6 +10,7 @@ import { GlobalBus } from "@/bus/global"
import { createKiloClient, type Event } from "@kilocode/sdk/v2"
import type { BunWebSocketData } from "hono/bun"
import { Flag } from "@/flag/flag"
+import { setTimeout as sleep } from "node:timers/promises"
await Log.init({
print: process.argv.includes("--print-logs"),
@@ -75,7 +76,7 @@ const startEventStream = (directory: string) => {
).catch(() => undefined)
if (!events) {
- await Bun.sleep(250)
+ await sleep(250)
continue
}
@@ -84,7 +85,7 @@ const startEventStream = (directory: string) => {
}
if (!signal.aborted) {
- await Bun.sleep(250)
+ await sleep(250)
}
}
})().catch((error) => {
diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts
index 41e6f672f6..9bbc7cf97d 100644
--- a/packages/opencode/src/cli/ui.ts
+++ b/packages/opencode/src/cli/ui.ts
@@ -25,12 +25,12 @@ export namespace UI {
export function println(...message: string[]) {
print(...message)
- Bun.stderr.write(EOL)
+ process.stderr.write(EOL)
}
export function print(...message: string[]) {
blank = false
- Bun.stderr.write(message.join(" "))
+ process.stderr.write(message.join(" "))
}
let blank = false
@@ -45,7 +45,7 @@ export namespace UI {
const result: string[] = []
const reset = "\x1b[0m"
const left = {
- fg: Bun.color("gray", "ansi") ?? "",
+ fg: "\x1b[90m",
shadow: "\x1b[38;5;235m",
bg: "\x1b[48;5;235m",
}
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index e28c576586..540e9976db 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -1376,7 +1376,7 @@ export namespace Config {
if (!parsed.data.$schema && isFile) {
parsed.data.$schema = "https://app.kilo.ai/config.json" // kilocode_change
const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://app.kilo.ai/config.json",') // kilocode_change
- await Bun.write(options.path, updated).catch(() => {})
+ await Filesystem.write(options.path, updated).catch(() => {})
}
const data = parsed.data
if (data.plugin && isFile) {
@@ -1572,3 +1572,5 @@ export namespace Config {
return state().then((x) => x.directories)
}
}
+Filesystem.write
+Filesystem.write
diff --git a/packages/opencode/src/config/migrate-tui-config.ts b/packages/opencode/src/config/migrate-tui-config.ts
index 30d7affb2f..5122958c90 100644
--- a/packages/opencode/src/config/migrate-tui-config.ts
+++ b/packages/opencode/src/config/migrate-tui-config.ts
@@ -70,7 +70,7 @@ export async function migrateTuiConfig(input: MigrateInput) {
if (extracted.keybinds !== undefined) payload.keybinds = extracted.keybinds
if (tui) Object.assign(payload, tui)
- const wrote = await Bun.write(target, JSON.stringify(payload, null, 2))
+ const wrote = await Filesystem.write(target, JSON.stringify(payload, null, 2))
.then(() => true)
.catch((error) => {
log.warn("failed to write tui migration target", { from: file, to: target, error })
@@ -104,7 +104,7 @@ async function backupAndStripLegacy(file: string, source: string) {
const hasBackup = await Filesystem.exists(backup)
const backed = hasBackup
? true
- : await Bun.write(backup, source)
+ : await Filesystem.write(backup, source)
.then(() => true)
.catch((error) => {
log.warn("failed to backup source config during tui migration", { path: file, backup, error })
@@ -123,7 +123,7 @@ async function backupAndStripLegacy(file: string, source: string) {
return applyEdits(acc, edits)
}, source)
- return Bun.write(file, text)
+ return Filesystem.write(file, text)
.then(() => {
log.info("stripped tui keys from server config", { path: file, backup })
return true
diff --git a/packages/opencode/src/mcp/oauth-callback.ts b/packages/opencode/src/mcp/oauth-callback.ts
index 120c21cf03..6132f6b789 100644
--- a/packages/opencode/src/mcp/oauth-callback.ts
+++ b/packages/opencode/src/mcp/oauth-callback.ts
@@ -1,3 +1,4 @@
+import { createConnection } from "net"
import { Log } from "../util/log"
import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH } from "./oauth-provider"
@@ -166,21 +167,12 @@ export namespace McpOAuthCallback {
export async function isPortInUse(): Promise {
return new Promise((resolve) => {
- Bun.connect({
- hostname: "127.0.0.1",
- port: OAUTH_CALLBACK_PORT,
- socket: {
- open(socket) {
- socket.end()
- resolve(true)
- },
- error() {
- resolve(false)
- },
- data() {},
- close() {},
- },
- }).catch(() => {
+ const socket = createConnection(OAUTH_CALLBACK_PORT, "127.0.0.1")
+ socket.on("connect", () => {
+ socket.destroy()
+ resolve(true)
+ })
+ socket.on("error", () => {
resolve(false)
})
})
diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts
index b8636b0f33..f7f9e36fbf 100644
--- a/packages/opencode/src/plugin/codex.ts
+++ b/packages/opencode/src/plugin/codex.ts
@@ -4,6 +4,7 @@ import { Installation } from "../installation"
import { Auth, OAUTH_DUMMY_KEY } from "../auth"
import os from "os"
import { ProviderTransform } from "@/provider/transform"
+import { setTimeout as sleep } from "node:timers/promises"
const log = Log.create({ service: "plugin.codex" })
@@ -367,6 +368,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise {
"gpt-5.1-codex-max",
"gpt-5.1-codex-mini",
"gpt-5.2",
+ "gpt-5.4",
"gpt-5.2-codex",
"gpt-5.3-codex",
"gpt-5.1-codex",
@@ -608,7 +610,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise {
return { type: "failed" as const }
}
- await Bun.sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
+ await sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
}
},
}
diff --git a/packages/opencode/src/plugin/copilot.ts b/packages/opencode/src/plugin/copilot.ts
index a4f0256877..85708cfc66 100644
--- a/packages/opencode/src/plugin/copilot.ts
+++ b/packages/opencode/src/plugin/copilot.ts
@@ -1,6 +1,7 @@
import type { Hooks, PluginInput } from "@kilocode/plugin"
import { Installation } from "@/installation"
import { iife } from "@/util/iife"
+import { setTimeout as sleep } from "node:timers/promises"
const CLIENT_ID = "Ov23li8tweQw6odWQebz"
// Add a small safety buffer when polling to avoid hitting the server
@@ -270,7 +271,7 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise {
}
if (data.error === "authorization_pending") {
- await Bun.sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
+ await sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
continue
}
@@ -286,13 +287,13 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise {
newInterval = serverInterval * 1000
}
- await Bun.sleep(newInterval + OAUTH_POLLING_SAFETY_MARGIN_MS)
+ await sleep(newInterval + OAUTH_POLLING_SAFETY_MARGIN_MS)
continue
}
if (data.error) return { type: "failed" as const }
- await Bun.sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
+ await sleep(deviceData.interval * 1000 + OAUTH_POLLING_SAFETY_MARGIN_MS)
continue
}
},
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index 950036bf57..836c49996d 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -6,6 +6,7 @@ import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
import { NoSuchModelError, type Provider as SDK } from "ai"
import { Log } from "../util/log"
import { BunProc } from "../bun"
+import { Hash } from "../util/hash"
import { Plugin } from "../plugin"
import {
ModelsDev,
@@ -816,7 +817,7 @@ export namespace Provider {
const modelLoaders: {
[providerID: string]: CustomModelLoader
} = {}
- const sdk = new Map()
+ const sdk = new Map()
log.info("init")
@@ -1111,7 +1112,7 @@ export namespace Provider {
...model.headers,
}
- const key = Bun.hash.xxHash32(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options }))
+ const key = Hash.fast(JSON.stringify({ providerID: model.providerID, npm: model.api.npm, options }))
const existing = s.sdk.get(key)
if (existing) return existing
diff --git a/packages/opencode/src/shell/shell.ts b/packages/opencode/src/shell/shell.ts
index 4a2446b868..21879dd0bf 100644
--- a/packages/opencode/src/shell/shell.ts
+++ b/packages/opencode/src/shell/shell.ts
@@ -3,6 +3,7 @@ import { lazy } from "@/util/lazy"
import { Filesystem } from "@/util/filesystem"
import path from "path"
import { spawn, type ChildProcess } from "child_process"
+import { setTimeout as sleep } from "node:timers/promises"
const SIGKILL_TIMEOUT_MS = 200
@@ -22,13 +23,13 @@ export namespace Shell {
try {
process.kill(-pid, "SIGTERM")
- await Bun.sleep(SIGKILL_TIMEOUT_MS)
+ await sleep(SIGKILL_TIMEOUT_MS)
if (!opts?.exited?.()) {
process.kill(-pid, "SIGKILL")
}
} catch (_e) {
proc.kill("SIGTERM")
- await Bun.sleep(SIGKILL_TIMEOUT_MS)
+ await sleep(SIGKILL_TIMEOUT_MS)
if (!opts?.exited?.()) {
proc.kill("SIGKILL")
}
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
index 113efb5fd7..e4d0f5eb68 100644
--- a/packages/opencode/src/snapshot/index.ts
+++ b/packages/opencode/src/snapshot/index.ts
@@ -1,6 +1,7 @@
import { $ } from "bun"
import path from "path"
import fs from "fs/promises"
+import { Filesystem } from "../util/filesystem"
import { Log } from "../util/log"
import { Flag } from "../flag/flag"
import { Global } from "../global"
@@ -271,13 +272,12 @@ export namespace Snapshot {
const target = path.join(git, "info", "exclude")
await fs.mkdir(path.join(git, "info"), { recursive: true })
if (!file) {
- await Bun.write(target, "")
+ await Filesystem.write(target, "")
return
}
- const text = await Bun.file(file)
- .text()
- .catch(() => "")
- await Bun.write(target, text)
+ const text = await Filesystem.readText(file).catch(() => "")
+
+ await Filesystem.write(target, text)
}
async function excludes() {
diff --git a/packages/opencode/src/util/hash.ts b/packages/opencode/src/util/hash.ts
new file mode 100644
index 0000000000..680e0f40bc
--- /dev/null
+++ b/packages/opencode/src/util/hash.ts
@@ -0,0 +1,7 @@
+import { createHash } from "crypto"
+
+export namespace Hash {
+ export function fast(input: string | Buffer): string {
+ return createHash("sha1").update(input).digest("hex")
+ }
+}
diff --git a/packages/opencode/test/preload.ts b/packages/opencode/test/preload.ts
index a5243736e2..5ae45d30a5 100644
--- a/packages/opencode/test/preload.ts
+++ b/packages/opencode/test/preload.ts
@@ -3,6 +3,7 @@
import os from "os"
import path from "path"
import fs from "fs/promises"
+import { setTimeout as sleep } from "node:timers/promises"
import { afterAll } from "bun:test"
// Set XDG env vars FIRST, before any src/ imports
@@ -15,7 +16,7 @@ afterAll(async () => {
typeof error === "object" && error !== null && "code" in error && error.code === "EBUSY"
const rm = async (left: number): Promise => {
Bun.gc(true)
- await Bun.sleep(100)
+ await sleep(100)
return fs.rm(dir, { recursive: true, force: true }).catch((error) => {
if (!busy(error)) throw error
if (left <= 1) throw error
diff --git a/packages/opencode/test/pty/pty-output-isolation.test.ts b/packages/opencode/test/pty/pty-output-isolation.test.ts
index 44858a0ed2..ec1bbd4690 100644
--- a/packages/opencode/test/pty/pty-output-isolation.test.ts
+++ b/packages/opencode/test/pty/pty-output-isolation.test.ts
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test"
import { Instance } from "../../src/project/instance"
import { Pty } from "../../src/pty"
import { tmpdir } from "../fixture/fixture"
+import { setTimeout as sleep } from "node:timers/promises"
describe("pty", () => {
test("does not leak output when websocket objects are reused", async () => {
@@ -43,7 +44,7 @@ describe("pty", () => {
// Output from a must never show up in b.
Pty.write(a.id, "AAA\n")
- await Bun.sleep(100)
+ await sleep(100)
expect(outB.join("")).not.toContain("AAA")
} finally {
@@ -88,7 +89,7 @@ describe("pty", () => {
}
Pty.write(a.id, "AAA\n")
- await Bun.sleep(100)
+ await sleep(100)
expect(outB.join("")).not.toContain("AAA")
} finally {
@@ -128,7 +129,7 @@ describe("pty", () => {
ctx.connId = 2
Pty.write(a.id, "AAA\n")
- await Bun.sleep(100)
+ await sleep(100)
expect(out.join("")).toContain("AAA")
} finally {
diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts
index 6768e72d95..eba4a99505 100644
--- a/packages/opencode/test/session/retry.test.ts
+++ b/packages/opencode/test/session/retry.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test"
import type { NamedError } from "@opencode-ai/util/error"
import { APICallError } from "ai"
+import { setTimeout as sleep } from "node:timers/promises"
import { SessionRetry } from "../../src/session/retry"
import { MessageV2 } from "../../src/session/message-v2"
@@ -135,7 +136,7 @@ describe("session.message-v2.fromError", () => {
new ReadableStream({
async pull(controller) {
controller.enqueue("Hello,")
- await Bun.sleep(10000)
+ await sleep(10000)
controller.enqueue(" World!")
controller.close()
},
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 91d17ace96..a340e7f6db 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -68,7 +68,8 @@
"solid-js": "catalog:",
"solid-list": "catalog:",
"strip-ansi": "7.1.2",
- "virtua": "catalog:"
+ "virtua": "catalog:",
+ "@typescript/native-preview": "catalog:"
},
"peerDependencies": {}
}
diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts
index 1e16cf8bd7..e8ea12e8ac 100644
--- a/script/upstream/transforms/transform-package-json.ts
+++ b/script/upstream/transforms/transform-package-json.ts
@@ -8,7 +8,8 @@
* 3. Injecting Kilo-specific dependencies
* 4. Preserving Kilo's version number
* 5. Preserving overrides and patchedDependencies
- * 6. Using "newest wins" strategy for dependency versions
+ * 6. Preserving Kilo's repository configuration
+ * 7. Using "newest wins" strategy for dependency versions
*/
import { $ } from "bun"
@@ -333,7 +334,14 @@ export async function transformPackageJson(file: string, options: PackageJsonOpt
}
}
- // 6. Handle workspaces for root package.json
+ // 6. Preserve repository (Kilo-specific, upstream doesn't have this)
+ const ourRepo = ourPkg.repository
+ if (ourRepo && JSON.stringify(pkg.repository) !== JSON.stringify(ourRepo)) {
+ pkg.repository = ourRepo
+ changes.push(`repository: preserved Kilo's repository configuration`)
+ }
+
+ // 7. Handle workspaces for root package.json
// Kilo has removed hosted platform packages (console/*, slack, etc.)
// so we need to preserve Kilo's workspace configuration instead of taking upstream's
const ourWorkspaces = ourPkg.workspaces as { packages?: string[]; catalog?: Record } | undefined
@@ -534,7 +542,14 @@ export async function transformAllPackageJson(options: PackageJsonOptions = {}):
}
}
- // 6. Handle workspaces for root package.json
+ // 6. Preserve repository (Kilo-specific, upstream doesn't have this)
+ const kiloRepo = kiloPkg.repository
+ if (kiloRepo && JSON.stringify(pkg.repository) !== JSON.stringify(kiloRepo)) {
+ pkg.repository = kiloRepo
+ changes.push(`repository: preserved Kilo's repository configuration`)
+ }
+
+ // 7. Handle workspaces for root package.json
// Kilo has removed hosted platform packages (console/*, slack, etc.)
// so we need to preserve Kilo's workspace configuration instead of taking upstream's
const kiloWorkspaces = kiloPkg.workspaces as