Merge pull request #7142 from Kilo-Org/catrielmuller/kilo-opencode-v1.2.19

OpenCode v1.2.19
This commit is contained in:
Catriel Müller
2026-03-17 08:17:02 -03:00
committed by GitHub
40 changed files with 305 additions and 99 deletions
+28 -7
View File
@@ -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=="],
+2 -1
View File
@@ -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}} متاحة الآن للاستخدام.",
+4 -2
View File
@@ -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",
+2 -1
View File
@@ -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}} พร้อมใช้งานแล้ว",
+2 -1
View File
@@ -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",
+2 -1
View File
@@ -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}} 模型了。",
+11 -23
View File
@@ -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={
<>
<div class="shrink-0 py-4 px-3">
<TooltipKeybind
title={language.t("command.session.new")}
keybind={command.keybind("session.new")}
placement="top"
<Button
size="large"
icon="plus-small"
class="w-full"
onClick={() => navigateWithSidebarReset(`/${base64Encode(p.worktree)}/session`)}
>
<Button
size="large"
icon="plus-small"
class="w-full"
onClick={() => navigateWithSidebarReset(`/${base64Encode(p.worktree)}/session`)}
>
{language.t("command.session.new")}
</Button>
</TooltipKeybind>
{language.t("command.session.new")}
</Button>
</div>
<div class="flex-1 min-h-0">
<LocalWorkspace
@@ -1970,15 +1964,9 @@ export default function Layout(props: ParentProps) {
>
<>
<div class="shrink-0 py-4 px-3">
<TooltipKeybind
title={language.t("workspace.new")}
keybind={command.keybind("workspace.new")}
placement="top"
>
<Button size="large" icon="plus-small" class="w-full" onClick={() => createWorkspace(p)}>
{language.t("workspace.new")}
</Button>
</TooltipKeybind>
<Button size="large" icon="plus-small" class="w-full" onClick={() => createWorkspace(p)}>
{language.t("workspace.new")}
</Button>
</div>
<div class="relative flex-1 min-h-0">
<DragDropProvider
@@ -43,7 +43,9 @@ export const ProjectIcon = (props: { project: LocalProject; class?: string; noti
<div class="size-full rounded overflow-clip">
<Avatar
fallback={name()}
src={props.project.id === KILO_PROJECT_ID ? "https://kilo.ai/favicon.svg" : props.project.icon?.override}
src={
props.project.id === KILO_PROJECT_ID ? "https://kilo.ai/favicon.svg" : props.project.icon?.override
}
{...getAvatarColors(props.project.icon?.color)}
class="size-full rounded"
classList={{ "badge-mask": notify() }}
+1
View File
@@ -822,6 +822,7 @@ export default function Page() {
showAllFiles,
tabForPath: file.tab,
openTab: tabs().open,
setActive: tabs().setActive,
loadFile: file.load,
})
@@ -11,12 +11,13 @@ describe("createOpenReviewFile", () => {
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"])
})
})
+8 -3
View File
@@ -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<void>
}) => {
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()
})
}
}
@@ -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
@@ -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
+2 -1
View File
@@ -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.",
+2 -2
View File
@@ -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],
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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<Hooks["auth"]>
@@ -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<string, string> = {}
if (method.prompts) {
for (const prompt of method.prompts) {
+2 -1
View File
@@ -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)
})
},
+3 -2
View File
@@ -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
+2 -1
View File
@@ -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")
+2 -1
View File
@@ -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
+3 -2
View File
@@ -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) => {
+3 -3
View File
@@ -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",
}
+3 -1
View File
@@ -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
@@ -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
+7 -15
View File
@@ -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<boolean> {
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)
})
})
+3 -1
View File
@@ -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<Hooks> {
"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<Hooks> {
return { type: "failed" as const }
}
await Bun.sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
await sleep(interval + OAUTH_POLLING_SAFETY_MARGIN_MS)
}
},
}
+4 -3
View File
@@ -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<Hooks> {
}
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<Hooks> {
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
}
},
+3 -2
View File
@@ -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<number, SDK>()
const sdk = new Map<string, SDK>()
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
+3 -2
View File
@@ -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")
}
+5 -5
View File
@@ -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() {
+7
View File
@@ -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")
}
}
+2 -1
View File
@@ -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<void> => {
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
@@ -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 {
+2 -1
View File
@@ -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()
},
+2 -1
View File
@@ -68,7 +68,8 @@
"solid-js": "catalog:",
"solid-list": "catalog:",
"strip-ansi": "7.1.2",
"virtua": "catalog:"
"virtua": "catalog:",
"@typescript/native-preview": "catalog:"
},
"peerDependencies": {}
}
+31
View File
@@ -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.
@@ -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<string, string> } | 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
+4
View File
@@ -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",
],
+113
View File
@@ -215,6 +215,17 @@ export async function checkoutTheirs(files: string[]): Promise<void> {
}
}
/**
* 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<void> {
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<boolean> {
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<void> {
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<number> {
// 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<string[]> {
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)
}