Merge pull request #149 from Kilo-Org/catrielmuller/kilo-opencode-v1.1.52

OpenCode v1.1.52
This commit is contained in:
Catriel Müller
2026-02-09 11:47:24 -03:00
committed by GitHub
101 changed files with 2937 additions and 1203 deletions
+86 -3
View File
@@ -373,6 +373,7 @@
"@ai-sdk/xai": "2.0.56",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.4.0",
"@gitlab/opencode-gitlab-auth": "1.3.2",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@kilocode/kilo-gateway": "workspace:*",
@@ -397,7 +398,7 @@
"ai": "catalog:",
"ai-gateway-provider": "2.3.1",
"bonjour-service": "1.3.0",
"bun-pty": "0.4.4",
"bun-pty": "0.4.8",
"chokidar": "4.0.3",
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
@@ -1039,8 +1040,22 @@
"@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.6", "", { "dependencies": { "@expressive-code/core": "^0.41.6" } }, "sha512-PBFa1wGyYzRExMDzBmAWC6/kdfG1oLn4pLpBeTfIRrALPjcGA/59HP3e7q9J0Smk4pC7U+lWkA2LHR8FYV8U7Q=="],
"@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.5", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
"@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
"@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="],
"@fastify/forwarded": ["@fastify/forwarded@3.0.1", "", {}, "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw=="],
"@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
"@fastify/proxy-addr": ["@fastify/proxy-addr@5.1.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw=="],
"@fastify/rate-limit": ["@fastify/rate-limit@10.3.0", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "fastify-plugin": "^5.0.0", "toad-cache": "^3.7.0" } }, "sha512-eIGkG9XKQs0nyynatApA3EVrojHOuq4l6fhB4eeCk4PIOeadvOJz9/4w3vGI44Go17uaXOWEcPkaD8kuKm7g6Q=="],
"@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
@@ -1055,6 +1070,8 @@
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.4.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-1fEZgqjSZ0WLesftw/J5UtFuJCYFDvCZCHhTH5PZAmpDEmCwllJBoe84L3+vIk38V2FGDMTW128iKTB2mVzr3A=="],
"@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.2", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-pvGrC+aDVLY8bRCC/fZaG/Qihvt2r4by5xbTo5JTSz9O7yIcR6xG2d9Wkuu4bcXFz674z2C+i5bUk+J/RSdBpg=="],
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
@@ -1271,6 +1288,8 @@
"@leichtgewicht/ip-codec": ["@leichtgewicht/ip-codec@2.0.5", "", {}, "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="],
"@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
"@mixmark-io/domino": ["@mixmark-io/domino@2.2.0", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
@@ -1359,8 +1378,12 @@
"@opencode-ai/function": ["@opencode-ai/function@workspace:packages/function"],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.52", "", { "dependencies": { "@opencode-ai/sdk": "1.1.52", "zod": "4.1.8" } }, "sha512-xmh4aKLgcUDcVdtTKdUXtX1ts8RngLLbwcIMUDEc/fOq0Y+3Dh52iMc13Daj/AhkR9j5Zs/+QIZnAo49QMSPgg=="],
"@opencode-ai/script": ["@opencode-ai/script@workspace:packages/script"],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.52", "", {}, "sha512-WtyQRGv1xdfMxVXySyKB7NbzmEcR+sqyHwWvndeY3dnxN+kxyezOjVatCB10zxznE9dz3qWIMwemVB0FwiXo/A=="],
"@opencode-ai/slack": ["@opencode-ai/slack@workspace:packages/slack"],
"@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"],
@@ -1523,6 +1546,8 @@
"@pierre/diffs": ["@pierre/diffs@1.0.2", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "3.19.0", "@shikijs/transformers": "3.19.0", "diff": "8.0.2", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "3.19.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-RkFSDD5X/U+8QjyilPViYGJfmJNWXR17zTL8zw48+DcVC1Ujbh6I1edyuRnFfgRzpft05x2DSCkz2cjoIAxPvQ=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@planetscale/database": ["@planetscale/database@1.19.0", "", {}, "sha512-Tv4jcFUFAFjOWrGSio49H6R2ijALv0ZzVBfJKIdm+kl9X046Fh4LLawrF9OMsglVbK6ukqMJsUCeucGAFTBcMA=="],
@@ -2131,6 +2156,8 @@
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
@@ -2207,10 +2234,14 @@
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
"autoprefixer": ["autoprefixer@10.4.24", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
"await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
"aws-sdk": ["aws-sdk@2.1692.0", "", { "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.6.2" } }, "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw=="],
@@ -2297,7 +2328,7 @@
"bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
"bun-pty": ["bun-pty@0.4.4", "", {}, "sha512-WK4G6uWsZgu1v4hKIlw6G1q2AOf8Rbga2Yr7RnxArVjjyb+mtVa/CFc9GOJf+OYSJSH8k7LonAtQOVeNAddRyg=="],
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
@@ -2689,6 +2720,8 @@
"fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
@@ -2697,12 +2730,20 @@
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-json-stringify": ["fast-json-stringify@6.2.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-Eaf/KNIDwHkzfyeQFNfLXJnQ7cl1XQI3+zRqmPlvtkMigbXnAcasTrvJQmquBSxKfFGeRA6PFog8t+hFmpDoWw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
"fastify": ["fastify@5.7.4", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA=="],
"fastify-plugin": ["fastify-plugin@5.1.0", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="],
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
@@ -2719,6 +2760,8 @@
"find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
"find-my-way": ["find-my-way@9.4.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"finity": ["finity@0.5.4", "", {}, "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA=="],
@@ -2965,7 +3008,7 @@
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
@@ -3117,6 +3160,8 @@
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="],
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
@@ -3165,6 +3210,8 @@
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
"light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
@@ -3489,6 +3536,8 @@
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -3605,6 +3654,12 @@
"pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="],
"pino": ["pino@10.3.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA=="],
"pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
@@ -3661,6 +3716,8 @@
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
"promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
@@ -3683,6 +3740,8 @@
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
@@ -3721,6 +3780,8 @@
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
"recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="],
"recma-jsx": ["recma-jsx@1.0.1", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="],
@@ -3791,6 +3852,8 @@
"restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="],
"ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
"retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="],
"retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="],
@@ -3803,6 +3866,8 @@
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
"rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
@@ -3825,6 +3890,10 @@
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"safe-regex2": ["safe-regex2@5.0.0", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="],
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
@@ -3833,6 +3902,8 @@
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
"secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
"selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -3849,6 +3920,8 @@
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
@@ -3913,6 +3986,8 @@
"solid-use": ["solid-use@0.9.1", "", { "peerDependencies": { "solid-js": "^1.7" } }, "sha512-UwvXDVPlrrbj/9ewG9ys5uL2IO4jSiwys2KPzK4zsnAcmEl7iDafZWW1Mo4BSEWOmQCGK6IvpmGHo1aou8iOFw=="],
"sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -3929,6 +4004,8 @@
"spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
@@ -4041,6 +4118,8 @@
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="],
"three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
"thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="],
@@ -4781,6 +4860,8 @@
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -4871,6 +4952,8 @@
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"read-cache/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
+7 -4
View File
@@ -7,6 +7,8 @@
"packageManager": "bun@1.3.5",
"scripts": {
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"dev:desktop": "bun --cwd packages/desktop tauri dev",
"dev:web": "bun --cwd packages/app dev",
"typecheck": "bun turbo typecheck",
"prepare": "husky",
"random": "echo 'Random script'",
@@ -18,8 +20,8 @@
"packages/*",
"packages/console/*",
"packages/sdk/js",
"packages/slack",
"script/upstream"
"script/upstream",
"packages/slack"
],
"catalog": {
"@types/bun": "1.3.5",
@@ -75,8 +77,8 @@
"dependencies": {
"@aws-sdk/client-s3": "3.933.0",
"@kilocode/plugin": "workspace:*",
"@kilocode/sdk": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@kilocode/sdk": "workspace:*",
"typescript": "catalog:"
},
"repository": {
@@ -102,5 +104,6 @@
"patchedDependencies": {
"ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch"
},
"version": "1.0.14"
"version": "1.0.14",
"peerDependencies": {}
}
+64 -6
View File
@@ -21,7 +21,12 @@ import {
import type { createSdk } from "./utils"
export async function defocus(page: Page) {
await page.mouse.click(5, 5)
await page
.evaluate(() => {
const el = document.activeElement
if (el instanceof HTMLElement) el.blur()
})
.catch(() => undefined)
}
export async function openPalette(page: Page) {
@@ -68,14 +73,50 @@ export async function toggleSidebar(page: Page) {
export async function openSidebar(page: Page) {
if (!(await isSidebarClosed(page))) return
const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
const visible = await button
.isVisible()
.then((x) => x)
.catch(() => false)
if (visible) await button.click()
if (!visible) await toggleSidebar(page)
const main = page.locator("main")
const opened = await expect(main)
.not.toHaveClass(/xl:border-l/, { timeout: 1500 })
.then(() => true)
.catch(() => false)
if (opened) return
await toggleSidebar(page)
await expect(page.locator("main")).not.toHaveClass(/xl:border-l/)
await expect(main).not.toHaveClass(/xl:border-l/)
}
export async function closeSidebar(page: Page) {
if (await isSidebarClosed(page)) return
const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
const visible = await button
.isVisible()
.then((x) => x)
.catch(() => false)
if (visible) await button.click()
if (!visible) await toggleSidebar(page)
const main = page.locator("main")
const closed = await expect(main)
.toHaveClass(/xl:border-l/, { timeout: 1500 })
.then(() => true)
.catch(() => false)
if (closed) return
await toggleSidebar(page)
await expect(page.locator("main")).toHaveClass(/xl:border-l/)
await expect(main).toHaveClass(/xl:border-l/)
}
export async function openSettings(page: Page) {
@@ -182,13 +223,30 @@ export async function hoverSessionItem(page: Page, sessionID: string) {
}
export async function openSessionMoreMenu(page: Page, sessionID: string) {
const sessionEl = await hoverSessionItem(page, sessionID)
await expect(page).toHaveURL(new RegExp(`/session/${sessionID}(?:[/?#]|$)`))
const menuTrigger = sessionEl.locator(dropdownMenuTriggerSelector).first()
const scroller = page.locator(".session-scroller").first()
await expect(scroller).toBeVisible()
await expect(scroller.getByRole("heading", { level: 1 }).first()).toBeVisible({ timeout: 30_000 })
const menu = page
.locator(dropdownMenuContentSelector)
.filter({ has: page.getByRole("menuitem", { name: /rename/i }) })
.filter({ has: page.getByRole("menuitem", { name: /archive/i }) })
.filter({ has: page.getByRole("menuitem", { name: /delete/i }) })
.first()
const opened = await menu
.isVisible()
.then((x) => x)
.catch(() => false)
if (opened) return menu
const menuTrigger = scroller.getByRole("button", { name: /more options/i }).first()
await expect(menuTrigger).toBeVisible()
await menuTrigger.click()
const menu = page.locator(dropdownMenuContentSelector).first()
await expect(menu).toBeVisible()
return menu
}
+56 -14
View File
@@ -11,57 +11,98 @@ import { sessionItemSelector, inlineInputSelector } from "../selectors"
const shareDisabled = process.env.OPENCODE_DISABLE_SHARE === "true" || process.env.OPENCODE_DISABLE_SHARE === "1"
test("sidebar session can be renamed", async ({ page, sdk, gotoSession }) => {
type Sdk = Parameters<typeof withSession>[0]
async function seedMessage(sdk: Sdk, sessionID: string) {
await sdk.session.promptAsync({
sessionID,
noReply: true,
parts: [{ type: "text", text: "e2e seed" }],
})
await expect
.poll(
async () => {
const messages = await sdk.session.messages({ sessionID, limit: 1 }).then((r) => r.data ?? [])
return messages.length
},
{ timeout: 30_000 },
)
.toBeGreaterThan(0)
}
test("session can be renamed via header menu", async ({ page, sdk, gotoSession }) => {
const stamp = Date.now()
const originalTitle = `e2e rename test ${stamp}`
const newTitle = `e2e renamed ${stamp}`
await withSession(sdk, originalTitle, async (session) => {
await seedMessage(sdk, session.id)
await gotoSession(session.id)
await openSidebar(page)
const menu = await openSessionMoreMenu(page, session.id)
await clickMenuItem(menu, /rename/i)
const input = page.locator(sessionItemSelector(session.id)).locator(inlineInputSelector).first()
const input = page.locator(".session-scroller").locator(inlineInputSelector).first()
await expect(input).toBeVisible()
await input.fill(newTitle)
await input.press("Enter")
await expect(page.locator(sessionItemSelector(session.id)).locator("a").first()).toContainText(newTitle)
await expect(page.getByRole("heading", { level: 1 }).first()).toContainText(newTitle)
})
})
test("sidebar session can be archived", async ({ page, sdk, gotoSession }) => {
test("session can be archived via header menu", async ({ page, sdk, gotoSession }) => {
const stamp = Date.now()
const title = `e2e archive test ${stamp}`
await withSession(sdk, title, async (session) => {
await seedMessage(sdk, session.id)
await gotoSession(session.id)
await openSidebar(page)
const sessionEl = page.locator(sessionItemSelector(session.id))
const menu = await openSessionMoreMenu(page, session.id)
await clickMenuItem(menu, /archive/i)
await expect(sessionEl).not.toBeVisible()
await expect
.poll(
async () => {
const data = await sdk.session.get({ sessionID: session.id }).then((r) => r.data)
return data?.time?.archived
},
{ timeout: 30_000 },
)
.not.toBeUndefined()
await openSidebar(page)
await expect(page.locator(sessionItemSelector(session.id))).toHaveCount(0)
})
})
test("sidebar session can be deleted", async ({ page, sdk, gotoSession }) => {
test("session can be deleted via header menu", async ({ page, sdk, gotoSession }) => {
const stamp = Date.now()
const title = `e2e delete test ${stamp}`
await withSession(sdk, title, async (session) => {
await seedMessage(sdk, session.id)
await gotoSession(session.id)
await openSidebar(page)
const sessionEl = page.locator(sessionItemSelector(session.id))
const menu = await openSessionMoreMenu(page, session.id)
await clickMenuItem(menu, /delete/i)
await confirmDialog(page, /delete/i)
await expect(sessionEl).not.toBeVisible()
await expect
.poll(
async () => {
const data = await sdk.session
.get({ sessionID: session.id })
.then((r) => r.data)
.catch(() => undefined)
return data?.id
},
{ timeout: 30_000 },
)
.toBeUndefined()
await openSidebar(page)
await expect(page.locator(sessionItemSelector(session.id))).toHaveCount(0)
})
})
@@ -72,6 +113,7 @@ test("session can be shared and unshared via header button", async ({ page, sdk,
const title = `e2e share test ${stamp}`
await withSession(sdk, title, async (session) => {
await seedMessage(sdk, session.id)
await gotoSession(session.id)
const { rightSection, popoverBody } = await openSharePopover(page)
@@ -1,5 +1,5 @@
import { test, expect } from "../fixtures"
import { openStatusPopover, defocus } from "../actions"
import { openStatusPopover } from "../actions"
test("status popover opens and shows tabs", async ({ page, gotoSession }) => {
await gotoSession()
@@ -88,7 +88,7 @@ test("status popover closes when clicking outside", async ({ page, gotoSession }
const { popoverBody } = await openStatusPopover(page)
await expect(popoverBody).toBeVisible()
await defocus(page)
await page.getByRole("main").click({ position: { x: 5, y: 5 } })
await expect(popoverBody).toHaveCount(0)
})
+2 -1
View File
@@ -66,5 +66,6 @@
"virtua": "catalog:",
"zod": "catalog:",
"@kilocode/kilo-i18n": "workspace:*"
}
},
"peerDependencies": {}
}
+4 -1
View File
@@ -1220,7 +1220,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
return undefined
})
if (session) navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
if (session) {
layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
}
}
if (!session) return
+23
View File
@@ -1,5 +1,6 @@
import type { Ghostty, Terminal as Term, FitAddon } from "ghostty-web"
import { ComponentProps, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
import { usePlatform } from "@/context/platform"
import { useSDK } from "@/context/sdk"
import { monoFontFamily, useSettings } from "@/context/settings"
import { SerializeAddon } from "@/addons/serialize"
@@ -52,6 +53,7 @@ const DEFAULT_TERMINAL_COLORS: Record<"light" | "dark", TerminalColors> = {
}
export const Terminal = (props: TerminalProps) => {
const platform = usePlatform()
const sdk = useSDK()
const settings = useSettings()
const theme = useTheme()
@@ -135,6 +137,22 @@ export const Terminal = (props: TerminalProps) => {
focusTerminal()
}
const handleLinkClick = (event: MouseEvent) => {
if (!event.shiftKey && !event.ctrlKey && !event.metaKey) return
if (event.altKey) return
if (event.button !== 0) return
const t = term
if (!t) return
const link = (t as unknown as { currentHoveredLink?: { text: string } }).currentHoveredLink
if (!link?.text) return
event.preventDefault()
event.stopImmediatePropagation()
platform.openLink(link.text)
}
onMount(() => {
const run = async () => {
const loaded = await loadGhostty()
@@ -146,6 +164,7 @@ export const Terminal = (props: TerminalProps) => {
const once = { value: false }
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect?directory=${encodeURIComponent(sdk.directory)}`)
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
if (window.__OPENCODE__?.serverPassword) {
url.username = "opencode"
url.password = window.__OPENCODE__?.serverPassword
@@ -166,6 +185,7 @@ export const Terminal = (props: TerminalProps) => {
fontSize: 14,
fontFamily: monoFontFamily(settings.appearance.font()),
allowTransparency: true,
convertEol: true,
theme: terminalColors(),
scrollback: 10_000,
ghostty: g,
@@ -239,6 +259,9 @@ export const Terminal = (props: TerminalProps) => {
container.addEventListener("pointerdown", handlePointerDown)
cleanups.push(() => container.removeEventListener("pointerdown", handlePointerDown))
container.addEventListener("click", handleLinkClick, { capture: true })
cleanups.push(() => container.removeEventListener("click", handleLinkClick, { capture: true }))
handleTextareaFocus = () => {
t.options.cursorBlink = true
}
+10
View File
@@ -70,6 +70,14 @@ function createCommentSession(dir: string, id: string | undefined) {
setFocus((current) => (current?.id === id ? null : current))
}
const clear = () => {
batch(() => {
setStore("comments", {})
setFocus(null)
setActive(null)
})
}
const all = createMemo(() => {
const files = Object.keys(store.comments)
const items = files.flatMap((file) => store.comments[file] ?? [])
@@ -82,6 +90,7 @@ function createCommentSession(dir: string, id: string | undefined) {
all,
add,
remove,
clear,
focus: createMemo(() => state.focus),
setFocus,
clearFocus: () => setFocus(null),
@@ -144,6 +153,7 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
all: () => session().all(),
add: (input: Omit<LineComment, "id" | "time">) => session().add(input),
remove: (file: string, id: string) => session().remove(file, id),
clear: () => session().clear(),
focus: () => session().focus(),
setFocus: (focus: CommentFocus | null) => session().setFocus(focus),
clearFocus: () => session().clearFocus(),
+11 -9
View File
@@ -1,5 +1,5 @@
import { createEffect, createMemo, createRoot, onCleanup } from "solid-js"
import { createStore, produce } from "solid-js/store"
import { batch, createEffect, createMemo, createRoot, onCleanup } from "solid-js"
import { createStore, produce, reconcile } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import type { FileContent, FileNode } from "@kilocode/sdk/v2"
import { showToast } from "@opencode-ai/ui/toast"
@@ -277,10 +277,8 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
const scope = createMemo(() => sdk.directory)
const directory = createMemo(() => sync.data.path.directory)
function normalize(input: string) {
const root = directory()
const root = scope()
const prefix = root.endsWith("/") ? root : root + "/"
let path = unquoteGitPath(stripQueryAndHash(stripFileProtocol(input)))
@@ -371,9 +369,13 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
inflight.clear()
treeInflight.clear()
contentLru.clear()
setStore("file", {})
setTree("node", {})
setTree("dir", { "": { expanded: true } })
batch(() => {
setStore("file", reconcile({}))
setTree("node", reconcile({}))
setTree("dir", reconcile({}))
setTree("dir", "", { expanded: true })
})
})
const viewCache = new Map<string, ViewCacheEntry>()
@@ -414,7 +416,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
return entry.value
}
const view = createMemo(() => loadView(params.dir!, params.id))
const view = createMemo(() => loadView(scope(), params.id))
function ensure(path: string) {
if (!path) return
+10
View File
@@ -18,6 +18,7 @@ import { dict as ar } from "@/i18n/ar"
import { dict as no } from "@/i18n/no"
import { dict as br } from "@/i18n/br"
import { dict as th } from "@/i18n/th"
import { dict as bs } from "@/i18n/bs"
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
@@ -33,6 +34,8 @@ import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
// kilocode_change start
import { dict as kiloEn } from "@kilocode/kilo-i18n/en"
import { dict as kiloZh } from "@kilocode/kilo-i18n/zh"
@@ -49,6 +52,7 @@ import { dict as kiloAr } from "@kilocode/kilo-i18n/ar"
import { dict as kiloNo } from "@kilocode/kilo-i18n/no"
import { dict as kiloBr } from "@kilocode/kilo-i18n/br"
import { dict as kiloTh } from "@kilocode/kilo-i18n/th"
import { dict as kiloBs } from "@kilocode/kilo-i18n/bs"
// kilocode_change end
export type Locale =
@@ -67,6 +71,7 @@ export type Locale =
| "no"
| "br"
| "th"
| "bs"
type RawDictionary = typeof en & typeof uiEn & typeof kiloEn // kilocode_change
type Dictionary = i18n.Flatten<RawDictionary>
@@ -83,6 +88,7 @@ const LOCALES: readonly Locale[] = [
"ja",
"pl",
"ru",
"bs",
"ar",
"no",
"br",
@@ -116,6 +122,7 @@ function detectLocale(): Locale {
return "no"
if (language.toLowerCase().startsWith("pt")) return "br"
if (language.toLowerCase().startsWith("th")) return "th"
if (language.toLowerCase().startsWith("bs")) return "bs"
}
return "en"
@@ -146,6 +153,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
if (store.locale === "no") return "no"
if (store.locale === "br") return "br"
if (store.locale === "th") return "th"
if (store.locale === "bs") return "bs"
return "en"
})
@@ -172,6 +180,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo, ...kiloNo }) }
if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr, ...kiloBr }) }
if (locale() === "th") return { ...base, ...i18n.flatten({ ...th, ...uiTh, ...kiloTh }) }
if (locale() === "bs") return { ...base, ...i18n.flatten({ ...bs, ...uiBs, ...kiloBs }) }
return { ...base, ...i18n.flatten({ ...ko, ...uiKo, ...kiloKo }) }
})
// kilocode_change end
@@ -194,6 +203,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont
no: "language.no",
br: "language.br",
th: "language.th",
bs: "language.bs",
}
const label = (value: Locale) => t(labelKey[value])
+65
View File
@@ -33,6 +33,14 @@ type SessionTabs = {
type SessionView = {
scroll: Record<string, SessionScroll>
reviewOpen?: string[]
pendingMessage?: string
pendingMessageAt?: number
}
type TabHandoff = {
dir: string
id: string
at: number
}
export type LocalProject = Partial<Project> & { worktree: string; expanded: boolean }
@@ -115,10 +123,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
},
sessionTabs: {} as Record<string, SessionTabs>,
sessionView: {} as Record<string, SessionView>,
handoff: {
tabs: undefined as TabHandoff | undefined,
},
}),
)
const MAX_SESSION_KEYS = 50
const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
const meta = { active: undefined as string | undefined, pruned: false }
const used = new Map<string, number>()
@@ -411,6 +423,16 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
return {
ready,
handoff: {
tabs: createMemo(() => store.handoff?.tabs),
setTabs(dir: string, id: string) {
setStore("handoff", "tabs", { dir, id, at: Date.now() })
},
clearTabs() {
if (!store.handoff?.tabs) return
setStore("handoff", "tabs", undefined)
},
},
projects: {
list,
open(directory: string) {
@@ -536,6 +558,49 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("mobileSidebar", "opened", (x) => !x)
},
},
pendingMessage: {
set(sessionKey: string, messageID: string) {
const at = Date.now()
touch(sessionKey)
const current = store.sessionView[sessionKey]
if (!current) {
setStore("sessionView", sessionKey, {
scroll: {},
pendingMessage: messageID,
pendingMessageAt: at,
})
prune(meta.active ?? sessionKey)
return
}
setStore(
"sessionView",
sessionKey,
produce((draft) => {
draft.pendingMessage = messageID
draft.pendingMessageAt = at
}),
)
},
consume(sessionKey: string) {
const current = store.sessionView[sessionKey]
const message = current?.pendingMessage
const at = current?.pendingMessageAt
if (!message || !at) return
setStore(
"sessionView",
sessionKey,
produce((draft) => {
delete draft.pendingMessage
delete draft.pendingMessageAt
}),
)
if (Date.now() - at > PENDING_MESSAGE_TTL_MS) return
return message
},
},
view(sessionKey: string | Accessor<string>) {
const key = typeof sessionKey === "function" ? sessionKey : () => sessionKey
+3 -3
View File
@@ -1,17 +1,17 @@
import { createOpencodeClient, type Event } from "@kilocode/sdk/v2/client"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { createEffect, createMemo, onCleanup } from "solid-js"
import { createEffect, createMemo, onCleanup, type Accessor } from "solid-js"
import { useGlobalSDK } from "./global-sdk"
import { usePlatform } from "./platform"
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
name: "SDK",
init: (props: { directory: string }) => {
init: (props: { directory: Accessor<string> }) => {
const platform = usePlatform()
const globalSDK = useGlobalSDK()
const directory = createMemo(() => props.directory)
const directory = createMemo(props.directory)
const client = createMemo(() =>
createOpencodeClient({
baseUrl: globalSDK.url,
-16
View File
@@ -321,22 +321,6 @@ export const dict = {
"context.usage.clickToView": "انقر لعرض السياق",
"context.usage.view": "عرض استخدام السياق",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "لغة",
"toast.language.description": "تم التبديل إلى {{language}}",
-16
View File
@@ -320,22 +320,6 @@ export const dict = {
"context.usage.clickToView": "Clique para ver o contexto",
"context.usage.view": "Ver uso do contexto",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Idioma",
"toast.language.description": "Alterado para {{language}}",
+747
View File
@@ -0,0 +1,747 @@
export const dict = {
"command.category.suggested": "Predloženo",
"command.category.view": "Prikaz",
"command.category.project": "Projekat",
"command.category.provider": "Provajder",
"command.category.server": "Server",
"command.category.session": "Sesija",
"command.category.theme": "Tema",
"command.category.language": "Jezik",
"command.category.file": "Datoteka",
"command.category.context": "Kontekst",
"command.category.terminal": "Terminal",
"command.category.model": "Model",
"command.category.mcp": "MCP",
"command.category.agent": "Agent",
"command.category.permissions": "Dozvole",
"command.category.workspace": "Radni prostor",
"command.category.settings": "Postavke",
"theme.scheme.system": "Sistem",
"theme.scheme.light": "Svijetlo",
"theme.scheme.dark": "Tamno",
"command.sidebar.toggle": "Prikaži/sakrij bočnu traku",
"command.project.open": "Otvori projekat",
"command.provider.connect": "Poveži provajdera",
"command.server.switch": "Promijeni server",
"command.settings.open": "Otvori postavke",
"command.session.previous": "Prethodna sesija",
"command.session.next": "Sljedeća sesija",
"command.session.previous.unseen": "Prethodna nepročitana sesija",
"command.session.next.unseen": "Sljedeća nepročitana sesija",
"command.session.archive": "Arhiviraj sesiju",
"command.palette": "Paleta komandi",
"command.theme.cycle": "Promijeni temu",
"command.theme.set": "Koristi temu: {{theme}}",
"command.theme.scheme.cycle": "Promijeni šemu boja",
"command.theme.scheme.set": "Koristi šemu boja: {{scheme}}",
"command.language.cycle": "Promijeni jezik",
"command.language.set": "Koristi jezik: {{language}}",
"command.session.new": "Nova sesija",
"command.file.open": "Otvori datoteku",
"command.tab.close": "Zatvori karticu",
"command.context.addSelection": "Dodaj odabir u kontekst",
"command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke",
"command.terminal.toggle": "Prikaži/sakrij terminal",
"command.fileTree.toggle": "Prikaži/sakrij stablo datoteka",
"command.review.toggle": "Prikaži/sakrij pregled",
"command.terminal.new": "Novi terminal",
"command.terminal.new.description": "Kreiraj novu karticu terminala",
"command.steps.toggle": "Prikaži/sakrij korake",
"command.steps.toggle.description": "Prikaži ili sakrij korake za trenutnu poruku",
"command.message.previous": "Prethodna poruka",
"command.message.previous.description": "Idi na prethodnu korisničku poruku",
"command.message.next": "Sljedeća poruka",
"command.message.next.description": "Idi na sljedeću korisničku poruku",
"command.model.choose": "Odaberi model",
"command.model.choose.description": "Odaberi drugi model",
"command.mcp.toggle": "Prikaži/sakrij MCP-ove",
"command.mcp.toggle.description": "Prikaži/sakrij MCP-ove",
"command.agent.cycle": "Promijeni agenta",
"command.agent.cycle.description": "Prebaci na sljedećeg agenta",
"command.agent.cycle.reverse": "Promijeni agenta unazad",
"command.agent.cycle.reverse.description": "Prebaci na prethodnog agenta",
"command.model.variant.cycle": "Promijeni nivo razmišljanja",
"command.model.variant.cycle.description": "Prebaci na sljedeći nivo",
"command.permissions.autoaccept.enable": "Automatski prihvataj izmjene",
"command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje izmjena",
"command.workspace.toggle": "Prikaži/sakrij radne prostore",
"command.workspace.toggle.description": "Omogući ili onemogući više radnih prostora u bočnoj traci",
"command.session.undo": "Poništi",
"command.session.undo.description": "Poništi posljednju poruku",
"command.session.redo": "Vrati",
"command.session.redo.description": "Vrati posljednju poništenu poruku",
"command.session.compact": "Sažmi sesiju",
"command.session.compact.description": "Sažmi sesiju kako bi se smanjio kontekst",
"command.session.fork": "Fork iz poruke",
"command.session.fork.description": "Kreiraj novu sesiju iz prethodne poruke",
"command.session.share": "Podijeli sesiju",
"command.session.share.description": "Podijeli ovu sesiju i kopiraj URL u međuspremnik",
"command.session.unshare": "Ukini dijeljenje sesije",
"command.session.unshare.description": "Zaustavi dijeljenje ove sesije",
"palette.search.placeholder": "Pretraži datoteke, komande i sesije",
"palette.empty": "Nema rezultata",
"palette.group.commands": "Komande",
"palette.group.files": "Datoteke",
"dialog.provider.search.placeholder": "Pretraži provajdere",
"dialog.provider.empty": "Nema pronađenih provajdera",
"dialog.provider.group.popular": "Popularno",
"dialog.provider.group.other": "Ostalo",
"dialog.provider.tag.recommended": "Preporučeno",
"dialog.provider.opencode.note": "Kurirani modeli uključujući Claude, GPT, Gemini i druge",
"dialog.provider.anthropic.note": "Direktan pristup Claude modelima, uključujući Pro i Max",
"dialog.provider.copilot.note": "Claude modeli za pomoć pri kodiranju",
"dialog.provider.openai.note": "GPT modeli za brze, sposobne opšte AI zadatke",
"dialog.provider.google.note": "Gemini modeli za brze, strukturirane odgovore",
"dialog.provider.openrouter.note": "Pristup svim podržanim modelima preko jednog provajdera",
"dialog.provider.vercel.note": "Jedinstven pristup AI modelima uz pametno rutiranje",
"dialog.model.select.title": "Odaberi model",
"dialog.model.search.placeholder": "Pretraži modele",
"dialog.model.empty": "Nema rezultata za modele",
"dialog.model.manage": "Upravljaj modelima",
"dialog.model.manage.description": "Prilagodi koji se modeli prikazuju u izborniku modela.",
"dialog.model.unpaid.freeModels.title": "Besplatni modeli koje obezbjeđuje Kilo",
"dialog.model.unpaid.addMore.title": "Dodaj još modela od popularnih provajdera",
"dialog.provider.viewAll": "Prikaži više provajdera",
"provider.connect.title": "Poveži {{provider}}",
"provider.connect.title.anthropicProMax": "Prijavi se putem Claude Pro/Max",
"provider.connect.selectMethod": "Odaberi način prijave za {{provider}}.",
"provider.connect.method.apiKey": "API ključ",
"provider.connect.status.inProgress": "Autorizacija je u toku...",
"provider.connect.status.waiting": "Čekanje na autorizaciju...",
"provider.connect.status.failed": "Autorizacija nije uspjela: {{error}}",
"provider.connect.apiKey.description":
"Unesi svoj {{provider}} API ključ da povežeš račun i koristiš {{provider}} modele u Kilo-u.",
"provider.connect.apiKey.label": "{{provider}} API ključ",
"provider.connect.apiKey.placeholder": "API ključ",
"provider.connect.apiKey.required": "API ključ je obavezan",
"provider.connect.opencodeZen.line1":
"OpenCode Zen ti daje pristup kuriranom skupu pouzdanih, optimizovanih modela za coding agente.",
"provider.connect.opencodeZen.line2":
"Sa jednim API ključem dobijaš pristup modelima kao što su Claude, GPT, Gemini, GLM i drugi.",
"provider.connect.opencodeZen.visit.prefix": "Posjeti ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " da preuzmeš svoj API ključ.",
"provider.connect.oauth.code.visit.prefix": "Posjeti ",
"provider.connect.oauth.code.visit.link": "ovaj link",
"provider.connect.oauth.code.visit.suffix":
" da preuzmeš autorizacijski kod i povežeš račun te koristiš {{provider}} modele u Kilo-u.",
"provider.connect.oauth.code.label": "{{method}} autorizacijski kod",
"provider.connect.oauth.code.placeholder": "Autorizacijski kod",
"provider.connect.oauth.code.required": "Autorizacijski kod je obavezan",
"provider.connect.oauth.code.invalid": "Nevažeći autorizacijski kod",
"provider.connect.oauth.auto.visit.prefix": "Posjeti ",
"provider.connect.oauth.auto.visit.link": "ovaj link",
"provider.connect.oauth.auto.visit.suffix":
" i unesi kod ispod da povežeš račun i koristiš {{provider}} modele u Kilo-u.",
"provider.connect.oauth.auto.confirmationCode": "Kod za potvrdu",
"provider.connect.toast.connected.title": "{{provider}} povezan",
"provider.connect.toast.connected.description": "{{provider}} modeli su sada dostupni za korištenje.",
"provider.disconnect.toast.disconnected.title": "{{provider}} odspojen",
"provider.disconnect.toast.disconnected.description": "{{provider}} modeli više nisu dostupni.",
"model.tag.free": "Besplatno",
"model.tag.latest": "Najnovije",
"model.provider.anthropic": "Anthropic",
"model.provider.openai": "OpenAI",
"model.provider.google": "Google",
"model.provider.xai": "xAI",
"model.provider.meta": "Meta",
"model.input.text": "tekst",
"model.input.image": "slika",
"model.input.audio": "zvuk",
"model.input.video": "video",
"model.input.pdf": "pdf",
"model.tooltip.allows": "Podržava: {{inputs}}",
"model.tooltip.reasoning.allowed": "Podržava rasuđivanje",
"model.tooltip.reasoning.none": "Bez rasuđivanja",
"model.tooltip.context": "Limit konteksta {{limit}}",
"common.search.placeholder": "Pretraži",
"common.goBack": "Nazad",
"common.goForward": "Naprijed",
"common.loading": "Učitavanje",
"common.loading.ellipsis": "...",
"common.cancel": "Otkaži",
"common.connect": "Poveži",
"common.disconnect": "Prekini vezu",
"common.submit": "Pošalji",
"common.save": "Sačuvaj",
"common.saving": "Čuvanje...",
"common.default": "Podrazumijevano",
"common.attachment": "prilog",
"prompt.placeholder.shell": "Unesi shell naredbu...",
"prompt.placeholder.normal": 'Pitaj bilo šta... "{{example}}"',
"prompt.placeholder.summarizeComments": "Sažmi komentare…",
"prompt.placeholder.summarizeComment": "Sažmi komentar…",
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc za izlaz",
"prompt.example.1": "Popravi TODO u bazi koda",
"prompt.example.2": "Koji je tehnološki stack ovog projekta?",
"prompt.example.3": "Popravi pokvarene testove",
"prompt.example.4": "Objasni kako radi autentifikacija",
"prompt.example.5": "Pronađi i popravi sigurnosne ranjivosti",
"prompt.example.6": "Dodaj jedinične testove za servis korisnika",
"prompt.example.7": "Refaktoriši ovu funkciju da bude čitljivija",
"prompt.example.8": "Šta znači ova greška?",
"prompt.example.9": "Pomozi mi da otklonim ovu grešku",
"prompt.example.10": "Generiši API dokumentaciju",
"prompt.example.11": "Optimizuj upite prema bazi podataka",
"prompt.example.12": "Dodaj validaciju ulaza",
"prompt.example.13": "Napravi novu komponentu za...",
"prompt.example.14": "Kako da deployam ovaj projekat?",
"prompt.example.15": "Pregledaj moj kod prema najboljim praksama",
"prompt.example.16": "Dodaj obradu grešaka u ovu funkciju",
"prompt.example.17": "Objasni ovaj regex obrazac",
"prompt.example.18": "Pretvori ovo u TypeScript",
"prompt.example.19": "Dodaj logovanje kroz cijelu bazu koda",
"prompt.example.20": "Koje su zavisnosti zastarjele?",
"prompt.example.21": "Pomozi mi da napišem migracijsku skriptu",
"prompt.example.22": "Implementiraj keširanje za ovaj endpoint",
"prompt.example.23": "Dodaj paginaciju u ovu listu",
"prompt.example.24": "Napravi CLI komandu za...",
"prompt.example.25": "Kako ovdje rade varijable okruženja?",
"prompt.popover.emptyResults": "Nema rezultata",
"prompt.popover.emptyCommands": "Nema komandi",
"prompt.dropzone.label": "Spusti slike ili PDF-ove ovdje",
"prompt.slash.badge.custom": "prilagođeno",
"prompt.slash.badge.skill": "skill",
"prompt.slash.badge.mcp": "mcp",
"prompt.context.active": "aktivno",
"prompt.context.includeActiveFile": "Uključi aktivnu datoteku",
"prompt.context.removeActiveFile": "Ukloni aktivnu datoteku iz konteksta",
"prompt.context.removeFile": "Ukloni datoteku iz konteksta",
"prompt.action.attachFile": "Priloži datoteku",
"prompt.attachment.remove": "Ukloni prilog",
"prompt.action.send": "Pošalji",
"prompt.action.stop": "Zaustavi",
"prompt.toast.pasteUnsupported.title": "Nepodržano lijepljenje",
"prompt.toast.pasteUnsupported.description": "Ovdje se mogu zalijepiti samo slike ili PDF-ovi.",
"prompt.toast.modelAgentRequired.title": "Odaberi agenta i model",
"prompt.toast.modelAgentRequired.description": "Odaberi agenta i model prije slanja upita.",
"prompt.toast.worktreeCreateFailed.title": "Neuspješno kreiranje worktree-a",
"prompt.toast.sessionCreateFailed.title": "Neuspješno kreiranje sesije",
"prompt.toast.shellSendFailed.title": "Neuspješno slanje shell naredbe",
"prompt.toast.commandSendFailed.title": "Neuspješno slanje komande",
"prompt.toast.promptSendFailed.title": "Neuspješno slanje upita",
"dialog.mcp.title": "MCP-ovi",
"dialog.mcp.description": "{{enabled}} od {{total}} omogućeno",
"dialog.mcp.empty": "Nema konfigurisnih MCP-ova",
"dialog.lsp.empty": "LSP-ovi se automatski otkrivaju prema tipu datoteke",
"dialog.plugins.empty": "Plugini su konfigurisani u opencode.json",
"mcp.status.connected": "povezano",
"mcp.status.failed": "neuspjelo",
"mcp.status.needs_auth": "potrebna autentifikacija",
"mcp.status.disabled": "onemogućeno",
"dialog.fork.empty": "Nema poruka za fork",
"dialog.directory.search.placeholder": "Pretraži foldere",
"dialog.directory.empty": "Nema pronađenih foldera",
"dialog.server.title": "Serveri",
"dialog.server.description": "Promijeni na koji se Kilo server ova aplikacija povezuje.",
"dialog.server.search.placeholder": "Pretraži servere",
"dialog.server.empty": "Još nema servera",
"dialog.server.add.title": "Dodaj server",
"dialog.server.add.url": "URL servera",
"dialog.server.add.placeholder": "http://localhost:4096",
"dialog.server.add.error": "Nije moguće povezati se na server",
"dialog.server.add.checking": "Provjera...",
"dialog.server.add.button": "Dodaj server",
"dialog.server.default.title": "Podrazumijevani server",
"dialog.server.default.description":
"Poveži se na ovaj server pri pokretanju aplikacije umjesto pokretanja lokalnog servera. Potreban je restart.",
"dialog.server.default.none": "Nije odabran server",
"dialog.server.default.set": "Postavi trenutni server kao podrazumijevani",
"dialog.server.default.clear": "Očisti",
"dialog.server.action.remove": "Ukloni server",
"dialog.server.menu.edit": "Uredi",
"dialog.server.menu.default": "Postavi kao podrazumijevano",
"dialog.server.menu.defaultRemove": "Ukloni podrazumijevano",
"dialog.server.menu.delete": "Izbriši",
"dialog.server.current": "Trenutni server",
"dialog.server.status.default": "Podrazumijevano",
"dialog.project.edit.title": "Uredi projekat",
"dialog.project.edit.name": "Naziv",
"dialog.project.edit.icon": "Ikonica",
"dialog.project.edit.icon.alt": "Ikonica projekta",
"dialog.project.edit.icon.hint": "Klikni ili prevuci sliku",
"dialog.project.edit.icon.recommended": "Preporučeno: 128x128px",
"dialog.project.edit.color": "Boja",
"dialog.project.edit.color.select": "Odaberi boju {{color}}",
"dialog.project.edit.worktree.startup": "Skripta za pokretanje radnog prostora",
"dialog.project.edit.worktree.startup.description": "Pokreće se nakon kreiranja novog radnog prostora (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "npr. bun install",
"context.breakdown.title": "Razlaganje konteksta",
"context.breakdown.note":
'Približna raspodjela ulaznih tokena. "Ostalo" uključuje definicije alata i dodatni overhead.',
"context.breakdown.system": "Sistem",
"context.breakdown.user": "Korisnik",
"context.breakdown.assistant": "Asistent",
"context.breakdown.tool": "Pozivi alata",
"context.breakdown.other": "Ostalo",
"context.systemPrompt.title": "Sistemski prompt",
"context.rawMessages.title": "Sirove poruke",
"context.stats.session": "Sesija",
"context.stats.messages": "Poruke",
"context.stats.provider": "Provajder",
"context.stats.model": "Model",
"context.stats.limit": "Limit konteksta",
"context.stats.totalTokens": "Ukupno tokena",
"context.stats.usage": "Korištenje",
"context.stats.inputTokens": "Ulazni tokeni",
"context.stats.outputTokens": "Izlazni tokeni",
"context.stats.reasoningTokens": "Tokeni za rasuđivanje",
"context.stats.cacheTokens": "Cache tokeni (čitanje/pisanje)",
"context.stats.userMessages": "Korisničke poruke",
"context.stats.assistantMessages": "Poruke asistenta",
"context.stats.totalCost": "Ukupni trošak",
"context.stats.sessionCreated": "Sesija kreirana",
"context.stats.lastActivity": "Posljednja aktivnost",
"context.usage.tokens": "Tokeni",
"context.usage.usage": "Korištenje",
"context.usage.cost": "Trošak",
"context.usage.clickToView": "Klikni da vidiš kontekst",
"context.usage.view": "Prikaži korištenje konteksta",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.bs": "Bosanski",
"language.th": "ไทย",
"toast.language.title": "Jezik",
"toast.language.description": "Prebačeno na {{language}}",
"toast.theme.title": "Tema promijenjena",
"toast.scheme.title": "Šema boja",
"toast.workspace.enabled.title": "Radni prostori omogućeni",
"toast.workspace.enabled.description": "Više worktree-ova se sada prikazuje u bočnoj traci",
"toast.workspace.disabled.title": "Radni prostori onemogućeni",
"toast.workspace.disabled.description": "Samo glavni worktree se prikazuje u bočnoj traci",
"toast.permissions.autoaccept.on.title": "Automatsko prihvatanje izmjena",
"toast.permissions.autoaccept.on.description": "Dozvole za izmjene i pisanje biće automatski odobrene",
"toast.permissions.autoaccept.off.title": "Zaustavljeno automatsko prihvatanje izmjena",
"toast.permissions.autoaccept.off.description": "Dozvole za izmjene i pisanje zahtijevaće odobrenje",
"toast.model.none.title": "Nije odabran model",
"toast.model.none.description": "Poveži provajdera da sažmeš ovu sesiju",
"toast.file.loadFailed.title": "Neuspjelo učitavanje datoteke",
"toast.file.listFailed.title": "Neuspješno listanje datoteka",
"toast.context.noLineSelection.title": "Nema odabranih linija",
"toast.context.noLineSelection.description": "Prvo odaberi raspon linija u kartici datoteke.",
"toast.session.share.copyFailed.title": "Neuspjelo kopiranje URL-a u međuspremnik",
"toast.session.share.success.title": "Sesija podijeljena",
"toast.session.share.success.description": "URL za dijeljenje je kopiran u međuspremnik!",
"toast.session.share.failed.title": "Neuspjelo dijeljenje sesije",
"toast.session.share.failed.description": "Došlo je do greške prilikom dijeljenja sesije",
"toast.session.unshare.success.title": "Dijeljenje sesije ukinuto",
"toast.session.unshare.success.description": "Dijeljenje sesije je uspješno ukinuto!",
"toast.session.unshare.failed.title": "Neuspjelo ukidanje dijeljenja",
"toast.session.unshare.failed.description": "Došlo je do greške prilikom ukidanja dijeljenja",
"toast.session.listFailed.title": "Neuspjelo učitavanje sesija za {{project}}",
"toast.update.title": "Dostupno ažuriranje",
"toast.update.description": "Nova verzija Kilo-a ({{version}}) je dostupna za instalaciju.",
"toast.update.action.installRestart": "Instaliraj i restartuj",
"toast.update.action.notYet": "Ne još",
"error.page.title": "Nešto je pošlo po zlu",
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
"error.page.details.label": "Detalji greške",
"error.page.action.restart": "Restartuj",
"error.page.action.checking": "Provjera...",
"error.page.action.checkUpdates": "Provjeri ažuriranja",
"error.page.action.updateTo": "Ažuriraj na {{version}}",
"error.page.report.prefix": "Molimo prijavi ovu grešku Kilo timu",
"error.page.report.discord": "na Discordu",
"error.page.version": "Verzija: {{version}}",
"error.dev.rootNotFound":
"Korijenski element nije pronađen. Da li si zaboravio da ga dodaš u index.html? Ili je možda id atribut pogrešno napisan?",
"error.globalSync.connectFailed": "Nije moguće povezati se na server. Da li server radi na `{{url}}`?",
"error.chain.unknown": "Nepoznata greška",
"error.chain.causedBy": "Uzrok:",
"error.chain.apiError": "API greška",
"error.chain.status": "Status: {{status}}",
"error.chain.retryable": "Može se ponoviti: {{retryable}}",
"error.chain.responseBody": "Tijelo odgovora:\n{{body}}",
"error.chain.didYouMean": "Da li si mislio: {{suggestions}}",
"error.chain.modelNotFound": "Model nije pronađen: {{provider}}/{{model}}",
"error.chain.checkConfig": "Provjeri konfiguraciju (opencode.json) - nazive provajdera/modela",
"error.chain.mcpFailed": 'MCP server "{{name}}" nije uspio. Napomena: Kilo još ne podržava MCP autentifikaciju.',
"error.chain.providerAuthFailed": "Autentifikacija provajdera nije uspjela ({{provider}}): {{message}}",
"error.chain.providerInitFailed":
'Neuspjelo inicijalizovanje provajdera "{{provider}}". Provjeri kredencijale i konfiguraciju.',
"error.chain.configJsonInvalid": "Konfiguracijska datoteka na {{path}} nije važeći JSON(C)",
"error.chain.configJsonInvalidWithMessage": "Konfiguracijska datoteka na {{path}} nije važeći JSON(C): {{message}}",
"error.chain.configDirectoryTypo":
'Direktorij "{{dir}}" u {{path}} nije ispravan. Preimenuj direktorij u "{{suggestion}}" ili ga ukloni. Ovo je česta greška u kucanju.',
"error.chain.configFrontmatterError": "Neuspjelo parsiranje frontmatter-a u {{path}}:\n{{message}}",
"error.chain.configInvalid": "Konfiguracijska datoteka na {{path}} nije ispravna",
"error.chain.configInvalidWithMessage": "Konfiguracijska datoteka na {{path}} nije ispravna: {{message}}",
"notification.permission.title": "Potrebna dozvola",
"notification.permission.description": "{{sessionTitle}} u {{projectName}} traži dozvolu",
"notification.question.title": "Pitanje",
"notification.question.description": "{{sessionTitle}} u {{projectName}} ima pitanje",
"notification.action.goToSession": "Idi na sesiju",
"notification.session.responseReady.title": "Odgovor je spreman",
"notification.session.error.title": "Greška sesije",
"notification.session.error.fallbackDescription": "Došlo je do greške",
"home.recentProjects": "Nedavni projekti",
"home.empty.title": "Nema nedavnih projekata",
"home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat",
"session.tab.session": "Sesija",
"session.tab.review": "Pregled",
"session.tab.context": "Kontekst",
"session.panel.reviewAndFiles": "Pregled i datoteke",
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
"session.review.change.one": "Izmjena",
"session.review.change.other": "Izmjene",
"session.review.loadingChanges": "Učitavanje izmjena...",
"session.review.empty": "Još nema izmjena u ovoj sesiji",
"session.review.noChanges": "Nema izmjena",
"session.files.selectToOpen": "Odaberi datoteku za otvaranje",
"session.files.all": "Sve datoteke",
"session.files.binaryContent": "Binarna datoteka (sadržaj se ne može prikazati)",
"session.messages.renderEarlier": "Prikaži ranije poruke",
"session.messages.loadingEarlier": "Učitavanje ranijih poruka...",
"session.messages.loadEarlier": "Učitaj ranije poruke",
"session.messages.loading": "Učitavanje poruka...",
"session.messages.jumpToLatest": "Idi na najnovije",
"session.context.addToContext": "Dodaj {{selection}} u kontekst",
"session.new.worktree.main": "Glavna grana",
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
"session.new.worktree.create": "Kreiraj novi worktree",
"session.new.lastModified": "Posljednja izmjena",
"session.header.search.placeholder": "Pretraži {{project}}",
"session.header.searchFiles": "Pretraži datoteke",
"status.popover.trigger": "Status",
"status.popover.ariaLabel": "Konfiguracije servera",
"status.popover.tab.servers": "Serveri",
"status.popover.tab.mcp": "MCP",
"status.popover.tab.lsp": "LSP",
"status.popover.tab.plugins": "Plugini",
"status.popover.action.manageServers": "Upravljaj serverima",
"session.share.popover.title": "Objavi na webu",
"session.share.popover.description.shared": "Ova sesija je javna na webu. Dostupna je svima koji imaju link.",
"session.share.popover.description.unshared": "Podijeli sesiju javno na webu. Biće dostupna svima koji imaju link.",
"session.share.action.share": "Podijeli",
"session.share.action.publish": "Objavi",
"session.share.action.publishing": "Objavljivanje...",
"session.share.action.unpublish": "Poništi objavu",
"session.share.action.unpublishing": "Poništavanje objave...",
"session.share.action.view": "Prikaži",
"session.share.copy.copied": "Kopirano",
"session.share.copy.copyLink": "Kopiraj link",
"lsp.tooltip.none": "Nema LSP servera",
"lsp.label.connected": "{{count}} LSP",
"prompt.loading": "Učitavanje upita...",
"terminal.loading": "Učitavanje terminala...",
"terminal.title": "Terminal",
"terminal.title.numbered": "Terminal {{number}}",
"terminal.close": "Zatvori terminal",
"terminal.connectionLost.title": "Veza prekinuta",
"terminal.connectionLost.description":
"Veza s terminalom je prekinuta. Ovo se može desiti kada se server restartuje.",
"common.closeTab": "Zatvori karticu",
"common.dismiss": "Odbaci",
"common.requestFailed": "Zahtjev nije uspio",
"common.moreOptions": "Više opcija",
"common.learnMore": "Saznaj više",
"common.rename": "Preimenuj",
"common.reset": "Resetuj",
"common.archive": "Arhiviraj",
"common.delete": "Izbriši",
"common.close": "Zatvori",
"common.edit": "Uredi",
"common.loadMore": "Učitaj još",
"common.key.esc": "ESC",
"sidebar.menu.toggle": "Prikaži/sakrij meni",
"sidebar.nav.projectsAndSessions": "Projekti i sesije",
"sidebar.settings": "Postavke",
"sidebar.help": "Pomoć",
"sidebar.workspaces.enable": "Omogući radne prostore",
"sidebar.workspaces.disable": "Onemogući radne prostore",
"sidebar.gettingStarted.title": "Početak",
"sidebar.gettingStarted.line1": "Kilo uključuje besplatne modele, tako da možeš odmah početi.",
"sidebar.gettingStarted.line2": "Poveži bilo kojeg provajdera da koristiš modele, npr. Claude, GPT, Gemini itd.",
"sidebar.project.recentSessions": "Nedavne sesije",
"sidebar.project.viewAllSessions": "Prikaži sve sesije",
"app.name.desktop": "Kilo Desktop",
"settings.section.desktop": "Desktop",
"settings.section.server": "Server",
"settings.tab.general": "Opšte",
"settings.tab.shortcuts": "Prečice",
"settings.general.section.appearance": "Izgled",
"settings.general.section.notifications": "Sistemske obavijesti",
"settings.general.section.updates": "Ažuriranja",
"settings.general.section.sounds": "Zvučni efekti",
"settings.general.row.language.title": "Jezik",
"settings.general.row.language.description": "Promijeni jezik prikaza u Kilo-u",
"settings.general.row.appearance.title": "Izgled",
"settings.general.row.appearance.description": "Prilagodi kako Kilo izgleda na tvom uređaju",
"settings.general.row.theme.title": "Tema",
"settings.general.row.theme.description": "Prilagodi temu Kilo-a.",
"settings.general.row.font.title": "Font",
"settings.general.row.font.description": "Prilagodi monospace font koji se koristi u blokovima koda",
"settings.general.row.releaseNotes.title": "Bilješke o izdanju",
"settings.general.row.releaseNotes.description": 'Prikaži iskačuće prozore "Šta je novo" nakon ažuriranja',
"settings.updates.row.startup.title": "Provjeri ažuriranja pri pokretanju",
"settings.updates.row.startup.description": "Automatski provjerava ažuriranja kada se Kilo pokrene",
"settings.updates.row.check.title": "Provjeri ažuriranja",
"settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna",
"settings.updates.action.checkNow": "Provjeri sada",
"settings.updates.action.checking": "Provjera...",
"settings.updates.toast.latest.title": "Sve je ažurno",
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju Kilo-a.",
"font.option.ibmPlexMono": "IBM Plex Mono",
"font.option.cascadiaCode": "Cascadia Code",
"font.option.firaCode": "Fira Code",
"font.option.hack": "Hack",
"font.option.inconsolata": "Inconsolata",
"font.option.intelOneMono": "Intel One Mono",
"font.option.iosevka": "Iosevka",
"font.option.jetbrainsMono": "JetBrains Mono",
"font.option.mesloLgs": "Meslo LGS",
"font.option.robotoMono": "Roboto Mono",
"font.option.sourceCodePro": "Source Code Pro",
"font.option.ubuntuMono": "Ubuntu Mono",
"sound.option.alert01": "Upozorenje 01",
"sound.option.alert02": "Upozorenje 02",
"sound.option.alert03": "Upozorenje 03",
"sound.option.alert04": "Upozorenje 04",
"sound.option.alert05": "Upozorenje 05",
"sound.option.alert06": "Upozorenje 06",
"sound.option.alert07": "Upozorenje 07",
"sound.option.alert08": "Upozorenje 08",
"sound.option.alert09": "Upozorenje 09",
"sound.option.alert10": "Upozorenje 10",
"sound.option.bipbop01": "Bip-bop 01",
"sound.option.bipbop02": "Bip-bop 02",
"sound.option.bipbop03": "Bip-bop 03",
"sound.option.bipbop04": "Bip-bop 04",
"sound.option.bipbop05": "Bip-bop 05",
"sound.option.bipbop06": "Bip-bop 06",
"sound.option.bipbop07": "Bip-bop 07",
"sound.option.bipbop08": "Bip-bop 08",
"sound.option.bipbop09": "Bip-bop 09",
"sound.option.bipbop10": "Bip-bop 10",
"sound.option.staplebops01": "Staplebops 01",
"sound.option.staplebops02": "Staplebops 02",
"sound.option.staplebops03": "Staplebops 03",
"sound.option.staplebops04": "Staplebops 04",
"sound.option.staplebops05": "Staplebops 05",
"sound.option.staplebops06": "Staplebops 06",
"sound.option.staplebops07": "Staplebops 07",
"sound.option.nope01": "Ne 01",
"sound.option.nope02": "Ne 02",
"sound.option.nope03": "Ne 03",
"sound.option.nope04": "Ne 04",
"sound.option.nope05": "Ne 05",
"sound.option.nope06": "Ne 06",
"sound.option.nope07": "Ne 07",
"sound.option.nope08": "Ne 08",
"sound.option.nope09": "Ne 09",
"sound.option.nope10": "Ne 10",
"sound.option.nope11": "Ne 11",
"sound.option.nope12": "Ne 12",
"sound.option.yup01": "Da 01",
"sound.option.yup02": "Da 02",
"sound.option.yup03": "Da 03",
"sound.option.yup04": "Da 04",
"sound.option.yup05": "Da 05",
"sound.option.yup06": "Da 06",
"settings.general.notifications.agent.title": "Agent",
"settings.general.notifications.agent.description":
"Prikaži sistemsku obavijest kada agent završi ili zahtijeva pažnju",
"settings.general.notifications.permissions.title": "Dozvole",
"settings.general.notifications.permissions.description": "Prikaži sistemsku obavijest kada je potrebna dozvola",
"settings.general.notifications.errors.title": "Greške",
"settings.general.notifications.errors.description": "Prikaži sistemsku obavijest kada dođe do greške",
"settings.general.sounds.agent.title": "Agent",
"settings.general.sounds.agent.description": "Pusti zvuk kada agent završi ili zahtijeva pažnju",
"settings.general.sounds.permissions.title": "Dozvole",
"settings.general.sounds.permissions.description": "Pusti zvuk kada je potrebna dozvola",
"settings.general.sounds.errors.title": "Greške",
"settings.general.sounds.errors.description": "Pusti zvuk kada dođe do greške",
"settings.shortcuts.title": "Prečice na tastaturi",
"settings.shortcuts.reset.button": "Vrati na podrazumijevano",
"settings.shortcuts.reset.toast.title": "Prečice resetovane",
"settings.shortcuts.reset.toast.description": "Prečice na tastaturi su vraćene na podrazumijevane.",
"settings.shortcuts.conflict.title": "Prečica je već u upotrebi",
"settings.shortcuts.conflict.description": "{{keybind}} je već dodijeljeno za {{titles}}.",
"settings.shortcuts.unassigned": "Nedodijeljeno",
"settings.shortcuts.pressKeys": "Pritisni tastere",
"settings.shortcuts.search.placeholder": "Pretraži prečice",
"settings.shortcuts.search.empty": "Nema pronađenih prečica",
"settings.shortcuts.group.general": "Opšte",
"settings.shortcuts.group.session": "Sesija",
"settings.shortcuts.group.navigation": "Navigacija",
"settings.shortcuts.group.modelAndAgent": "Model i agent",
"settings.shortcuts.group.terminal": "Terminal",
"settings.shortcuts.group.prompt": "Upit",
"settings.providers.title": "Provajderi",
"settings.providers.description": "Postavke provajdera će se ovdje moći podešavati.",
"settings.providers.section.connected": "Povezani provajderi",
"settings.providers.connected.empty": "Nema povezanih provajdera",
"settings.providers.section.popular": "Popularni provajderi",
"settings.providers.tag.environment": "Okruženje",
"settings.providers.tag.config": "Konfiguracija",
"settings.providers.tag.custom": "Prilagođeno",
"settings.providers.tag.other": "Ostalo",
"settings.models.title": "Modeli",
"settings.models.description": "Postavke modela će se ovdje moći podešavati.",
"settings.agents.title": "Agenti",
"settings.agents.description": "Postavke agenata će se ovdje moći podešavati.",
"settings.commands.title": "Komande",
"settings.commands.description": "Postavke komandi će se ovdje moći podešavati.",
"settings.mcp.title": "MCP",
"settings.mcp.description": "MCP postavke će se ovdje moći podešavati.",
"settings.permissions.title": "Dozvole",
"settings.permissions.description": "Kontroliši koje alate server smije koristiti po defaultu.",
"settings.permissions.section.tools": "Alati",
"settings.permissions.toast.updateFailed.title": "Neuspjelo ažuriranje dozvola",
"settings.permissions.action.allow": "Dozvoli",
"settings.permissions.action.ask": "Pitaj",
"settings.permissions.action.deny": "Zabrani",
"settings.permissions.tool.read.title": "Čitanje",
"settings.permissions.tool.read.description": "Čitanje datoteke (podudara se s putanjom datoteke)",
"settings.permissions.tool.edit.title": "Uređivanje",
"settings.permissions.tool.edit.description":
"Mijenjanje datoteka, uključujući izmjene, pisanja, patch-eve i multi-izmjene",
"settings.permissions.tool.glob.title": "Glob",
"settings.permissions.tool.glob.description": "Podudaranje datoteka pomoću glob šablona",
"settings.permissions.tool.grep.title": "Grep",
"settings.permissions.tool.grep.description": "Pretraživanje sadržaja datoteka pomoću regularnih izraza",
"settings.permissions.tool.list.title": "Lista",
"settings.permissions.tool.list.description": "Listanje datoteka unutar direktorija",
"settings.permissions.tool.bash.title": "Bash",
"settings.permissions.tool.bash.description": "Pokretanje shell komandi",
"settings.permissions.tool.task.title": "Zadatak",
"settings.permissions.tool.task.description": "Pokretanje pod-agenta",
"settings.permissions.tool.skill.title": "Vještina",
"settings.permissions.tool.skill.description": "Učitaj vještinu po nazivu",
"settings.permissions.tool.lsp.title": "LSP",
"settings.permissions.tool.lsp.description": "Pokreni upite jezičnog servera",
"settings.permissions.tool.todoread.title": "Čitanje liste zadataka",
"settings.permissions.tool.todoread.description": "Čitanje liste zadataka",
"settings.permissions.tool.todowrite.title": "Ažuriranje liste zadataka",
"settings.permissions.tool.todowrite.description": "Ažuriraj listu zadataka",
"settings.permissions.tool.webfetch.title": "Web preuzimanje",
"settings.permissions.tool.webfetch.description": "Preuzmi sadržaj sa URL-a",
"settings.permissions.tool.websearch.title": "Web pretraga",
"settings.permissions.tool.websearch.description": "Pretražuj web",
"settings.permissions.tool.codesearch.title": "Pretraga koda",
"settings.permissions.tool.codesearch.description": "Pretraži kod na webu",
"settings.permissions.tool.external_directory.title": "Vanjski direktorij",
"settings.permissions.tool.external_directory.description": "Pristup datotekama izvan direktorija projekta",
"settings.permissions.tool.doom_loop.title": "Beskonačna petlja",
"settings.permissions.tool.doom_loop.description": "Otkriva ponovljene pozive alata sa identičnim unosom",
"session.delete.failed.title": "Neuspjelo brisanje sesije",
"session.delete.title": "Izbriši sesiju",
"session.delete.confirm": 'Izbriši sesiju "{{name}}"?',
"session.delete.button": "Izbriši sesiju",
"workspace.new": "Novi radni prostor",
"workspace.type.local": "lokalno",
"workspace.type.sandbox": "sandbox",
"workspace.create.failed.title": "Neuspješno kreiranje radnog prostora",
"workspace.delete.failed.title": "Neuspješno brisanje radnog prostora",
"workspace.resetting.title": "Resetovanje radnog prostora",
"workspace.resetting.description": "Ovo može potrajati minut.",
"workspace.reset.failed.title": "Neuspješno resetovanje radnog prostora",
"workspace.reset.success.title": "Radni prostor resetovan",
"workspace.reset.success.description": "Radni prostor sada odgovara podrazumijevanoj grani.",
"workspace.error.stillPreparing": "Radni prostor se još priprema",
"workspace.status.checking": "Provjera neobjedinjenih promjena...",
"workspace.status.error": "Nije moguće provjeriti git status.",
"workspace.status.clean": "Nisu pronađene neobjedinjene promjene.",
"workspace.status.dirty": "Pronađene su neobjedinjene promjene u ovom radnom prostoru.",
"workspace.delete.title": "Izbriši radni prostor",
"workspace.delete.confirm": 'Izbriši radni prostor "{{name}}"?',
"workspace.delete.button": "Izbriši radni prostor",
"workspace.reset.title": "Resetuj radni prostor",
"workspace.reset.confirm": 'Resetuj radni prostor "{{name}}"?',
"workspace.reset.button": "Resetuj radni prostor",
"workspace.reset.archived.none": "Nijedna aktivna sesija neće biti arhivirana.",
"workspace.reset.archived.one": "1 sesija će biti arhivirana.",
"workspace.reset.archived.many": "Biće arhivirano {{count}} sesija.",
"workspace.reset.note": "Ovo će resetovati radni prostor da odgovara podrazumijevanoj grani.",
}
-16
View File
@@ -322,22 +322,6 @@ export const dict = {
"context.usage.clickToView": "Klik for at se kontekst",
"context.usage.view": "Se kontekstforbrug",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Sprog",
"toast.language.description": "Skiftede til {{language}}",
-16
View File
@@ -328,22 +328,6 @@ export const dict = {
"context.usage.clickToView": "Klicken, um Kontext anzuzeigen",
"context.usage.view": "Kontextnutzung anzeigen",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Sprache",
"toast.language.description": "Zu {{language}} gewechselt",
+1
View File
@@ -343,6 +343,7 @@ export const dict = {
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.bs": "Bosanski",
"language.th": "ไทย",
"toast.language.title": "Language",
-16
View File
@@ -323,22 +323,6 @@ export const dict = {
"context.usage.clickToView": "Haz clic para ver contexto",
"context.usage.view": "Ver uso del contexto",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Idioma",
"toast.language.description": "Cambiado a {{language}}",
-16
View File
@@ -323,22 +323,6 @@ export const dict = {
"context.usage.clickToView": "Cliquez pour voir le contexte",
"context.usage.view": "Voir l'utilisation du contexte",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Langue",
"toast.language.description": "Passé à {{language}}",
-16
View File
@@ -321,22 +321,6 @@ export const dict = {
"context.usage.clickToView": "クリックしてコンテキストを表示",
"context.usage.view": "コンテキスト使用量を表示",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "言語",
"toast.language.description": "{{language}}に切り替えました",
-16
View File
@@ -324,22 +324,6 @@ export const dict = {
"context.usage.clickToView": "컨텍스트를 보려면 클릭",
"context.usage.view": "컨텍스트 사용량 보기",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "언어",
"toast.language.description": "{{language}}(으)로 전환됨",
-16
View File
@@ -324,22 +324,6 @@ export const dict = {
"context.usage.clickToView": "Klikk for å se kontekst",
"context.usage.view": "Se kontekstforbruk",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Språk",
"toast.language.description": "Byttet til {{language}}",
-16
View File
@@ -322,22 +322,6 @@ export const dict = {
"context.usage.clickToView": "Kliknij, aby zobaczyć kontekst",
"context.usage.view": "Pokaż użycie kontekstu",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Język",
"toast.language.description": "Przełączono na {{language}}",
-16
View File
@@ -323,22 +323,6 @@ export const dict = {
"context.usage.clickToView": "Нажмите для просмотра контекста",
"context.usage.view": "Показать использование контекста",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "Язык",
"toast.language.description": "Переключено на {{language}}",
-16
View File
@@ -326,22 +326,6 @@ export const dict = {
"context.usage.clickToView": "คลิกเพื่อดูบริบท",
"context.usage.view": "ดูการใช้บริบท",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "ภาษา",
"toast.language.description": "สลับไปที่ {{language}}",
-16
View File
@@ -324,22 +324,6 @@ export const dict = {
"context.usage.clickToView": "点击查看上下文",
"context.usage.view": "查看上下文用量",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "语言",
"toast.language.description": "已切换到{{language}}",
-16
View File
@@ -321,22 +321,6 @@ export const dict = {
"context.usage.clickToView": "點擊查看上下文",
"context.usage.view": "檢視上下文用量",
"language.en": "English",
"language.zh": "简体中文",
"language.zht": "繁體中文",
"language.ko": "한국어",
"language.de": "Deutsch",
"language.es": "Español",
"language.fr": "Français",
"language.da": "Dansk",
"language.ja": "日本語",
"language.pl": "Polski",
"language.ru": "Русский",
"language.ar": "العربية",
"language.no": "Norsk",
"language.br": "Português (Brasil)",
"language.th": "ไทย",
"toast.language.title": "語言",
"toast.language.description": "已切換到 {{language}}",
+1 -1
View File
@@ -31,7 +31,7 @@ export default function Layout(props: ParentProps) {
})
return (
<Show when={directory()}>
<SDKProvider directory={directory()}>
<SDKProvider directory={directory}>
<SyncProvider>
{iife(() => {
const sync = useSync()
+69 -173
View File
@@ -58,6 +58,7 @@ import { usePermission } from "@/context/permission"
import { Binary } from "@opencode-ai/util/binary"
import { retry } from "@opencode-ai/util/retry"
import { playSound, soundSrc } from "@/utils/sound"
import { createAim } from "@/utils/aim"
import { Worktree as WorktreeState } from "@/utils/worktree"
import { agentColor } from "@/utils/agent"
@@ -146,9 +147,20 @@ export default function Layout(props: ParentProps) {
const navLeave = { current: undefined as number | undefined }
const aim = createAim({
enabled: () => !layout.sidebar.opened(),
active: () => state.hoverProject,
el: () => state.nav,
onActivate: (directory) => {
globalSync.child(directory)
setState("hoverProject", directory)
setState("hoverSession", undefined)
},
})
onCleanup(() => {
if (navLeave.current === undefined) return
clearTimeout(navLeave.current)
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
aim.reset()
})
const sidebarHovering = createMemo(() => !layout.sidebar.opened() && state.hoverProject !== undefined)
@@ -162,15 +174,22 @@ export default function Layout(props: ParentProps) {
createEffect(() => {
if (!layout.sidebar.opened()) return
aim.reset()
setState("hoverProject", undefined)
})
createEffect(() => {
if (state.hoverProject !== undefined) return
aim.reset()
})
createEffect(
on(
() => ({ dir: params.dir, id: params.id }),
() => {
if (layout.sidebar.opened()) return
if (!state.hoverProject) return
aim.reset()
setState("hoverSession", undefined)
setState("hoverProject", undefined)
},
@@ -1000,69 +1019,6 @@ export default function Layout(props: ParentProps) {
}
}
async function deleteSession(session: Session) {
const [store, setStore] = globalSync.child(session.directory)
const sessions = (store.session ?? []).filter((s) => !s.parentID && !s.time?.archived)
const index = sessions.findIndex((s) => s.id === session.id)
const nextSession = sessions[index + 1] ?? sessions[index - 1]
const result = await globalSDK.client.session
.delete({ directory: session.directory, sessionID: session.id })
.then((x) => x.data)
.catch((err) => {
showToast({
title: language.t("session.delete.failed.title"),
description: errorMessage(err),
})
return false
})
if (!result) return
setStore(
produce((draft) => {
const removed = new Set<string>([session.id])
const byParent = new Map<string, string[]>()
for (const item of draft.session) {
const parentID = item.parentID
if (!parentID) continue
const existing = byParent.get(parentID)
if (existing) {
existing.push(item.id)
continue
}
byParent.set(parentID, [item.id])
}
const stack = [session.id]
while (stack.length) {
const parentID = stack.pop()
if (!parentID) continue
const children = byParent.get(parentID)
if (!children) continue
for (const child of children) {
if (removed.has(child)) continue
removed.add(child)
stack.push(child)
}
}
draft.session = draft.session.filter((s) => !removed.has(s.id))
}),
)
if (session.id === params.id) {
if (nextSession) {
navigate(`/${params.dir}/session/${nextSession.id}`)
} else {
navigate(`/${params.dir}/session`)
}
}
}
command.register(() => {
const commands: CommandOption[] = [
{
@@ -1316,15 +1272,6 @@ export default function Layout(props: ParentProps) {
globalSync.project.meta(project.worktree, { name })
}
async function renameSession(session: Session, next: string) {
if (next === session.title) return
await globalSDK.client.session.update({
directory: session.directory,
sessionID: session.id,
title: next,
})
}
const renameWorkspace = (directory: string, next: string, projectId?: string, branch?: string) => {
const current = workspaceName(directory, projectId, branch) ?? branch ?? getFilename(directory)
if (current === next) return
@@ -1475,33 +1422,6 @@ export default function Layout(props: ParentProps) {
})
}
function DialogDeleteSession(props: { session: Session }) {
const handleDelete = async () => {
await deleteSession(props.session)
dialog.close()
}
return (
<Dialog title={language.t("session.delete.title")} fit>
<div class="flex flex-col gap-4 pl-6 pr-2.5 pb-3">
<div class="flex flex-col gap-1">
<span class="text-14-regular text-text-strong">
{language.t("session.delete.confirm", { name: props.session.title })}
</span>
</div>
<div class="flex justify-end gap-2">
<Button variant="ghost" size="large" onClick={() => dialog.close()}>
{language.t("common.cancel")}
</Button>
<Button variant="primary" size="large" onClick={handleDelete}>
{language.t("session.delete.button")}
</Button>
</div>
</div>
</Dialog>
)
}
function DialogDeleteWorkspace(props: { root: string; directory: string }) {
const name = createMemo(() => getFilename(props.directory))
const [data, setData] = createStore({
@@ -1855,10 +1775,6 @@ export default function Layout(props: ParentProps) {
const hoverAllowed = createMemo(() => !props.mobile && sidebarExpanded())
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
const isActive = createMemo(() => props.session.id === params.id)
const [menu, setMenu] = createStore({
open: false,
pendingRename: false,
})
const hoverPrefetch = { current: undefined as ReturnType<typeof setTimeout> | undefined }
const cancelHoverPrefetch = () => {
@@ -1885,7 +1801,7 @@ export default function Layout(props: ParentProps) {
const item = (
<A
href={`${props.slug}/session/${props.session.id}`}
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${menu.open ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${props.mobile ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
onPointerEnter={scheduleHoverPrefetch}
onPointerLeave={cancelHoverPrefetch}
onMouseEnter={scheduleHoverPrefetch}
@@ -1917,14 +1833,9 @@ export default function Layout(props: ParentProps) {
</Match>
</Switch>
</div>
<InlineEditor
id={`session:${props.session.id}`}
value={() => props.session.title}
onSave={(next) => renameSession(props.session, next)}
class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"
displayClass="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate"
stopPropagation
/>
<span class="text-14-regular text-text-strong grow-1 min-w-0 overflow-hidden text-ellipsis truncate">
{props.session.title}
</span>
<Show when={props.session.summary}>
{(summary) => (
<div class="group-hover/session:hidden group-active/session:hidden group-focus-within/session:hidden">
@@ -1972,7 +1883,10 @@ export default function Layout(props: ParentProps) {
getLabel={messageLabel}
onMessageSelect={(message) => {
if (!isActive()) {
sessionStorage.setItem("opencode.pendingMessage", `${props.session.id}|${message.id}`)
layout.pendingMessage.set(
`${base64Encode(props.session.directory)}/${props.session.id}`,
message.id,
)
navigate(`${props.slug}/session/${props.session.id}`)
return
}
@@ -1989,49 +1903,25 @@ export default function Layout(props: ParentProps) {
<div
class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`}
classList={{
"opacity-100 pointer-events-auto": menu.open,
"opacity-0 pointer-events-none": !menu.open,
"opacity-100 pointer-events-auto": !!props.mobile,
"opacity-0 pointer-events-none": !props.mobile,
"group-hover/session:opacity-100 group-hover/session:pointer-events-auto": true,
"group-focus-within/session:opacity-100 group-focus-within/session:pointer-events-auto": true,
}}
>
<DropdownMenu modal={!sidebarHovering()} open={menu.open} onOpenChange={(open) => setMenu("open", open)}>
<Tooltip value={language.t("common.moreOptions")} placement="top">
<DropdownMenu.Trigger
as={IconButton}
icon="dot-grid"
variant="ghost"
class="size-6 rounded-md data-[expanded]:bg-surface-base-active"
aria-label={language.t("common.moreOptions")}
/>
</Tooltip>
<DropdownMenu.Portal mount={!props.mobile ? state.nav : undefined}>
<DropdownMenu.Content
onCloseAutoFocus={(event) => {
if (!menu.pendingRename) return
event.preventDefault()
setMenu("pendingRename", false)
openEditor(`session:${props.session.id}`, props.session.title)
}}
>
<DropdownMenu.Item
onSelect={() => {
setMenu("pendingRename", true)
setMenu("open", false)
}}
>
<DropdownMenu.ItemLabel>{language.t("common.rename")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Item onSelect={() => archiveSession(props.session)}>
<DropdownMenu.ItemLabel>{language.t("common.archive")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={() => dialog.show(() => <DialogDeleteSession session={props.session} />)}>
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
<Tooltip value={language.t("common.archive")} placement="top">
<IconButton
icon="archive"
variant="ghost"
class="size-6 rounded-md"
aria-label={language.t("common.archive")}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
void archiveSession(props.session)
}}
/>
</Tooltip>
</div>
</div>
)
@@ -2440,17 +2330,17 @@ export default function Layout(props: ParentProps) {
!selected() && !active(),
"bg-surface-base-hover border border-border-weak-base": !selected() && active(),
}}
onMouseEnter={() => {
onMouseEnter={(event: MouseEvent) => {
if (!overlay()) return
globalSync.child(props.project.worktree)
setState("hoverProject", props.project.worktree)
setState("hoverSession", undefined)
aim.enter(props.project.worktree, event)
}}
onMouseLeave={() => {
if (!overlay()) return
aim.leave(props.project.worktree)
}}
onFocus={() => {
if (!overlay()) return
globalSync.child(props.project.worktree)
setState("hoverProject", props.project.worktree)
setState("hoverSession", undefined)
aim.activate(props.project.worktree)
}}
onClick={() => navigateToProject(props.project.worktree)}
onBlur={() => setOpen(false)}
@@ -2598,17 +2488,22 @@ export default function Layout(props: ParentProps) {
}
const LocalWorkspace = (props: { project: LocalProject; mobile?: boolean }): JSX.Element => {
const [workspaceStore, setWorkspaceStore] = globalSync.child(props.project.worktree)
const workspace = createMemo(() => {
const [store, setStore] = globalSync.child(props.project.worktree)
return { store, setStore }
})
const slug = createMemo(() => base64Encode(props.project.worktree))
const sessions = createMemo(() =>
workspaceStore.session
.filter((session) => session.directory === workspaceStore.path.directory)
const sessions = createMemo(() => {
const store = workspace().store
return store.session
.filter((session) => session.directory === store.path.directory)
.filter((session) => !session.parentID && !session.time?.archived)
.toSorted(sortSessions(Date.now())),
)
.toSorted(sortSessions(Date.now()))
})
const children = createMemo(() => {
const store = workspace().store
const map = new Map<string, string[]>()
for (const session of workspaceStore.session) {
for (const session of store.session) {
if (!session.parentID) continue
const existing = map.get(session.parentID)
if (existing) {
@@ -2619,11 +2514,11 @@ export default function Layout(props: ParentProps) {
}
return map
})
const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false)
const booted = createMemo((prev) => prev || workspace().store.status === "complete", false)
const loading = createMemo(() => !booted() && sessions().length === 0)
const hasMore = createMemo(() => workspaceStore.sessionTotal > sessions().length)
const hasMore = createMemo(() => workspace().store.sessionTotal > sessions().length)
const loadMore = async () => {
setWorkspaceStore("limit", (limit) => limit + 5)
workspace().setStore("limit", (limit) => limit + 5)
await globalSync.project.loadSessions(props.project.worktree)
}
@@ -2935,7 +2830,7 @@ export default function Layout(props: ParentProps) {
return (
<div class="flex h-full w-full overflow-hidden">
<div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden">
<div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden" onMouseMove={aim.move}>
<div class="flex-1 min-h-0 w-full">
<DragDropProvider
onDragStart={handleDragStart}
@@ -3030,6 +2925,7 @@ export default function Layout(props: ParentProps) {
navLeave.current = undefined
}}
onMouseLeave={() => {
aim.reset()
if (!sidebarHovering()) return
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
@@ -3045,7 +2941,7 @@ export default function Layout(props: ParentProps) {
</div>
<Show when={!layout.sidebar.opened() ? hoverProjectData() : undefined} keyed>
{(project) => (
<div class="absolute inset-y-0 left-16 z-50 flex">
<div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
<SidebarPanel project={project} />
</div>
)}
File diff suppressed because it is too large Load Diff
+138
View File
@@ -0,0 +1,138 @@
type Point = { x: number; y: number }
export function createAim(props: {
enabled: () => boolean
active: () => string | undefined
el: () => HTMLElement | undefined
onActivate: (id: string) => void
delay?: number
max?: number
tolerance?: number
edge?: number
}) {
const state = {
locs: [] as Point[],
timer: undefined as number | undefined,
pending: undefined as string | undefined,
over: undefined as string | undefined,
last: undefined as Point | undefined,
}
const delay = props.delay ?? 250
const max = props.max ?? 4
const tolerance = props.tolerance ?? 80
const edge = props.edge ?? 18
const cancel = () => {
if (state.timer !== undefined) clearTimeout(state.timer)
state.timer = undefined
state.pending = undefined
}
const reset = () => {
cancel()
state.over = undefined
state.last = undefined
state.locs.length = 0
}
const move = (event: MouseEvent) => {
if (!props.enabled()) return
const el = props.el()
if (!el) return
const rect = el.getBoundingClientRect()
const x = event.clientX
const y = event.clientY
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) return
state.locs.push({ x, y })
if (state.locs.length > max) state.locs.shift()
}
const wait = () => {
if (!props.enabled()) return 0
if (!props.active()) return 0
const el = props.el()
if (!el) return 0
if (state.locs.length < 2) return 0
const rect = el.getBoundingClientRect()
const loc = state.locs[state.locs.length - 1]
if (!loc) return 0
const prev = state.locs[0] ?? loc
if (prev.x < rect.left || prev.x > rect.right || prev.y < rect.top || prev.y > rect.bottom) return 0
if (state.last && loc.x === state.last.x && loc.y === state.last.y) return 0
if (rect.right - loc.x <= edge) {
state.last = loc
return delay
}
const upper = { x: rect.right, y: rect.top - tolerance }
const lower = { x: rect.right, y: rect.bottom + tolerance }
const slope = (a: Point, b: Point) => (b.y - a.y) / (b.x - a.x)
const decreasing = slope(loc, upper)
const increasing = slope(loc, lower)
const prevDecreasing = slope(prev, upper)
const prevIncreasing = slope(prev, lower)
if (decreasing < prevDecreasing && increasing > prevIncreasing) {
state.last = loc
return delay
}
state.last = undefined
return 0
}
const activate = (id: string) => {
cancel()
props.onActivate(id)
}
const request = (id: string) => {
if (!id) return
if (props.active() === id) return
if (!props.active()) {
activate(id)
return
}
const ms = wait()
if (ms === 0) {
activate(id)
return
}
cancel()
state.pending = id
state.timer = window.setTimeout(() => {
state.timer = undefined
if (state.pending !== id) return
state.pending = undefined
if (!props.enabled()) return
if (!props.active()) return
if (state.over !== id) return
props.onActivate(id)
}, ms)
}
const enter = (id: string, event: MouseEvent) => {
if (!props.enabled()) return
state.over = id
move(event)
request(id)
}
const leave = (id: string) => {
if (state.over === id) state.over = undefined
if (state.pending === id) cancel()
}
return { move, enter, leave, activate, request, cancel, reset }
}
@@ -26,6 +26,7 @@ export const oaCompatHelper: ProviderHelper = () => ({
modifyUrl: (providerApi: string) => providerApi + "/chat/completions",
modifyHeaders: (headers: Headers, body: Record<string, any>, apiKey: string) => {
headers.set("authorization", `Bearer ${apiKey}`)
headers.set("x-session-affinity", headers.get("x-kilo-session") ?? "") // kilocode_change
},
modifyBody: (body: Record<string, any>) => {
return {
+3 -3
View File
@@ -25,9 +25,9 @@
"db": "sst shell drizzle-kit",
"db-dev": "sst shell --stage dev -- drizzle-kit",
"db-prod": "sst shell --stage production -- drizzle-kit",
"shell": "sst shell -- bun",
"shell-dev": "sst shell --stage dev -- bun",
"shell-prod": "sst shell --stage production -- bun",
"shell": "sst shell",
"shell-dev": "sst shell --stage dev",
"shell-prod": "sst shell --stage production",
"update-models": "script/update-models.ts",
"promote-models-to-dev": "script/promote-models.ts dev",
"promote-models-to-prod": "script/promote-models.ts production",
@@ -17,10 +17,8 @@ const oldValues = Array.from({ length: PARTS }, (_, i) => {
?.split("=")
.slice(1)
.join("=")
// TODO
//if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
//return value
return value ?? ""
if (!value) throw new Error(`ZEN_MODELS${i + 1} not found`)
return value
})
// store the prettified json to a temp file
+2 -1
View File
@@ -19,5 +19,6 @@
"@types/node": "catalog:",
"cloudflare": "5.2.0"
},
"version": "1.0.14"
"version": "1.0.14",
"peerDependencies": {}
}
@@ -42,6 +42,13 @@
"active": true,
"targets": ["deb", "rpm", "dmg", "nsis", "app"],
"externalBin": ["sidecars/kilo-cli"],
"linux": {
"rpm": {
"compression": {
"type": "none"
}
}
},
"macOS": {
"entitlements": "./entitlements.plist"
},
@@ -21,6 +21,11 @@
"files": {
"/usr/share/metainfo/ai.kilo.kilo.metainfo.xml": "release/appstream.metainfo.xml"
}
},
"rpm": {
"compression": {
"type": "none"
}
}
}
},
+32
View File
@@ -0,0 +1,32 @@
export const dict = {
"desktop.menu.checkForUpdates": "Provjeri ažuriranja...",
"desktop.menu.installCli": "Instaliraj CLI...",
"desktop.menu.reloadWebview": "Ponovo učitavanje webview-a",
"desktop.menu.restart": "Restartuj",
"desktop.dialog.chooseFolder": "Odaberi folder",
"desktop.dialog.chooseFile": "Odaberi datoteku",
"desktop.dialog.saveFile": "Sačuvaj datoteku",
"desktop.updater.checkFailed.title": "Provjera ažuriranja nije uspjela",
"desktop.updater.checkFailed.message": "Nije moguće provjeriti ažuriranja",
"desktop.updater.none.title": "Nema dostupnog ažuriranja",
"desktop.updater.none.message": "Već koristiš najnoviju verziju Kilo-a",
"desktop.updater.downloadFailed.title": "Ažuriranje nije uspjelo",
"desktop.updater.downloadFailed.message": "Neuspjelo preuzimanje ažuriranja",
"desktop.updater.downloaded.title": "Ažuriranje preuzeto",
"desktop.updater.downloaded.prompt":
"Verzija {{version}} Kilo-a je preuzeta. Želiš li da je instaliraš i ponovo pokreneš aplikaciju?",
"desktop.updater.installFailed.title": "Ažuriranje nije uspjelo",
"desktop.updater.installFailed.message": "Neuspjela instalacija ažuriranja",
"desktop.cli.installed.title": "CLI instaliran",
"desktop.cli.installed.message":
"CLI je instaliran u {{path}}\n\nRestartuj terminal da bi koristio komandu 'opencode'.",
"desktop.cli.failed.title": "Instalacija nije uspjela",
"desktop.cli.failed.message": "Neuspjela instalacija CLI-a: {{error}}",
"desktop.error.serverStartFailed.title": "Kilo se nije mogao pokrenuti",
"desktop.error.serverStartFailed.description":
"Lokalni Kilo server se nije mogao pokrenuti. Restartuj aplikaciju ili provjeri mrežne postavke (VPN/proxy) i pokušaj ponovo.",
}
+37 -2
View File
@@ -15,6 +15,7 @@ import { dict as desktopRu } from "./ru"
import { dict as desktopAr } from "./ar"
import { dict as desktopNo } from "./no"
import { dict as desktopBr } from "./br"
import { dict as desktopBs } from "./bs"
import { dict as appEn } from "../../../app/src/i18n/en"
import { dict as appZh } from "../../../app/src/i18n/zh"
@@ -30,13 +31,45 @@ import { dict as appRu } from "../../../app/src/i18n/ru"
import { dict as appAr } from "../../../app/src/i18n/ar"
import { dict as appNo } from "../../../app/src/i18n/no"
import { dict as appBr } from "../../../app/src/i18n/br"
import { dict as appBs } from "../../../app/src/i18n/bs"
export type Locale = "en" | "zh" | "zht" | "ko" | "de" | "es" | "fr" | "da" | "ja" | "pl" | "ru" | "ar" | "no" | "br"
export type Locale =
| "en"
| "zh"
| "zht"
| "ko"
| "de"
| "es"
| "fr"
| "da"
| "ja"
| "pl"
| "ru"
| "ar"
| "no"
| "br"
| "bs"
type RawDictionary = typeof appEn & typeof desktopEn
type Dictionary = i18n.Flatten<RawDictionary>
const LOCALES: readonly Locale[] = ["en", "zh", "zht", "ko", "de", "es", "fr", "da", "ja", "pl", "ru", "ar", "no", "br"]
const LOCALES: readonly Locale[] = [
"en",
"zh",
"zht",
"ko",
"de",
"es",
"fr",
"da",
"ja",
"pl",
"ru",
"bs",
"ar",
"no",
"br",
]
function detectLocale(): Locale {
if (typeof navigator !== "object") return "en"
@@ -64,6 +97,7 @@ function detectLocale(): Locale {
)
return "no"
if (language.toLowerCase().startsWith("pt")) return "br"
if (language.toLowerCase().startsWith("bs")) return "bs"
}
return "en"
@@ -108,6 +142,7 @@ function build(locale: Locale): Dictionary {
if (locale === "ar") return { ...base, ...i18n.flatten(appAr), ...i18n.flatten(desktopAr) }
if (locale === "no") return { ...base, ...i18n.flatten(appNo), ...i18n.flatten(desktopNo) }
if (locale === "br") return { ...base, ...i18n.flatten(appBr), ...i18n.flatten(desktopBr) }
if (locale === "bs") return { ...base, ...i18n.flatten(appBs), ...i18n.flatten(desktopBs) }
return { ...base, ...i18n.flatten(appKo), ...i18n.flatten(desktopKo) }
}
-11
View File
@@ -30,17 +30,6 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
void initI18n()
// Floating UI can call getComputedStyle with non-elements (e.g., null refs, virtual elements).
// This happens on all platforms (WebView2 on Windows, WKWebView on macOS), not just Windows.
const originalGetComputedStyle = window.getComputedStyle
window.getComputedStyle = ((elt: Element, pseudoElt?: string | null) => {
if (!(elt instanceof Element)) {
// Fall back to a safe element when a non-element is passed.
return originalGetComputedStyle(document.documentElement, pseudoElt ?? undefined)
}
return originalGetComputedStyle(elt, pseudoElt ?? undefined)
}) as typeof window.getComputedStyle
let update: Update | null = null
const deepLinkEvent = "opencode:deep-link"
+6 -6
View File
@@ -1,7 +1,7 @@
id = "kilo"
name = "Kilo"
description = "The open source coding agent."
version = "1.1.51"
version = "1.1.52"
schema_version = 1
authors = ["Anomaly"]
repository = "https://github.com/Kilo-Org/kilo"
@@ -11,26 +11,26 @@ name = "Kilo"
icon = "./icons/opencode.svg"
[agent_servers.opencode.targets.darwin-aarch64]
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.51/opencode-darwin-arm64.zip"
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.52/opencode-darwin-arm64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.darwin-x86_64]
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.51/opencode-darwin-x64.zip"
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.52/opencode-darwin-x64.zip"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-aarch64]
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.51/opencode-linux-arm64.tar.gz"
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.52/opencode-linux-arm64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.linux-x86_64]
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.51/opencode-linux-x64.tar.gz"
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.52/opencode-linux-x64.tar.gz"
cmd = "./opencode"
args = ["acp"]
[agent_servers.opencode.targets.windows-x86_64]
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.51/opencode-windows-x64.zip"
archive = "https://github.com/Kilo-Org/kilo/releases/download/v1.1.52/opencode-windows-x64.zip"
cmd = "./opencode.exe"
args = ["acp"]
+17
View File
@@ -0,0 +1,17 @@
// kilocode_change - new file
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
// Kilo Gateway provider translations
"provider.connect.kiloGateway.line1":
"Kilo Gateway vam daje pristup kuriranom skupu pouzdanih, optimizovanih modela za coding agente.",
"provider.connect.kiloGateway.line2":
"Sa jednim API ključem dobijate pristup modelima kao što su Claude, GPT, Gemini, GLM i drugi.",
"provider.connect.kiloGateway.visit.prefix": "Posjetite ",
"provider.connect.kiloGateway.visit.link": "kilo.ai",
"provider.connect.kiloGateway.visit.suffix": " da preuzmete svoj API ključ.",
// Provider dialog translations
"dialog.provider.group.recommended": "Preporučeno",
"dialog.provider.kilo.note": "Pristup za 500+ AI modela",
} satisfies Partial<Record<Keys, string>>
+9 -7
View File
@@ -73,17 +73,16 @@
"@ai-sdk/xai": "2.0.56",
"@clack/prompts": "1.0.0-alpha.1",
"@gitlab/gitlab-ai-provider": "3.4.0",
"@gitlab/opencode-gitlab-auth": "1.3.2",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
"@kilocode/kilo-gateway": "workspace:*",
"@kilocode/kilo-telemetry": "workspace:*",
"@kilocode/plugin": "workspace:*",
"@kilocode/sdk": "workspace:*",
"@modelcontextprotocol/sdk": "1.25.2",
"@octokit/graphql": "9.0.2",
"@octokit/rest": "catalog:",
"@openauthjs/openauth": "catalog:",
"@kilocode/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@kilocode/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.4",
"@opentui/core": "0.1.77",
@@ -97,7 +96,7 @@
"ai": "catalog:",
"ai-gateway-provider": "2.3.1",
"bonjour-service": "1.3.0",
"bun-pty": "0.4.4",
"bun-pty": "0.4.8",
"chokidar": "4.0.3",
"clipboardy": "4.0.0",
"decimal.js": "10.5.0",
@@ -123,6 +122,9 @@
"xdg-basedir": "5.1.0",
"yargs": "18.0.0",
"zod": "catalog:",
"zod-to-json-schema": "3.24.5"
}
"zod-to-json-schema": "3.24.5",
"@kilocode/kilo-gateway": "workspace:*",
"@kilocode/kilo-telemetry": "workspace:*"
},
"peerDependencies": {}
}
+4 -75
View File
@@ -23,10 +23,9 @@ await Bun.file(`./dist/${pkg.name}/LICENSE`).write(await Bun.file("../../LICENSE
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
name: pkg.name,
name: pkg.name + "-ai",
bin: {
kilo: `./bin/kilo`,
kilocode: `./bin/kilo`,
[pkg.name]: `./bin/${pkg.name}`,
},
scripts: {
postinstall: "bun ./postinstall.mjs || node ./postinstall.mjs",
@@ -34,10 +33,6 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
version: version,
license: pkg.license,
optionalDependencies: binaries,
repository: {
type: "git",
url: "https://github.com/Kilo-Org/kilo",
},
},
null,
2,
@@ -52,7 +47,7 @@ const tasks = Object.entries(binaries).map(async ([name]) => {
await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(`./dist/${name}`)
})
await Promise.all(tasks)
await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag ${Script.channel} --provenance`
await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag ${Script.channel}`
const image = "ghcr.io/Kilo-Org/kilo"
const platforms = "linux/amd64,linux/arm64"
@@ -100,73 +95,7 @@ if (!Script.preview) {
"",
].join("\n")
// Source-based PKGBUILD for opencode
const sourcePkgbuild = [
"# Maintainer: dax",
"# Maintainer: adam",
"",
"pkgname='opencode'",
`pkgver=${pkgver}`,
`_subver=${_subver}`,
"options=('!debug' '!strip')",
"pkgrel=1",
"pkgdesc='The AI coding agent built for the terminal.'",
"url='https://github.com/Kilo-Org/kilo'",
"arch=('aarch64' 'x86_64')",
"license=('MIT')",
"provides=('opencode')",
"conflicts=('opencode-bin')",
"depends=('ripgrep')",
"makedepends=('git' 'bun' 'go')",
"",
`source=("opencode-\${pkgver}.tar.gz::https://github.com/Kilo-Org/kilo/archive/v\${pkgver}\${_subver}.tar.gz")`,
`sha256sums=('SKIP')`,
"",
"build() {",
` cd "opencode-\${pkgver}"`,
` bun install`,
" cd ./packages/opencode",
` OPENCODE_CHANNEL=latest OPENCODE_VERSION=${pkgver} bun run ./script/build.ts --single`,
"}",
"",
"package() {",
` cd "opencode-\${pkgver}/packages/opencode"`,
' mkdir -p "${pkgdir}/usr/bin"',
' target_arch="x64"',
' case "$CARCH" in',
' x86_64) target_arch="x64" ;;',
' aarch64) target_arch="arm64" ;;',
' *) printf "unsupported architecture: %s\\n" "$CARCH" >&2 ; return 1 ;;',
" esac",
' libc=""',
" if command -v ldd >/dev/null 2>&1; then",
" if ldd --version 2>&1 | grep -qi musl; then",
' libc="-musl"',
" fi",
" fi",
' if [ -z "$libc" ] && ls /lib/ld-musl-* >/dev/null 2>&1; then',
' libc="-musl"',
" fi",
' base=""',
' if [ "$target_arch" = "x64" ]; then',
" if ! grep -qi avx2 /proc/cpuinfo 2>/dev/null; then",
' base="-baseline"',
" fi",
" fi",
' bin="dist/opencode-linux-${target_arch}${base}${libc}/bin/opencode"',
' if [ ! -f "$bin" ]; then',
' printf "unable to find binary for %s%s%s\\n" "$target_arch" "$base" "$libc" >&2',
" return 1",
" fi",
' install -Dm755 "$bin" "${pkgdir}/usr/bin/opencode"',
"}",
"",
].join("\n")
for (const [pkg, pkgbuild] of [
["opencode-bin", binaryPkgbuild],
["opencode", sourcePkgbuild],
]) {
for (const [pkg, pkgbuild] of [["opencode-bin", binaryPkgbuild]]) {
for (let i = 0; i < 30; i++) {
try {
await $`rm -rf ./dist/aur-${pkg}`
+2 -8
View File
@@ -7,6 +7,7 @@ import { NamedError } from "@opencode-ai/util/error"
import { readableStreamToText } from "bun"
import { Lock } from "../util/lock"
import { PackageRegistry } from "./registry"
import { proxied } from "@/util/proxied"
export namespace BunProc {
const log = Log.create({ service: "bun" })
@@ -86,20 +87,13 @@ export namespace BunProc {
log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion })
}
const proxied = !!(
process.env.HTTP_PROXY ||
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.https_proxy
)
// Build command arguments
const args = [
"add",
"--force",
"--exact",
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
...(proxied ? ["--no-cache"] : []),
...(proxied() ? ["--no-cache"] : []),
"--cwd",
Global.Path.cache,
pkg + "@" + version,
@@ -550,6 +550,7 @@ export function Session() {
{
title: showThinking() ? "Hide thinking" : "Show thinking",
value: "session.toggle.thinking",
keybind: "display_thinking",
category: "Session",
slash: {
name: "thinking",
@@ -1631,6 +1632,7 @@ function BlockTool(props: {
function Bash(props: ToolProps<typeof BashTool>) {
const { theme } = useTheme()
const sync = useSync()
const isRunning = createMemo(() => props.part.state.status === "running")
const output = createMemo(() => stripAnsi(props.metadata.output?.trim() ?? ""))
const [expanded, setExpanded] = createSignal(false)
const lines = createMemo(() => output().split("\n"))
@@ -1671,6 +1673,7 @@ function Bash(props: ToolProps<typeof BashTool>) {
<BlockTool
title={title()}
part={props.part}
spinner={isRunning()}
onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
>
<box gap={1}>
@@ -1,5 +1,5 @@
import { createStore } from "solid-js/store"
import { createMemo, For, Show } from "solid-js"
import { createMemo, createSignal, For, Show } from "solid-js"
import { useKeyboard } from "@opentui/solid"
import type { TextareaRenderable } from "@opentui/core"
import { useKeybind } from "../../context/keybind"
@@ -19,6 +19,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
const questions = createMemo(() => props.request.questions)
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
const tabs = createMemo(() => (single() ? 1 : questions().length + 1)) // questions + confirm tab (no confirm for single select)
const [tabHover, setTabHover] = createSignal<number | "confirm" | null>(null)
const [store, setStore] = createStore({
tab: 0,
answers: [] as QuestionAnswer[],
@@ -269,7 +270,15 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
<box
paddingLeft={1}
paddingRight={1}
backgroundColor={isActive() ? theme.accent : theme.backgroundElement}
backgroundColor={
isActive()
? theme.accent
: tabHover() === index()
? theme.backgroundElement
: theme.backgroundPanel
}
onMouseOver={() => setTabHover(index())}
onMouseOut={() => setTabHover(null)}
onMouseUp={() => selectTab(index())}
>
<text
@@ -290,7 +299,11 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
<box
paddingLeft={1}
paddingRight={1}
backgroundColor={confirm() ? theme.accent : theme.backgroundElement}
backgroundColor={
confirm() ? theme.accent : tabHover() === "confirm" ? theme.backgroundElement : theme.backgroundPanel
}
onMouseOver={() => setTabHover("confirm")}
onMouseOut={() => setTabHover(null)}
onMouseUp={() => selectTab(questions().length)}
>
<text fg={confirm() ? selectedForeground(theme, theme.accent) : theme.textMuted}>Confirm</text>
+52 -14
View File
@@ -24,11 +24,13 @@ import { LSPServer } from "../lsp/server"
import { BunProc } from "@/bun"
import { Installation } from "@/installation"
import { ConfigMarkdown } from "./markdown"
import { existsSync } from "fs"
import { constants, existsSync } from "fs"
import { Bus } from "@/bus"
import { GlobalBus } from "@/bus/global"
import { Event } from "../server/event"
import { PackageRegistry } from "@/bun/registry"
import { proxied } from "@/util/proxied"
import { iife } from "@/util/iife"
import { ModesMigrator } from "../kilocode/modes-migrator" // kilocode_change
import { RulesMigrator } from "../kilocode/rules-migrator" // kilocode_change
@@ -229,6 +231,8 @@ export namespace Config {
log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
}
const deps = []
for (const dir of unique(directories)) {
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
for (const file of ["opencode.jsonc", "opencode.json"]) {
@@ -241,10 +245,12 @@ export namespace Config {
}
}
const shouldInstall = await needsInstall(dir)
if (shouldInstall) {
await installDependencies(dir)
}
deps.push(
iife(async () => {
const shouldInstall = await needsInstall(dir)
if (shouldInstall) await installDependencies(dir)
}),
)
result.command = mergeDeep(result.command ?? {}, await loadCommand(dir))
result.agent = mergeDeep(result.agent, await loadAgent(dir))
@@ -318,32 +324,63 @@ export namespace Config {
return {
config: result,
directories,
deps,
}
})
export async function waitForDependencies() {
const deps = await state().then((x) => x.deps)
await Promise.all(deps)
}
export async function installDependencies(dir: string) {
const pkg = path.join(dir, "package.json")
const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION
const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION
if (!(await Bun.file(pkg).exists())) {
await Bun.write(pkg, "{}")
const json = await Bun.file(pkg)
.json()
.catch(() => ({}))
json.dependencies = {
...json.dependencies,
"@opencode-ai/plugin": targetVersion,
}
await Bun.write(pkg, JSON.stringify(json, null, 2))
await new Promise((resolve) => setTimeout(resolve, 3000))
const gitignore = path.join(dir, ".gitignore")
const hasGitIgnore = await Bun.file(gitignore).exists()
if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))
await BunProc.run(["add", `@kilocode/plugin@${targetVersion}`, "--exact"], {
// kilocode_change
cwd: dir,
}).catch(() => {})
// Install any additional dependencies defined in the package.json
// This allows local plugins and custom tools to use external packages
await BunProc.run(["install"], { cwd: dir }).catch(() => {})
await BunProc.run(
[
"install",
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
...(proxied() ? ["--no-cache"] : []),
],
{ cwd: dir },
).catch(() => {})
}
async function isWritable(dir: string) {
try {
await fs.access(dir, constants.W_OK)
return true
} catch {
return false
}
}
async function needsInstall(dir: string) {
// Some config dirs may be read-only.
// Installing deps there will fail; skip installation in that case.
const writable = await isWritable(dir)
if (!writable) {
log.debug("config dir is not writable, skipping dependency install", { dir })
return false
}
const nodeModules = path.join(dir, "node_modules")
if (!existsSync(nodeModules)) return true
@@ -953,6 +990,7 @@ export namespace Config {
terminal_title_toggle: z.string().optional().default("none").describe("Toggle terminal title"),
tips_toggle: z.string().optional().default("<leader>h").describe("Toggle tips on home screen"),
news_toggle: z.string().optional().default("none").describe("Toggle news on home screen"), // kilocode_change
display_thinking: z.string().optional().default("none").describe("Toggle thinking blocks visibility"),
})
.strict()
.meta({
+1
View File
@@ -361,6 +361,7 @@ export async function CodexAuthPlugin(input: PluginInput): Promise<Hooks> {
"gpt-5.1-codex-mini",
"gpt-5.2",
"gpt-5.2-codex",
"gpt-5.3-codex",
"gpt-5.1-codex",
])
for (const modelId of Object.keys(provider.models)) {
+7 -4
View File
@@ -301,17 +301,20 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
},
],
},
"chat.headers": async (input, output) => {
if (!input.model.providerID.includes("github-copilot")) return
"chat.headers": async (incoming, output) => {
if (!incoming.model.providerID.includes("github-copilot")) return
if (input.model.api.npm === "@ai-sdk/anthropic") {
if (incoming.model.api.npm === "@ai-sdk/anthropic") {
output.headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
}
const session = await sdk.session
.get({
path: {
id: input.sessionID,
id: incoming.sessionID,
},
query: {
directory: input.directory,
},
throwOnError: true,
})
+11 -2
View File
@@ -11,15 +11,23 @@ import { CodexAuthPlugin } from "./codex"
import { Session } from "../session"
import { NamedError } from "@opencode-ai/util/error"
import { CopilotAuthPlugin } from "./copilot"
import { gitlabAuthPlugin as GitlabAuthPlugin } from "@gitlab/opencode-gitlab-auth"
import { KiloAuthPlugin } from "@kilocode/kilo-gateway" // kilocode_change
export namespace Plugin {
const log = Log.create({ service: "plugin" })
const BUILTIN = ["opencode-anthropic-auth@0.0.13", "@gitlab/opencode-gitlab-auth@1.3.2"]
const BUILTIN = ["opencode-anthropic-auth@0.0.13"]
// Built-in plugins that are directly imported (not installed from npm)
const INTERNAL_PLUGINS: PluginInstance[] = [KiloAuthPlugin, CodexAuthPlugin, CopilotAuthPlugin] // kilocode_change
// kilocode_change start
const INTERNAL_PLUGINS: PluginInstance[] = [
KiloAuthPlugin,
CodexAuthPlugin,
CopilotAuthPlugin,
GitlabAuthPlugin as unknown as PluginInstance,
] // kilocode_change end
const state = Instance.state(async () => {
const client = createOpencodeClient({
@@ -45,6 +53,7 @@ export namespace Plugin {
}
const plugins = [...(config.plugin ?? [])]
if (plugins.length) await Config.waitForDependencies()
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
plugins.push(...BUILTIN)
}
@@ -443,6 +443,29 @@ export namespace Provider {
},
}
},
"cloudflare-workers-ai": async (input) => {
const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
if (!accountId) return { autoload: false }
const apiKey = await iife(async () => {
const envToken = Env.get("CLOUDFLARE_API_KEY")
if (envToken) return envToken
const auth = await Auth.get(input.id)
if (auth?.type === "api") return auth.key
return undefined
})
return {
autoload: !!apiKey,
options: {
apiKey,
baseURL: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
},
async getModel(sdk: any, modelID: string) {
return sdk.languageModel(modelID)
},
}
},
"cloudflare-ai-gateway": async (input) => {
const accountId = Env.get("CLOUDFLARE_ACCOUNT_ID")
const gateway = Env.get("CLOUDFLARE_GATEWAY_ID")
@@ -799,6 +799,18 @@ export namespace ProviderTransform {
}
}
// Enable thinking by default for kimi-k2.5/k2p5 models using anthropic SDK
const modelId = input.model.api.id.toLowerCase()
if (
(input.model.api.npm === "@ai-sdk/anthropic" || input.model.api.npm === "@ai-sdk/google-vertex/anthropic") &&
(modelId.includes("k2p5") || modelId.includes("kimi-k2.5") || modelId.includes("kimi-k2p5"))
) {
result["thinking"] = {
type: "enabled",
budgetTokens: Math.min(16_000, Math.floor(input.model.limit.output / 2 - 1)),
}
}
if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) {
if (!input.model.api.id.includes("gpt-5-pro")) {
result["reasoningEffort"] = "medium"
+54 -2
View File
@@ -438,6 +438,26 @@ export namespace MessageV2 {
export function toModelMessages(input: WithParts[], model: Provider.Model): ModelMessage[] {
const result: UIMessage[] = []
const toolNames = new Set<string>()
// Track media from tool results that need to be injected as user messages
// for providers that don't support media in tool results.
//
// OpenAI-compatible APIs only support string content in tool results, so we need
// to extract media and inject as user messages. Other SDKs (anthropic, google,
// bedrock) handle type: "content" with media parts natively.
//
// Only apply this workaround if the model actually supports image input -
// otherwise there's no point extracting images.
const supportsMediaInToolResults = (() => {
if (model.api.npm === "@ai-sdk/anthropic") return true
if (model.api.npm === "@ai-sdk/openai") return true
if (model.api.npm === "@ai-sdk/amazon-bedrock") return true
if (model.api.npm === "@ai-sdk/google-vertex/anthropic") return true
if (model.api.npm === "@ai-sdk/google") {
const id = model.api.id.toLowerCase()
return id.includes("gemini-3") && !id.includes("gemini-2")
}
return false
})()
const toModelOutput = (output: unknown) => {
if (typeof output === "string") {
@@ -514,6 +534,7 @@ export namespace MessageV2 {
if (msg.info.role === "assistant") {
const differentModel = `${model.providerID}/${model.id}` !== `${msg.info.providerID}/${msg.info.modelID}`
const media: Array<{ mime: string; url: string }> = []
if (
msg.info.error &&
@@ -545,11 +566,23 @@ export namespace MessageV2 {
if (part.state.status === "completed") {
const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output
const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? [])
// For providers that don't support media in tool results, extract media files
// (images, PDFs) to be sent as a separate user message
const isMediaAttachment = (a: { mime: string }) =>
a.mime.startsWith("image/") || a.mime === "application/pdf"
const mediaAttachments = attachments.filter(isMediaAttachment)
const nonMediaAttachments = attachments.filter((a) => !isMediaAttachment(a))
if (!supportsMediaInToolResults && mediaAttachments.length > 0) {
media.push(...mediaAttachments)
}
const finalAttachments = supportsMediaInToolResults ? attachments : nonMediaAttachments
const output =
attachments.length > 0
finalAttachments.length > 0
? {
text: outputText,
attachments,
attachments: finalAttachments,
}
: outputText
@@ -593,6 +626,25 @@ export namespace MessageV2 {
}
if (assistantMessage.parts.length > 0) {
result.push(assistantMessage)
// Inject pending media as a user message for providers that don't support
// media (images, PDFs) in tool results
if (media.length > 0) {
result.push({
id: Identifier.ascending("message"),
role: "user",
parts: [
{
type: "text" as const,
text: "Attached image(s) from tool result:",
},
...media.map((attachment) => ({
type: "file" as const,
url: attachment.url,
mediaType: attachment.mime,
})),
],
})
}
}
}
}
+5 -3
View File
@@ -968,9 +968,11 @@ export namespace SessionPrompt {
// have to normalize, symbol search returns absolute paths
// Decode the pathname since URL constructor doesn't automatically decode it
const filepath = fileURLToPath(part.url)
const stat = await Bun.file(filepath).stat()
const stat = await Bun.file(filepath)
.stat()
.catch(() => undefined)
if (stat.isDirectory()) {
if (stat?.isDirectory()) {
part.mime = "application/x-directory"
}
@@ -989,7 +991,7 @@ export namespace SessionPrompt {
// workspace/symbol searches, so we'll try to find the
// symbol in the document to get the full range
if (start === end) {
const symbols = await LSP.documentSymbol(filePathURI)
const symbols = await LSP.documentSymbol(filePathURI).catch(() => [])
for (const symbol of symbols) {
let range: LSP.Range | undefined
if ("range" in symbol) {
+9 -12
View File
@@ -35,18 +35,15 @@ export namespace ToolRegistry {
const custom = [] as Tool.Info[]
const glob = new Bun.Glob("{tool,tools}/*.{js,ts}")
for (const dir of await Config.directories()) {
for await (const match of glob.scan({
cwd: dir,
absolute: true,
followSymlinks: true,
dot: true,
})) {
const namespace = path.basename(match, path.extname(match))
const mod = await import(match)
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
}
const matches = await Config.directories().then((dirs) =>
dirs.flatMap((dir) => [...glob.scanSync({ cwd: dir, absolute: true, followSymlinks: true, dot: true })]),
)
if (matches.length) await Config.waitForDependencies()
for (const match of matches) {
const namespace = path.basename(match, path.extname(match))
const mod = await import(match)
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
}
}
+15 -4
View File
@@ -16,7 +16,12 @@ const parameters = z.object({
description: z.string().describe("A short (3-5 words) description of the task"),
prompt: z.string().describe("The task for the agent to perform"),
subagent_type: z.string().describe("The type of specialized agent to use for this task"),
session_id: z.string().describe("Existing Task session to continue").optional(),
task_id: z
.string()
.describe(
"This should only be set if you mean to resume a previous task (you can pass a prior task_id and the task will continue the same subagent session as before instead of creating a fresh one)",
)
.optional(),
command: z.string().describe("The command that triggered this task").optional(),
})
@@ -60,8 +65,8 @@ export const TaskTool = Tool.define("task", async (ctx) => {
const hasTaskPermission = agent.permission.some((rule) => rule.permission === "task")
const session = await iife(async () => {
if (params.session_id) {
const found = await Session.get(params.session_id).catch(() => {})
if (params.task_id) {
const found = await Session.get(params.task_id).catch(() => {})
if (found) return found
}
@@ -176,7 +181,13 @@ export const TaskTool = Tool.define("task", async (ctx) => {
}))
const text = result.parts.findLast((x) => x.type === "text")?.text ?? ""
const output = text + "\n\n" + ["<task_metadata>", `session_id: ${session.id}`, "</task_metadata>"].join("\n")
const output = [
`task_id: ${session.id} (for resuming to continue this task if needed)`,
"",
"<task_result>",
text,
"</task_result>",
].join("\n")
return {
title: params.description,
+3 -3
View File
@@ -17,10 +17,10 @@ When NOT to use the Task tool:
Usage notes:
1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
3. Each agent invocation is stateless unless you provide a session_id. Your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session.
3. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
4. The agent's outputs should generally be trusted
5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent
5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands).
6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above):
+3
View File
@@ -0,0 +1,3 @@
export function proxied() {
return !!(process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)
}
@@ -566,6 +566,68 @@ test("gets config directories", async () => {
})
})
test("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR", async () => {
if (process.platform === "win32") return
await using tmp = await tmpdir<string>({
init: async (dir) => {
const ro = path.join(dir, "readonly")
await fs.mkdir(ro, { recursive: true })
await fs.chmod(ro, 0o555)
return ro
},
dispose: async (dir) => {
const ro = path.join(dir, "readonly")
await fs.chmod(ro, 0o755).catch(() => {})
return ro
},
})
const prev = process.env.OPENCODE_CONFIG_DIR
process.env.OPENCODE_CONFIG_DIR = tmp.extra
try {
await Instance.provide({
directory: tmp.path,
fn: async () => {
await Config.get()
},
})
} finally {
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
else process.env.OPENCODE_CONFIG_DIR = prev
}
})
test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
await using tmp = await tmpdir<string>({
init: async (dir) => {
const cfg = path.join(dir, "configdir")
await fs.mkdir(cfg, { recursive: true })
return cfg
},
})
const prev = process.env.OPENCODE_CONFIG_DIR
process.env.OPENCODE_CONFIG_DIR = tmp.extra
try {
await Instance.provide({
directory: tmp.path,
fn: async () => {
await Config.get()
await Config.waitForDependencies()
},
})
expect(await Bun.file(path.join(tmp.extra, "package.json")).exists()).toBe(true)
expect(await Bun.file(path.join(tmp.extra, ".gitignore")).exists()).toBe(true)
} finally {
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
else process.env.OPENCODE_CONFIG_DIR = prev
}
})
test("resolves scoped npm plugins in config", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
@@ -1,46 +1,12 @@
import { test, expect, mock, describe } from "bun:test"
import { test, expect, describe } from "bun:test"
import path from "path"
import { unlink } from "fs/promises"
// === Mocks ===
// These mocks are required because Provider.list() triggers:
// 1. BunProc.install("@aws-sdk/credential-providers") - in bedrock custom loader
// 2. Plugin.list() which calls BunProc.install() for default plugins
// Without mocks, these would attempt real package installations that timeout in tests.
mock.module("../../src/bun/index", () => ({
BunProc: {
install: async (pkg: string, _version?: string) => {
// Return package name without version for mocking
const lastAtIndex = pkg.lastIndexOf("@")
return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg
},
run: async () => {
throw new Error("BunProc.run should not be called in tests")
},
which: () => process.execPath,
InstallFailedError: class extends Error {},
},
}))
mock.module("@aws-sdk/credential-providers", () => ({
fromNodeProviderChain: () => async () => ({
accessKeyId: "mock-access-key-id",
secretAccessKey: "mock-secret-access-key",
}),
}))
const mockPlugin = () => ({})
mock.module("opencode-copilot-auth", () => ({ default: mockPlugin }))
mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin }))
mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin }))
// Import after mocks are set up
const { tmpdir } = await import("../fixture/fixture")
const { Instance } = await import("../../src/project/instance")
const { Provider } = await import("../../src/provider/provider")
const { Env } = await import("../../src/env")
const { Global } = await import("../../src/global")
import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { Provider } from "../../src/provider/provider"
import { Env } from "../../src/env"
import { Global } from "../../src/global"
test("Bedrock: config region takes precedence over AWS_REGION env var", async () => {
await using tmp = await tmpdir({
@@ -1,35 +1,11 @@
import { test, expect, mock } from "bun:test"
import { test, expect } from "bun:test"
import path from "path"
// === Mocks ===
// These mocks prevent real package installations during tests
mock.module("../../src/bun/index", () => ({
BunProc: {
install: async (pkg: string, _version?: string) => {
// Return package name without version for mocking
const lastAtIndex = pkg.lastIndexOf("@")
return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg
},
run: async () => {
throw new Error("BunProc.run should not be called in tests")
},
which: () => process.execPath,
InstallFailedError: class extends Error {},
},
}))
const mockPlugin = () => ({})
mock.module("opencode-copilot-auth", () => ({ default: mockPlugin }))
mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin }))
mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin }))
// Import after mocks are set up
const { tmpdir } = await import("../fixture/fixture")
const { Instance } = await import("../../src/project/instance")
const { Provider } = await import("../../src/provider/provider")
const { Env } = await import("../../src/env")
const { Global } = await import("../../src/global")
import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { Provider } from "../../src/provider/provider"
import { Env } from "../../src/env"
import { Global } from "../../src/global"
test("GitLab Duo: loads provider with API key from environment", async () => {
await using tmp = await tmpdir({
@@ -1,27 +1,6 @@
import { test, expect, mock } from "bun:test"
import { test, expect } from "bun:test"
import path from "path"
// Mock BunProc and default plugins to prevent actual installations during tests
mock.module("../../src/bun/index", () => ({
BunProc: {
install: async (pkg: string, _version?: string) => {
// Return package name without version for mocking
const lastAtIndex = pkg.lastIndexOf("@")
return lastAtIndex > 0 ? pkg.substring(0, lastAtIndex) : pkg
},
run: async () => {
throw new Error("BunProc.run should not be called in tests")
},
which: () => process.execPath,
InstallFailedError: class extends Error {},
},
}))
const mockPlugin = () => ({})
mock.module("opencode-copilot-auth", () => ({ default: mockPlugin }))
mock.module("opencode-anthropic-auth", () => ({ default: mockPlugin }))
mock.module("@gitlab/opencode-gitlab-auth", () => ({ default: mockPlugin }))
import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { Provider } from "../../src/provider/provider"
@@ -0,0 +1,53 @@
import path from "path"
import { describe, expect, test } from "bun:test"
import { Instance } from "../../src/project/instance"
import { Session } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt"
import { tmpdir } from "../fixture/fixture"
describe("session.prompt missing file", () => {
test("does not fail the prompt when a file part is missing", async () => {
await using tmp = await tmpdir({
git: true,
config: {
agent: {
build: {
model: "openai/gpt-5.2",
},
},
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const missing = path.join(tmp.path, "does-not-exist.ts")
const msg = await SessionPrompt.prompt({
sessionID: session.id,
agent: "build",
noReply: true,
parts: [
{ type: "text", text: "please review @does-not-exist.ts" },
{
type: "file",
mime: "text/plain",
url: `file://${missing}`,
filename: "does-not-exist.ts",
},
],
})
if (msg.info.role !== "user") throw new Error("expected user message")
const hasFailure = msg.parts.some(
(part) => part.type === "text" && part.synthetic && part.text.includes("Read tool failed to read"),
)
expect(hasFailure).toBe(true)
await Session.remove(session.id)
},
})
})
})
@@ -73,4 +73,50 @@ describe("tool.registry", () => {
},
})
})
test("loads tools with external dependencies without crashing", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
const opencodeDir = path.join(dir, ".opencode")
await fs.mkdir(opencodeDir, { recursive: true })
const toolsDir = path.join(opencodeDir, "tools")
await fs.mkdir(toolsDir, { recursive: true })
await Bun.write(
path.join(opencodeDir, "package.json"),
JSON.stringify({
name: "custom-tools",
dependencies: {
"@kilocode/plugin": "^0.0.0",
cowsay: "^1.6.0",
},
}),
)
await Bun.write(
path.join(toolsDir, "cowsay.ts"),
[
"import { say } from 'cowsay'",
"export default {",
" description: 'tool that imports cowsay at top level',",
" args: { text: { type: 'string' } },",
" execute: async ({ text }: { text: string }) => {",
" return say({ text })",
" },",
"}",
"",
].join("\n"),
)
},
})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const ids = await ToolRegistry.ids()
expect(ids).toContain("cowsay")
},
})
})
})
+2 -1
View File
@@ -24,5 +24,6 @@
"@types/node": "catalog:",
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
}
},
"peerDependencies": {}
}
+3 -1
View File
@@ -8,5 +8,7 @@
"exports": {
".": "./src/index.ts"
},
"version": "1.0.14"
"version": "1.0.14",
"dependencies": {},
"peerDependencies": {}
}
+2 -1
View File
@@ -29,5 +29,6 @@
"dependencies": {},
"publishConfig": {
"directory": "dist"
}
},
"peerDependencies": {}
}
+4
View File
@@ -1313,6 +1313,10 @@ export type KeybindsConfig = {
* Toggle news on home screen
*/
news_toggle?: string
/**
* Toggle thinking blocks visibility
*/
display_thinking?: string
}
/**
+5
View File
@@ -9298,6 +9298,11 @@
"description": "Toggle news on home screen",
"default": "none",
"type": "string"
},
"display_thinking": {
"description": "Toggle thinking blocks visibility",
"default": "none",
"type": "string"
}
},
"additionalProperties": false
@@ -120,6 +120,11 @@
gap: calc(var(--spacing) * 0.5);
}
&[data-size="small"] {
width: 20px;
height: 20px;
}
&[data-size="large"] {
height: 32px;
/* padding: 0 8px 0 6px; */
+1 -1
View File
@@ -4,7 +4,7 @@ import { Icon, IconProps } from "./icon"
export interface IconButtonProps extends ComponentProps<typeof Kobalte> {
icon: IconProps["name"]
size?: "normal" | "large"
size?: "small" | "normal" | "large"
iconSize?: IconProps["size"]
variant?: "primary" | "secondary" | "ghost"
}
+1 -1
View File
@@ -69,7 +69,7 @@ function createCopyButton(labels: CopyLabels) {
button.type = "button"
button.setAttribute("data-component", "icon-button")
button.setAttribute("data-variant", "secondary")
button.setAttribute("data-size", "normal")
button.setAttribute("data-size", "small")
button.setAttribute("data-slot", "markdown-copy-button")
button.setAttribute("aria-label", labels.copy)
button.setAttribute("title", labels.copy)
+1 -1
View File
@@ -121,7 +121,7 @@
[data-slot="text-part-copy-wrapper"] {
position: absolute;
top: 8px;
top: -28px;
right: 8px;
opacity: 0;
transition: opacity 0.15s ease;
@@ -425,6 +425,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
>
<IconButton
icon={copied() ? "check" : "copy"}
size="small"
variant="secondary"
onMouseDown={(e) => e.preventDefault()}
onClick={(event) => {
@@ -694,6 +695,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
>
<IconButton
icon={copied() ? "check" : "copy"}
size="small"
variant="secondary"
onMouseDown={(e) => e.preventDefault()}
onClick={handleCopy}
+10 -6
View File
@@ -10,9 +10,9 @@
display: none;
}
/* [data-slot="session-review-container"] { */
/* height: 100%; */
/* } */
[data-slot="session-review-container"] {
flex: 1 1 auto;
}
[data-slot="session-review-header"] {
position: sticky;
@@ -44,10 +44,11 @@
[data-component="sticky-accordion-header"] {
top: 40px;
}
&[data-expanded]::before {
top: -40px;
}
[data-component="sticky-accordion-header"][data-expanded]::before,
[data-slot="accordion-item"][data-expanded] [data-component="sticky-accordion-header"]::before {
top: -40px;
}
[data-slot="accordion-trigger"] {
@@ -79,6 +80,7 @@
[data-slot="session-review-accordion-content"] {
position: relative;
z-index: 0;
overflow: hidden;
}
@@ -211,7 +213,9 @@
[data-slot="session-review-diff-wrapper"] {
position: relative;
overflow: hidden;
z-index: 0;
--line-comment-z: 5;
--line-comment-popover-z: 30;
--line-comment-open-z: 6;
}
}
+289 -282
View File
@@ -36,6 +36,8 @@ export type SessionReviewLineComment = {
export type SessionReviewFocus = { file: string; id: string }
export interface SessionReviewProps {
title?: JSX.Element
empty?: JSX.Element
split?: boolean
diffStyle?: SessionReviewDiffStyle
onDiffStyleChange?: (diffStyle: SessionReviewDiffStyle) => void
@@ -184,6 +186,7 @@ export const SessionReview = (props: SessionReviewProps) => {
const open = () => props.open ?? store.open
const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified")
const hasDiffs = () => props.diffs.length > 0
const handleChange = (open: string[]) => {
props.onOpenChange?.(open)
@@ -287,9 +290,9 @@ export const SessionReview = (props: SessionReviewProps) => {
[props.classes?.header ?? ""]: !!props.classes?.header,
}}
>
<div data-slot="session-review-title">{i18n.t("ui.sessionReview.title")}</div>
<div data-slot="session-review-title">{props.title ?? i18n.t("ui.sessionReview.title")}</div>
<div data-slot="session-review-actions">
<Show when={props.onDiffStyleChange}>
<Show when={hasDiffs() && props.onDiffStyleChange}>
<RadioGroup
options={["unified", "split"] as const}
current={diffStyle()}
@@ -300,12 +303,14 @@ export const SessionReview = (props: SessionReviewProps) => {
onSelect={(style) => style && props.onDiffStyleChange?.(style)}
/>
</Show>
<Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}>
<Switch>
<Match when={open().length > 0}>{i18n.t("ui.sessionReview.collapseAll")}</Match>
<Match when={true}>{i18n.t("ui.sessionReview.expandAll")}</Match>
</Switch>
</Button>
<Show when={hasDiffs()}>
<Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}>
<Switch>
<Match when={open().length > 0}>{i18n.t("ui.sessionReview.collapseAll")}</Match>
<Match when={true}>{i18n.t("ui.sessionReview.expandAll")}</Match>
</Switch>
</Button>
</Show>
{props.actions}
</div>
</div>
@@ -315,322 +320,324 @@ export const SessionReview = (props: SessionReviewProps) => {
[props.classes?.container ?? ""]: !!props.classes?.container,
}}
>
<Accordion multiple value={open()} onChange={handleChange}>
<For each={props.diffs}>
{(diff) => {
let wrapper: HTMLDivElement | undefined
<Show when={hasDiffs()} fallback={props.empty}>
<Accordion multiple value={open()} onChange={handleChange}>
<For each={props.diffs}>
{(diff) => {
let wrapper: HTMLDivElement | undefined
const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === diff.file))
const commentedLines = createMemo(() => comments().map((c) => c.selection))
const comments = createMemo(() => (props.comments ?? []).filter((c) => c.file === diff.file))
const commentedLines = createMemo(() => comments().map((c) => c.selection))
const beforeText = () => (typeof diff.before === "string" ? diff.before : "")
const afterText = () => (typeof diff.after === "string" ? diff.after : "")
const beforeText = () => (typeof diff.before === "string" ? diff.before : "")
const afterText = () => (typeof diff.after === "string" ? diff.after : "")
const isAdded = () => beforeText().length === 0 && afterText().length > 0
const isDeleted = () => afterText().length === 0 && beforeText().length > 0
const isImage = () => isImageFile(diff.file)
const isAudio = () => isAudioFile(diff.file)
const isAdded = () => beforeText().length === 0 && afterText().length > 0
const isDeleted = () => afterText().length === 0 && beforeText().length > 0
const isImage = () => isImageFile(diff.file)
const isAudio = () => isAudioFile(diff.file)
const diffImageSrc = dataUrlFromValue(diff.after) ?? dataUrlFromValue(diff.before)
const [imageSrc, setImageSrc] = createSignal<string | undefined>(diffImageSrc)
const [imageStatus, setImageStatus] = createSignal<"idle" | "loading" | "error">("idle")
const diffImageSrc = dataUrlFromValue(diff.after) ?? dataUrlFromValue(diff.before)
const [imageSrc, setImageSrc] = createSignal<string | undefined>(diffImageSrc)
const [imageStatus, setImageStatus] = createSignal<"idle" | "loading" | "error">("idle")
const diffAudioSrc = dataUrlFromValue(diff.after) ?? dataUrlFromValue(diff.before)
const [audioSrc, setAudioSrc] = createSignal<string | undefined>(diffAudioSrc)
const [audioStatus, setAudioStatus] = createSignal<"idle" | "loading" | "error">("idle")
const [audioMime, setAudioMime] = createSignal<string | undefined>(undefined)
const diffAudioSrc = dataUrlFromValue(diff.after) ?? dataUrlFromValue(diff.before)
const [audioSrc, setAudioSrc] = createSignal<string | undefined>(diffAudioSrc)
const [audioStatus, setAudioStatus] = createSignal<"idle" | "loading" | "error">("idle")
const [audioMime, setAudioMime] = createSignal<string | undefined>(undefined)
const selectedLines = createMemo(() => {
const current = selection()
if (!current || current.file !== diff.file) return null
return current.range
})
const selectedLines = createMemo(() => {
const current = selection()
if (!current || current.file !== diff.file) return null
return current.range
})
const draftRange = createMemo(() => {
const current = commenting()
if (!current || current.file !== diff.file) return null
return current.range
})
const draftRange = createMemo(() => {
const current = commenting()
if (!current || current.file !== diff.file) return null
return current.range
})
const [draft, setDraft] = createSignal("")
const [positions, setPositions] = createSignal<Record<string, number>>({})
const [draftTop, setDraftTop] = createSignal<number | undefined>(undefined)
const [draft, setDraft] = createSignal("")
const [positions, setPositions] = createSignal<Record<string, number>>({})
const [draftTop, setDraftTop] = createSignal<number | undefined>(undefined)
const getRoot = () => {
const el = wrapper
if (!el) return
const getRoot = () => {
const el = wrapper
if (!el) return
const host = el.querySelector("diffs-container")
if (!(host instanceof HTMLElement)) return
return host.shadowRoot ?? undefined
}
const updateAnchors = () => {
const el = wrapper
if (!el) return
const root = getRoot()
if (!root) return
const next: Record<string, number> = {}
for (const item of comments()) {
const marker = findMarker(root, item.selection)
if (!marker) continue
next[item.id] = markerTop(el, marker)
}
setPositions(next)
const range = draftRange()
if (!range) {
setDraftTop(undefined)
return
const host = el.querySelector("diffs-container")
if (!(host instanceof HTMLElement)) return
return host.shadowRoot ?? undefined
}
const marker = findMarker(root, range)
if (!marker) {
setDraftTop(undefined)
return
const updateAnchors = () => {
const el = wrapper
if (!el) return
const root = getRoot()
if (!root) return
const next: Record<string, number> = {}
for (const item of comments()) {
const marker = findMarker(root, item.selection)
if (!marker) continue
next[item.id] = markerTop(el, marker)
}
setPositions(next)
const range = draftRange()
if (!range) {
setDraftTop(undefined)
return
}
const marker = findMarker(root, range)
if (!marker) {
setDraftTop(undefined)
return
}
setDraftTop(markerTop(el, marker))
}
setDraftTop(markerTop(el, marker))
}
const scheduleAnchors = () => {
requestAnimationFrame(updateAnchors)
}
const scheduleAnchors = () => {
requestAnimationFrame(updateAnchors)
}
createEffect(() => {
comments()
scheduleAnchors()
})
createEffect(() => {
comments()
scheduleAnchors()
})
createEffect(() => {
const range = draftRange()
if (!range) return
setDraft("")
scheduleAnchors()
})
createEffect(() => {
const range = draftRange()
if (!range) return
setDraft("")
scheduleAnchors()
})
createEffect(() => {
if (!open().includes(diff.file)) return
if (!isImage()) return
if (imageSrc()) return
if (imageStatus() !== "idle") return
createEffect(() => {
if (!open().includes(diff.file)) return
if (!isImage()) return
if (imageSrc()) return
if (imageStatus() !== "idle") return
const reader = props.readFile
if (!reader) return
const reader = props.readFile
if (!reader) return
setImageStatus("loading")
reader(diff.file)
.then((result) => {
const src = dataUrl(result)
if (!src) {
setImageStatus("loading")
reader(diff.file)
.then((result) => {
const src = dataUrl(result)
if (!src) {
setImageStatus("error")
return
}
setImageSrc(src)
setImageStatus("idle")
})
.catch(() => {
setImageStatus("error")
return
}
setImageSrc(src)
setImageStatus("idle")
})
.catch(() => {
setImageStatus("error")
})
})
})
})
createEffect(() => {
if (!open().includes(diff.file)) return
if (!isAudio()) return
if (audioSrc()) return
if (audioStatus() !== "idle") return
createEffect(() => {
if (!open().includes(diff.file)) return
if (!isAudio()) return
if (audioSrc()) return
if (audioStatus() !== "idle") return
const reader = props.readFile
if (!reader) return
const reader = props.readFile
if (!reader) return
setAudioStatus("loading")
reader(diff.file)
.then((result) => {
const src = dataUrl(result)
if (!src) {
setAudioStatus("loading")
reader(diff.file)
.then((result) => {
const src = dataUrl(result)
if (!src) {
setAudioStatus("error")
return
}
setAudioMime(normalizeMimeType(result?.mimeType))
setAudioSrc(src)
setAudioStatus("idle")
})
.catch(() => {
setAudioStatus("error")
return
}
setAudioMime(normalizeMimeType(result?.mimeType))
setAudioSrc(src)
setAudioStatus("idle")
})
.catch(() => {
setAudioStatus("error")
})
})
})
})
const handleLineSelected = (range: SelectedLineRange | null) => {
if (!props.onLineComment) return
const handleLineSelected = (range: SelectedLineRange | null) => {
if (!props.onLineComment) return
if (!range) {
setSelection(null)
return
if (!range) {
setSelection(null)
return
}
setSelection({ file: diff.file, range })
}
setSelection({ file: diff.file, range })
}
const handleLineSelectionEnd = (range: SelectedLineRange | null) => {
if (!props.onLineComment) return
const handleLineSelectionEnd = (range: SelectedLineRange | null) => {
if (!props.onLineComment) return
if (!range) {
setCommenting(null)
return
}
if (!range) {
setCommenting(null)
return
setSelection({ file: diff.file, range })
setCommenting({ file: diff.file, range })
}
setSelection({ file: diff.file, range })
setCommenting({ file: diff.file, range })
}
const openComment = (comment: SessionReviewComment) => {
setOpened({ file: comment.file, id: comment.id })
setSelection({ file: comment.file, range: comment.selection })
}
const openComment = (comment: SessionReviewComment) => {
setOpened({ file: comment.file, id: comment.id })
setSelection({ file: comment.file, range: comment.selection })
}
const isCommentOpen = (comment: SessionReviewComment) => {
const current = opened()
if (!current) return false
return current.file === comment.file && current.id === comment.id
}
const isCommentOpen = (comment: SessionReviewComment) => {
const current = opened()
if (!current) return false
return current.file === comment.file && current.id === comment.id
}
return (
<Accordion.Item
value={diff.file}
id={diffId(diff.file)}
data-file={diff.file}
data-slot="session-review-accordion-item"
data-selected={props.focusedFile === diff.file ? "" : undefined}
>
<StickyAccordionHeader>
<Accordion.Trigger>
<div data-slot="session-review-trigger-content">
<div data-slot="session-review-file-info">
<FileIcon node={{ path: diff.file, type: "file" }} />
<div data-slot="session-review-file-name-container">
<Show when={diff.file.includes("/")}>
<span data-slot="session-review-directory">{`\u202A${getDirectory(diff.file)}\u202C`}</span>
</Show>
<span data-slot="session-review-filename">{getFilename(diff.file)}</span>
<Show when={props.onViewFile}>
<button
data-slot="session-review-view-button"
type="button"
onClick={(e) => {
e.stopPropagation()
props.onViewFile?.(diff.file)
}}
>
<Icon name="eye" size="small" />
</button>
</Show>
return (
<Accordion.Item
value={diff.file}
id={diffId(diff.file)}
data-file={diff.file}
data-slot="session-review-accordion-item"
data-selected={props.focusedFile === diff.file ? "" : undefined}
>
<StickyAccordionHeader>
<Accordion.Trigger>
<div data-slot="session-review-trigger-content">
<div data-slot="session-review-file-info">
<FileIcon node={{ path: diff.file, type: "file" }} />
<div data-slot="session-review-file-name-container">
<Show when={diff.file.includes("/")}>
<span data-slot="session-review-directory">{`\u202A${getDirectory(diff.file)}\u202C`}</span>
</Show>
<span data-slot="session-review-filename">{getFilename(diff.file)}</span>
<Show when={props.onViewFile}>
<button
data-slot="session-review-view-button"
type="button"
onClick={(e) => {
e.stopPropagation()
props.onViewFile?.(diff.file)
}}
>
<Icon name="eye" size="small" />
</button>
</Show>
</div>
</div>
<div data-slot="session-review-trigger-actions">
<Switch>
<Match when={isAdded()}>
<span data-slot="session-review-change" data-type="added">
{i18n.t("ui.sessionReview.change.added")}
</span>
</Match>
<Match when={isDeleted()}>
<span data-slot="session-review-change" data-type="removed">
{i18n.t("ui.sessionReview.change.removed")}
</span>
</Match>
<Match when={true}>
<DiffChanges changes={diff} />
</Match>
</Switch>
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
<div data-slot="session-review-trigger-actions">
<Switch>
<Match when={isAdded()}>
<span data-slot="session-review-change" data-type="added">
{i18n.t("ui.sessionReview.change.added")}
</span>
</Match>
<Match when={isDeleted()}>
<span data-slot="session-review-change" data-type="removed">
{i18n.t("ui.sessionReview.change.removed")}
</span>
</Match>
<Match when={true}>
<DiffChanges changes={diff} />
</Match>
</Switch>
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-review-accordion-content">
<div
data-slot="session-review-diff-wrapper"
ref={(el) => {
wrapper = el
anchors.set(diff.file, el)
scheduleAnchors()
}}
>
<Dynamic
component={diffComponent}
preloadedDiff={diff.preloaded}
diffStyle={diffStyle()}
onRendered={() => {
props.onDiffRendered?.()
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-review-accordion-content">
<div
data-slot="session-review-diff-wrapper"
ref={(el) => {
wrapper = el
anchors.set(diff.file, el)
scheduleAnchors()
}}
enableLineSelection={props.onLineComment != null}
onLineSelected={handleLineSelected}
onLineSelectionEnd={handleLineSelectionEnd}
selectedLines={selectedLines()}
commentedLines={commentedLines()}
before={{
name: diff.file!,
contents: typeof diff.before === "string" ? diff.before : "",
}}
after={{
name: diff.file!,
contents: typeof diff.after === "string" ? diff.after : "",
}}
/>
>
<Dynamic
component={diffComponent}
preloadedDiff={diff.preloaded}
diffStyle={diffStyle()}
onRendered={() => {
props.onDiffRendered?.()
scheduleAnchors()
}}
enableLineSelection={props.onLineComment != null}
onLineSelected={handleLineSelected}
onLineSelectionEnd={handleLineSelectionEnd}
selectedLines={selectedLines()}
commentedLines={commentedLines()}
before={{
name: diff.file!,
contents: typeof diff.before === "string" ? diff.before : "",
}}
after={{
name: diff.file!,
contents: typeof diff.after === "string" ? diff.after : "",
}}
/>
<For each={comments()}>
{(comment) => (
<LineComment
id={comment.id}
top={positions()[comment.id]}
onMouseEnter={() => setSelection({ file: comment.file, range: comment.selection })}
onClick={() => {
if (isCommentOpen(comment)) {
setOpened(null)
return
}
<For each={comments()}>
{(comment) => (
<LineComment
id={comment.id}
top={positions()[comment.id]}
onMouseEnter={() => setSelection({ file: comment.file, range: comment.selection })}
onClick={() => {
if (isCommentOpen(comment)) {
setOpened(null)
return
}
openComment(comment)
}}
open={isCommentOpen(comment)}
comment={comment.comment}
selection={selectionLabel(comment.selection)}
/>
)}
</For>
<Show when={draftRange()}>
{(range) => (
<Show when={draftTop() !== undefined}>
<LineCommentEditor
top={draftTop()}
value={draft()}
selection={selectionLabel(range())}
onInput={setDraft}
onCancel={() => setCommenting(null)}
onSubmit={(comment) => {
props.onLineComment?.({
file: diff.file,
selection: range(),
comment,
preview: selectionPreview(diff, range()),
})
setCommenting(null)
openComment(comment)
}}
open={isCommentOpen(comment)}
comment={comment.comment}
selection={selectionLabel(comment.selection)}
/>
</Show>
)}
</Show>
</div>
</Accordion.Content>
</Accordion.Item>
)
}}
</For>
</Accordion>
)}
</For>
<Show when={draftRange()}>
{(range) => (
<Show when={draftTop() !== undefined}>
<LineCommentEditor
top={draftTop()}
value={draft()}
selection={selectionLabel(range())}
onInput={setDraft}
onCancel={() => setCommenting(null)}
onSubmit={(comment) => {
props.onLineComment?.({
file: diff.file,
selection: range(),
comment,
preview: selectionPreview(diff, range()),
})
setCommenting(null)
}}
/>
</Show>
)}
</Show>
</div>
</Accordion.Content>
</Accordion.Item>
)
}}
</For>
</Accordion>
</Show>
</div>
</div>
)
+16 -9
View File
@@ -219,22 +219,28 @@
gap: 4px;
align-self: stretch;
[data-slot="session-turn-summary-title-row"] {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
[data-slot="session-turn-response"] {
position: relative;
width: 100%;
}
[data-slot="session-turn-response-copy-wrapper"] {
position: absolute;
top: 8px;
right: 8px;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: 1;
}
[data-slot="session-turn-response"]:hover [data-slot="session-turn-response-copy-wrapper"] {
&:hover [data-slot="session-turn-response-copy-wrapper"],
&:focus-within [data-slot="session-turn-response-copy-wrapper"] {
opacity: 1;
pointer-events: auto;
}
p {
@@ -409,10 +415,11 @@
[data-component="sticky-accordion-header"] {
top: var(--sticky-header-height, 0px);
}
&[data-expanded]::before {
top: calc(-1 * var(--sticky-header-height, 0px));
}
[data-component="sticky-accordion-header"][data-expanded]::before,
[data-slot="accordion-item"][data-expanded] [data-component="sticky-accordion-header"]::before {
top: calc(-1 * var(--sticky-header-height, 0px));
}
[data-slot="session-turn-accordion-trigger-content"] {
+13 -112
View File
@@ -8,25 +8,16 @@ import {
TextPart,
ToolPart,
} from "@kilocode/sdk/v2/client"
import { type FileDiff } from "@kilocode/sdk/v2"
import { useData } from "../context"
import { useDiffComponent } from "../context/diff"
import { type UiI18nKey, type UiI18nParams, useI18n } from "../context/i18n"
import { findLast } from "@opencode-ai/util/array"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { Binary } from "@opencode-ai/util/binary"
import { createEffect, createMemo, createSignal, For, Match, on, onCleanup, ParentProps, Show, Switch } from "solid-js"
import { DiffChanges } from "./diff-changes"
import { Message, Part } from "./message-part"
import { Markdown } from "./markdown"
import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
import { IconButton } from "./icon-button"
import { Card } from "./card"
import { Dynamic } from "solid-js/web"
import { Button } from "./button"
import { Spinner } from "./spinner"
import { Tooltip } from "./tooltip"
@@ -143,7 +134,6 @@ export function SessionTurn(
) {
const i18n = useI18n()
const data = useData()
const diffComponent = useDiffComponent()
const emptyMessages: MessageType[] = []
const emptyParts: PartType[] = []
@@ -153,7 +143,6 @@ export function SessionTurn(
const emptyPermissionParts: { part: ToolPart; message: AssistantMessage }[] = []
const emptyQuestions: QuestionRequest[] = []
const emptyQuestionParts: { part: ToolPart; message: AssistantMessage }[] = []
const emptyDiffs: FileDiff[] = []
const idle = { type: "idle" as const }
const allMessages = createMemo(() => data.store.message[props.sessionID] ?? emptyMessages)
@@ -409,8 +398,7 @@ export function SessionTurn(
const response = createMemo(() => lastTextPart()?.text)
const responsePartId = createMemo(() => lastTextPart()?.id)
const messageDiffs = createMemo(() => message()?.summary?.diffs ?? emptyDiffs)
const hasDiffs = createMemo(() => messageDiffs().length > 0)
const hasDiffs = createMemo(() => (message()?.summary?.diffs?.length ?? 0) > 0)
const hideResponsePart = createMemo(() => !working() && !!responsePartId())
const [copied, setCopied] = createSignal(false)
@@ -476,28 +464,12 @@ export function SessionTurn(
updateStickyHeight(sticky.getBoundingClientRect().height)
})
const diffInit = 20
const diffBatch = 20
const [store, setStore] = createStore({
retrySeconds: 0,
diffsOpen: [] as string[],
diffLimit: diffInit,
status: rawStatus(),
duration: duration(),
})
createEffect(
on(
() => message()?.id,
() => {
setStore("diffsOpen", [])
setStore("diffLimit", diffInit)
},
{ defer: true },
),
)
createEffect(() => {
const r = retry()
if (!r) {
@@ -727,17 +699,11 @@ export function SessionTurn(
<div class="sr-only" aria-live="polite">
{!working() && response() ? response() : ""}
</div>
<Show when={!working() && (response() || hasDiffs())}>
<Show when={!working() && response()}>
<div data-slot="session-turn-summary-section">
<div data-slot="session-turn-summary-header">
<h2 data-slot="session-turn-summary-title">{i18n.t("ui.sessionTurn.summary.response")}</h2>
<div data-slot="session-turn-response">
<Markdown
data-slot="session-turn-markdown"
data-diffs={hasDiffs()}
text={response() ?? ""}
cacheKey={responsePartId()}
/>
<div data-slot="session-turn-summary-title-row">
<h2 data-slot="session-turn-summary-title">{i18n.t("ui.sessionTurn.summary.response")}</h2>
<Show when={response()}>
<div data-slot="session-turn-response-copy-wrapper">
<Tooltip
@@ -747,6 +713,7 @@ export function SessionTurn(
>
<IconButton
icon={copied() ? "check" : "copy"}
size="small"
variant="secondary"
onMouseDown={(e) => e.preventDefault()}
onClick={(event) => {
@@ -759,81 +726,15 @@ export function SessionTurn(
</div>
</Show>
</div>
<div data-slot="session-turn-response">
<Markdown
data-slot="session-turn-markdown"
data-diffs={hasDiffs()}
text={response() ?? ""}
cacheKey={responsePartId()}
/>
</div>
</div>
<Accordion
data-slot="session-turn-accordion"
multiple
value={store.diffsOpen}
onChange={(value) => {
if (!Array.isArray(value)) return
setStore("diffsOpen", value)
}}
>
<For each={messageDiffs().slice(0, store.diffLimit)}>
{(diff) => (
<Accordion.Item value={diff.file}>
<StickyAccordionHeader>
<Accordion.Trigger>
<div data-slot="session-turn-accordion-trigger-content">
<div data-slot="session-turn-file-info">
<FileIcon
node={{ path: diff.file, type: "file" }}
data-slot="session-turn-file-icon"
/>
<div data-slot="session-turn-file-path">
<Show when={diff.file.includes("/")}>
<span data-slot="session-turn-directory">
{`\u202A${getDirectory(diff.file)}\u202C`}
</span>
</Show>
<span data-slot="session-turn-filename">{getFilename(diff.file)}</span>
</div>
</div>
<div data-slot="session-turn-accordion-actions">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content data-slot="session-turn-accordion-content">
<Show when={store.diffsOpen.includes(diff.file!)}>
<Dynamic
component={diffComponent}
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Show>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
<Show when={messageDiffs().length > store.diffLimit}>
<Button
data-slot="session-turn-accordion-more"
variant="ghost"
size="small"
onClick={() => {
const total = messageDiffs().length
setStore("diffLimit", (limit) => {
const next = limit + diffBatch
if (next > total) return total
return next
})
}}
>
{i18n.t("ui.sessionTurn.diff.showMore", {
count: messageDiffs().length - store.diffLimit,
})}
</Button>
</Show>
</div>
</Show>
<Show when={error() && !props.stepsExpanded}>
@@ -1,16 +1,18 @@
[data-component="sticky-accordion-header"] {
position: sticky;
top: 0px;
}
&[data-expanded] {
z-index: 10;
[data-component="sticky-accordion-header"][data-expanded],
[data-slot="accordion-item"][data-expanded] [data-component="sticky-accordion-header"] {
z-index: 10;
}
&::before {
content: "";
z-index: -10;
position: absolute;
inset: 0;
background-color: var(--background-stronger);
}
}
[data-component="sticky-accordion-header"][data-expanded]::before,
[data-slot="accordion-item"][data-expanded] [data-component="sticky-accordion-header"]::before {
content: "";
z-index: -10;
position: absolute;
inset: 0;
background-color: var(--background-stronger);
}
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "تغييرات الجلسة",
"ui.sessionReview.title.lastTurn": "تغييرات آخر دور",
"ui.sessionReview.diffStyle.unified": "موجد",
"ui.sessionReview.diffStyle.split": "منقسم",
"ui.sessionReview.expandAll": "توسيع الكل",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Alterações da sessão",
"ui.sessionReview.title.lastTurn": "Alterações do último turno",
"ui.sessionReview.diffStyle.unified": "Unificado",
"ui.sessionReview.diffStyle.split": "Dividido",
"ui.sessionReview.expandAll": "Expandir tudo",
+108
View File
@@ -0,0 +1,108 @@
import { dict as en } from "./en"
type Keys = keyof typeof en
export const dict = {
"ui.sessionReview.title": "Promjene sesije",
"ui.sessionReview.title.lastTurn": "Promjene u posljednjem potezu",
"ui.sessionReview.diffStyle.unified": "Ujedinjeno",
"ui.sessionReview.diffStyle.split": "Podijeljeno",
"ui.sessionReview.expandAll": "Proširi sve",
"ui.sessionReview.collapseAll": "Sažmi sve",
"ui.sessionReview.change.added": "Dodano",
"ui.sessionReview.change.removed": "Uklonjeno",
"ui.lineComment.label.prefix": "Komentar na ",
"ui.lineComment.label.suffix": "",
"ui.lineComment.editorLabel.prefix": "Komentarišeš ",
"ui.lineComment.editorLabel.suffix": "",
"ui.lineComment.placeholder": "Dodaj komentar",
"ui.lineComment.submit": "Komentariši",
"ui.sessionTurn.steps.show": "Prikaži korake",
"ui.sessionTurn.steps.hide": "Sakrij korake",
"ui.sessionTurn.summary.response": "Odgovor",
"ui.sessionTurn.diff.showMore": "Prikaži još izmjena ({{count}})",
"ui.sessionTurn.retry.retrying": "ponovni pokušaj",
"ui.sessionTurn.retry.inSeconds": "za {{seconds}}s",
"ui.sessionTurn.status.delegating": "Delegiranje posla",
"ui.sessionTurn.status.planning": "Planiranje sljedećih koraka",
"ui.sessionTurn.status.gatheringContext": "Prikupljanje konteksta",
"ui.sessionTurn.status.searchingCodebase": "Pretraživanje baze koda",
"ui.sessionTurn.status.searchingWeb": "Pretraživanje weba",
"ui.sessionTurn.status.makingEdits": "Pravljenje izmjena",
"ui.sessionTurn.status.runningCommands": "Pokretanje komandi",
"ui.sessionTurn.status.thinking": "Razmišljanje",
"ui.sessionTurn.status.thinkingWithTopic": "Razmišljanje - {{topic}}",
"ui.sessionTurn.status.gatheringThoughts": "Sređivanje misli",
"ui.sessionTurn.status.consideringNextSteps": "Razmatranje sljedećih koraka",
"ui.messagePart.diagnostic.error": "Greška",
"ui.messagePart.title.edit": "Uredi",
"ui.messagePart.title.write": "Napiši",
"ui.messagePart.option.typeOwnAnswer": "Unesi svoj odgovor",
"ui.messagePart.review.title": "Pregledaj svoje odgovore",
"ui.list.loading": "Učitavanje",
"ui.list.empty": "Nema rezultata",
"ui.list.clearFilter": "Očisti filter",
"ui.list.emptyWithFilter.prefix": "Nema rezultata za",
"ui.list.emptyWithFilter.suffix": "",
"ui.messageNav.newMessage": "Nova poruka",
"ui.textField.copyToClipboard": "Kopiraj u međuspremnik",
"ui.textField.copyLink": "Kopiraj link",
"ui.textField.copied": "Kopirano",
"ui.imagePreview.alt": "Pregled slike",
"ui.tool.read": "Čitanje",
"ui.tool.loaded": "Učitano",
"ui.tool.list": "Listanje",
"ui.tool.glob": "Glob",
"ui.tool.grep": "Grep",
"ui.tool.webfetch": "Web preuzimanje",
"ui.tool.shell": "Shell",
"ui.tool.patch": "Patch",
"ui.tool.todos": "Lista zadataka",
"ui.tool.todos.read": "Čitanje liste zadataka",
"ui.tool.questions": "Pitanja",
"ui.tool.agent": "{{type}} agent",
"ui.common.file.one": "datoteka",
"ui.common.file.other": "datoteke",
"ui.common.question.one": "pitanje",
"ui.common.question.other": "pitanja",
"ui.common.add": "Dodaj",
"ui.common.cancel": "Otkaži",
"ui.common.confirm": "Potvrdi",
"ui.common.dismiss": "Odbaci",
"ui.common.close": "Zatvori",
"ui.common.next": "Dalje",
"ui.common.submit": "Pošalji",
"ui.permission.deny": "Zabrani",
"ui.permission.allowAlways": "Uvijek dozvoli",
"ui.permission.allowOnce": "Dozvoli jednom",
"ui.message.expand": "Proširi poruku",
"ui.message.collapse": "Sažmi poruku",
"ui.message.copy": "Kopiraj",
"ui.message.copied": "Kopirano!",
"ui.message.attachment.alt": "prilog",
"ui.patch.action.deleted": "Obrisano",
"ui.patch.action.created": "Kreirano",
"ui.patch.action.moved": "Premješteno",
"ui.patch.action.patched": "Primijenjeno",
"ui.question.subtitle.answered": "{{count}} odgovoreno",
"ui.question.answer.none": "(nema odgovora)",
"ui.question.review.notAnswered": "(nije odgovoreno)",
"ui.question.multiHint": "(odaberi sve što važi)",
"ui.question.custom.placeholder": "Unesi svoj odgovor...",
} satisfies Partial<Record<Keys, string>>
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Sessionsændringer",
"ui.sessionReview.title.lastTurn": "Ændringer fra sidste tur",
"ui.sessionReview.diffStyle.unified": "Samlet",
"ui.sessionReview.diffStyle.split": "Opdelt",
"ui.sessionReview.expandAll": "Udvid alle",
+1
View File
@@ -4,6 +4,7 @@ type Keys = keyof typeof en
export const dict = {
"ui.sessionReview.title": "Sitzungsänderungen",
"ui.sessionReview.title.lastTurn": "Änderungen der letzten Runde",
"ui.sessionReview.diffStyle.unified": "Vereinheitlicht",
"ui.sessionReview.diffStyle.split": "Geteilt",
"ui.sessionReview.expandAll": "Alle erweitern",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Session changes",
"ui.sessionReview.title.lastTurn": "Last turn changes",
"ui.sessionReview.diffStyle.unified": "Unified",
"ui.sessionReview.diffStyle.split": "Split",
"ui.sessionReview.expandAll": "Expand all",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Cambios de la sesión",
"ui.sessionReview.title.lastTurn": "Cambios del último turno",
"ui.sessionReview.diffStyle.unified": "Unificado",
"ui.sessionReview.diffStyle.split": "Dividido",
"ui.sessionReview.expandAll": "Expandir todo",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Modifications de la session",
"ui.sessionReview.title.lastTurn": "Modifications du dernier tour",
"ui.sessionReview.diffStyle.unified": "Unifié",
"ui.sessionReview.diffStyle.split": "Divisé",
"ui.sessionReview.expandAll": "Tout développer",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "セッションの変更",
"ui.sessionReview.title.lastTurn": "前回ターンの変更",
"ui.sessionReview.diffStyle.unified": "Unified",
"ui.sessionReview.diffStyle.split": "Split",
"ui.sessionReview.expandAll": "すべて展開",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "세션 변경 사항",
"ui.sessionReview.title.lastTurn": "마지막 턴 변경 사항",
"ui.sessionReview.diffStyle.unified": "통합 보기",
"ui.sessionReview.diffStyle.split": "분할 보기",
"ui.sessionReview.expandAll": "모두 펼치기",
+1
View File
@@ -3,6 +3,7 @@ type Keys = keyof typeof en
export const dict: Record<Keys, string> = {
"ui.sessionReview.title": "Sesjonsendringer",
"ui.sessionReview.title.lastTurn": "Endringer i siste tur",
"ui.sessionReview.diffStyle.unified": "Samlet",
"ui.sessionReview.diffStyle.split": "Delt",
"ui.sessionReview.expandAll": "Utvid alle",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Zmiany w sesji",
"ui.sessionReview.title.lastTurn": "Zmiany z ostatniej tury",
"ui.sessionReview.diffStyle.unified": "Ujednolicony",
"ui.sessionReview.diffStyle.split": "Podzielony",
"ui.sessionReview.expandAll": "Rozwiń wszystko",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "Изменения сессии",
"ui.sessionReview.title.lastTurn": "Изменения последнего хода",
"ui.sessionReview.diffStyle.unified": "Объединённый",
"ui.sessionReview.diffStyle.split": "Разделённый",
"ui.sessionReview.expandAll": "Развернуть всё",
+1
View File
@@ -1,5 +1,6 @@
export const dict = {
"ui.sessionReview.title": "การเปลี่ยนแปลงเซสชัน",
"ui.sessionReview.title.lastTurn": "การเปลี่ยนแปลงของเทิร์นล่าสุด",
"ui.sessionReview.diffStyle.unified": "แบบรวม",
"ui.sessionReview.diffStyle.split": "แบบแยก",
"ui.sessionReview.expandAll": "ขยายทั้งหมด",
+1
View File
@@ -4,6 +4,7 @@ type Keys = keyof typeof en
export const dict = {
"ui.sessionReview.title": "会话变更",
"ui.sessionReview.title.lastTurn": "上一轮变更",
"ui.sessionReview.diffStyle.unified": "统一",
"ui.sessionReview.diffStyle.split": "拆分",
"ui.sessionReview.expandAll": "全部展开",
+1
View File
@@ -4,6 +4,7 @@ type Keys = keyof typeof en
export const dict = {
"ui.sessionReview.title": "工作階段變更",
"ui.sessionReview.title.lastTurn": "上一輪變更",
"ui.sessionReview.diffStyle.unified": "整合",
"ui.sessionReview.diffStyle.split": "拆分",
"ui.sessionReview.expandAll": "全部展開",
+2 -1
View File
@@ -37,5 +37,6 @@
"@kilocode/cli": "workspace:*",
"@types/node": "catalog:",
"typescript": "catalog:"
}
},
"peerDependencies": {}
}
+2 -1
View File
@@ -97,7 +97,8 @@ Kilo has a list of keybinds that you can customize through the Kilo config.
"history_next": "down",
"terminal_suspend": "ctrl+z",
"terminal_title_toggle": "none",
"tips_toggle": "<leader>h"
"tips_toggle": "<leader>h",
"display_thinking": "none"
}
}
```

Some files were not shown because too many files have changed in this diff Show More