diff --git a/bun.lock b/bun.lock
index 56bb0f5c310..c1a64cf5a8b 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",
@@ -2780,7 +2781,7 @@
"fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
- "fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+ "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
"fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="],
@@ -4402,8 +4403,6 @@
"@aws-sdk/token-providers/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
- "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
-
"@azure/core-http/@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="],
"@azure/core-http/@azure/core-tracing": ["@azure/core-tracing@1.0.0-preview.13", "", { "dependencies": { "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" } }, "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ=="],
@@ -4532,8 +4531,6 @@
"@morphllm/morphsdk/diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="],
- "@morphllm/morphsdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
-
"@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
@@ -5260,8 +5257,6 @@
"@kilocode/cli/@morphllm/morphsdk/diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="],
- "@kilocode/cli/@morphllm/morphsdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
-
"@kilocode/kilo-gateway/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.75", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gGaGZjkFpqcXJk6321JzhRl66pM2VxBlI470L8W4DQUW4S6iDT1R9L7awSzGB4Cn9toUl7DTV8BemaXZYXV4SA=="],
"@kilocode/kilo-gateway/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.75", "", { "os": "darwin", "cpu": "x64" }, "sha512-tPlvqQI0whZ76amHydpJs5kN+QeWAIcFbI8RAtlAo9baj2EbxTDC+JGwgb9Fnt0/YQx831humbtaNDhV2Jt1bw=="],
@@ -5448,6 +5443,8 @@
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+ "aws-sdk/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
+
"babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
"babel-plugin-module-resolver/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
@@ -5702,10 +5699,32 @@
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
+ "@aws-sdk/client-cognito-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.11", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.4.1", "tslib": "^2.6.2" } }, "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ=="],
+ "@aws-sdk/credential-provider-env/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/credential-provider-http/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/credential-provider-ini/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/credential-provider-login/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
"@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.933.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.933.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-o1GX0+IPlFi/D8ei9y/jj3yucJWNfPnbB5appVBWevAyUdZA5KzQ2nK/hDxiu9olTZlFEFpf1m1Rn3FaGxHqsw=="],
+ "@aws-sdk/credential-provider-process/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/credential-provider-sso/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/credential-provider-web-identity/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/credential-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
+ "@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
"@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@electron/rebuild/ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
@@ -6146,6 +6165,8 @@
"test-exclude/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+ "@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.4.1", "", { "dependencies": { "fast-xml-builder": "^1.0.0", "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A=="],
+
"@electron/rebuild/ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"@electron/rebuild/ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts
index edf33607314..b1b83350694 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 5ca652a639b..4a2bca5f291 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 f07df4a2f20..b52bd580677 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 242ce219217..10e72dc9851 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 c71e66268b1..505cf2a530f 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 b9c8a31c9d5..e042cdaca65 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 20f1d99a8be..60b26cdf47c 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 192c7667d9d..1f756fc2454 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/app/src/pages/session/use-session-hash-scroll.ts b/packages/app/src/pages/session/use-session-hash-scroll.ts
index 71b9a217d70..74b0ca83ce0 100644
--- a/packages/app/src/pages/session/use-session-hash-scroll.ts
+++ b/packages/app/src/pages/session/use-session-hash-scroll.ts
@@ -55,6 +55,7 @@ export const useSessionHashScroll = (input: {
}
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
+ console.log({ message, behavior })
if (input.currentMessageId() !== message.id) input.setActiveMessage(message)
const index = messageIndex().get(message.id) ?? -1
diff --git a/packages/desktop/src/i18n/de.ts b/packages/desktop/src/i18n/de.ts
index 224e0b62d2c..6ef1167a362 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/script/build.ts b/packages/opencode/script/build.ts
index 6bde9ff754e..264ab6af389 100755
--- a/packages/opencode/script/build.ts
+++ b/packages/opencode/script/build.ts
@@ -179,8 +179,8 @@ for (const item of targets) {
autoloadTsconfig: true,
autoloadPackageJson: true,
target: name.replace(pkg.name, "bun") as any,
- outfile: `dist/${name}/bin/kilo`,
- execArgv: [`--user-agent=kilo/${Script.version}`, "--use-system-ca", "--"],
+ outfile: `dist/${name}/bin/kilo`, // kilocode_change
+ execArgv: [`--user-agent=kilo/${Script.version}`, "--use-system-ca", "--"], // kilocode_change
windows: {},
},
entrypoints: ["./src/index.ts", parserWorker, workerPath],
diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts
index 075b9ca5bb9..62690a6ff1e 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 cce7f2dd954..53a5f4bdafc 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 d83c4ed8a43..4b8a3e7d453 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 e7891f2ab26..cc7ac4c1892 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 55e31778954..8a0df9a569c 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 fdadc1be9b6..007c5fe5f27 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 21997e881a2..f7560b7491f 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 41e6f672f63..9bbc7cf97d9 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 e28c576586b..540e9976db1 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 30d7affb2f6..5122958c902 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 120c21cf031..6132f6b7892 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 b8636b0f33c..f7f9e36fbff 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 a4f0256877b..85708cfc66b 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 950036bf57d..836c49996df 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 4a2446b8689..21879dd0bf5 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 113efb5fd70..e4d0f5eb688 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 00000000000..680e0f40bc8
--- /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 a5243736e2f..5ae45d30a57 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 44858a0ed27..ec1bbd46901 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 6768e72d95a..eba4a995053 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 91d17ace965..a340e7f6db7 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/merge.ts b/script/upstream/merge.ts
index da933f93597..ce5f3e87965 100644
--- a/script/upstream/merge.ts
+++ b/script/upstream/merge.ts
@@ -141,6 +141,22 @@ async function main() {
const currentBranch = await git.getCurrentBranch()
logger.info(`Current branch: ${currentBranch}`)
+ // Enable git rerere so conflict resolutions are recorded and reused across merges
+ if (!options.dryRun) {
+ await git.ensureRerere()
+ logger.info("git rerere enabled (resolutions will be recorded and reused automatically)")
+
+ // Train rerere from past upstream merge commits so the cache is populated
+ // even on a fresh clone. This replays past merges to learn their resolutions.
+ logger.info("Training rerere cache from past merge history...")
+ const learned = await git.trainRerere("merge: upstream\\|Resolve merge conflict")
+ if (learned > 0) {
+ logger.success(`Learned ${learned} conflict resolution(s) from history`)
+ } else {
+ logger.info("No new resolutions to learn from history (cache already up to date)")
+ }
+ }
+
// Step 2: Fetch upstream
logger.step(2, 8, "Fetching upstream...")
@@ -362,6 +378,13 @@ async function main() {
const keepOursResults = await resetToOurs(config.keepOurs, { dryRun: false, verbose: options.verbose })
logger.success(`Reset ${keepOursResults.length} files to Kilo's version`)
+ // Clean untracked build artifacts from Kilo-specific directories.
+ // These packages don't exist in upstream, so their .gitignore files are absent
+ // on the opencode branch. Artifacts like bin/, out/, .next/ etc. would otherwise
+ // be picked up by the git add -A below.
+ logger.info("Cleaning Kilo-specific directory artifacts...")
+ await git.cleanDirectories(config.kiloDirectories)
+
// Commit all transformations
await git.stageAll()
await git.commit(`refactor: kilo compat for ${targetVersion.tag}`)
@@ -378,6 +401,14 @@ async function main() {
logger.info("Conflicted files:")
logger.list(mergeResult.conflicts)
+ // Check if git rerere already auto-resolved any conflicts from recorded history.
+ // rerere.autoupdate stages them automatically; we just log how many were handled.
+ const rerereResolved = await git.getRerereResolved()
+ if (rerereResolved.length > 0) {
+ logger.success(`git rerere auto-resolved ${rerereResolved.length} conflict(s) from recorded history:`)
+ logger.list(rerereResolved)
+ }
+
// Since we applied all branding transforms pre-merge, remaining conflicts should be minimal.
// These are likely files with kilocode_change markers or actual logic differences.
diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts
index 1e16cf8bd75..e8ea12e8ac9 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
diff --git a/script/upstream/utils/config.ts b/script/upstream/utils/config.ts
index 19b1bc3c29a..b0fe7318a58 100644
--- a/script/upstream/utils/config.ts
+++ b/script/upstream/utils/config.ts
@@ -203,6 +203,10 @@ export const defaultConfig: MergeConfig = {
"packages/opencode/test/kilocode",
"packages/kilo-gateway",
"packages/kilo-telemetry",
+ "packages/kilo-vscode",
+ "packages/kilo-ui",
+ "packages/kilo-docs",
+ "packages/kilo-i18n",
"script/upstream",
],
diff --git a/script/upstream/utils/git.ts b/script/upstream/utils/git.ts
index 0daf573dcf2..cf4fef55ee5 100644
--- a/script/upstream/utils/git.ts
+++ b/script/upstream/utils/git.ts
@@ -215,6 +215,17 @@ export async function checkoutTheirs(files: string[]): Promise {
}
}
+/**
+ * Remove untracked files and directories from specific directories.
+ * Used to clean build artifacts from Kilo-specific directories after checking
+ * out the upstream branch, where package-level .gitignore files don't exist.
+ */
+export async function cleanDirectories(dirs: string[]): Promise {
+ for (const dir of dirs) {
+ await $`git clean -fd ${dir}`.quiet().nothrow()
+ }
+}
+
/**
* Check if the "ours" version of a conflicted file contains kilocode_change markers.
* Uses git stage :2: which is the "ours" side during a merge conflict.
@@ -225,3 +236,105 @@ export async function oursHasKilocodeChanges(file: string): Promise {
if (result.exitCode !== 0) return false
return result.stdout.toString().includes("kilocode_change")
}
+
+/**
+ * Enable git rerere (REuse REcorded REsolution) in the local repo config.
+ * Also enables autoupdate so resolved files are automatically staged.
+ */
+export async function ensureRerere(): Promise {
+ await $`git config rerere.enabled true`.quiet()
+ await $`git config rerere.autoupdate true`.quiet()
+}
+
+/**
+ * Train the rerere cache from past merge commits in the repo history.
+ * Implements the same logic as git's contrib/rerere-train.sh:
+ * For each merge commit in the range, replay the merge to let rerere
+ * record the pre-image, then check out the resolved tree so rerere
+ * records the post-image (the resolution).
+ *
+ * Returns the number of resolutions learned.
+ */
+export async function trainRerere(grep: string): Promise {
+ // Save the current HEAD so we can restore it afterwards
+ const headResult = await $`git symbolic-ref -q HEAD`.quiet().nothrow()
+ const branch = headResult.exitCode === 0 ? headResult.stdout.toString().trim() : null
+ const originalHead = branch ?? (await $`git rev-parse --verify HEAD`.text()).trim()
+
+ let learned = 0
+
+ try {
+ // Find all merge commits matching the grep pattern (merges have multiple parents)
+ const revList = await $`git rev-list --parents --all --grep=${grep}`.quiet().nothrow()
+ if (revList.exitCode !== 0 || !revList.stdout.toString().trim()) return 0
+
+ const lines = revList.stdout
+ .toString()
+ .trim()
+ .split("\n")
+ .filter((l) => l.trim())
+
+ for (const line of lines) {
+ const parts = line.trim().split(/\s+/)
+ if (parts.length < 3) continue // skip non-merges (need commit + at least 2 parents)
+
+ const [commit, parent1, ...otherParents] = parts
+
+ // Checkout the first parent
+ const coResult = await $`git checkout -q ${parent1}`.quiet().nothrow()
+ if (coResult.exitCode !== 0) continue
+
+ // Attempt the merge - we expect it to fail with conflicts
+ const mergeResult = await $`git merge --no-gpg-sign ${otherParents}`.quiet().nothrow()
+ if (mergeResult.exitCode === 0) {
+ // Cleanly merged — no conflicts to learn from, reset and skip
+ await $`git reset -q --hard`.quiet().nothrow()
+ continue
+ }
+
+ // Check if rerere recorded a pre-image (MERGE_RR exists and is non-empty)
+ const mergeRR = Bun.file(`${process.env.GIT_DIR || ".git"}/MERGE_RR`)
+ const hasMergeRR = await mergeRR.exists().catch(() => false)
+ if (!hasMergeRR) {
+ await $`git reset -q --hard`.quiet().nothrow()
+ continue
+ }
+
+ // Record the conflict pre-image
+ await $`git rerere`.quiet().nothrow()
+
+ // Apply the actual resolution by checking out the merge commit's tree
+ await $`git checkout -q ${commit} -- .`.quiet().nothrow()
+
+ // Record the resolution post-image
+ await $`git rerere`.quiet().nothrow()
+
+ learned++
+ await $`git reset -q --hard`.quiet().nothrow()
+ }
+ } finally {
+ // Always restore original branch
+ if (branch) {
+ await $`git checkout ${branch.replace("refs/heads/", "")}`.quiet().nothrow()
+ } else {
+ await $`git checkout ${originalHead}`.quiet().nothrow()
+ }
+ }
+
+ return learned
+}
+
+/**
+ * Return files that git rerere has already auto-resolved.
+ * These files no longer have conflict markers but haven't been staged yet
+ * (unless rerere.autoupdate is true, in which case they're already staged).
+ */
+export async function getRerereResolved(): Promise {
+ const result = await $`git rerere status`.quiet().nothrow()
+ if (result.exitCode !== 0 || !result.stdout.toString().trim()) return []
+ return result.stdout
+ .toString()
+ .trim()
+ .split("\n")
+ .filter((f) => f.length > 0)
+}